package client

import (
	"fmt"
	"log"

	"github.com/duffy/usb-server/internal/crypto"
)

// tunnelCodec seals and opens tunnel payloads.
//
// It sits above the transport so that a tunnel is protected the same way
// whether its frames travel directly or through the relay. A nil codec passes
// data through unchanged, which is what a client configured with only a group
// hash — and therefore unable to derive the key — falls back to.
type tunnelCodec struct {
	sealer *crypto.Sealer
	opener *crypto.Opener
}

// newTunnelCodec builds a codec for one end of a tunnel.
// send is the direction this end transmits in; it receives on the other.
func newTunnelCodec(secret *crypto.TunnelSecret, tunnelID string, send crypto.Direction) (*tunnelCodec, error) {
	if secret == nil {
		return nil, nil // unencrypted, by configuration
	}

	key, err := secret.TunnelKey(tunnelID)
	if err != nil {
		return nil, err
	}

	recv := crypto.DirShareToUse
	if send == crypto.DirShareToUse {
		recv = crypto.DirUseToShare
	}

	sealer, err := crypto.NewSealer(key, send)
	if err != nil {
		return nil, err
	}
	opener, err := crypto.NewOpener(key, recv)
	if err != nil {
		return nil, err
	}

	return &tunnelCodec{sealer: sealer, opener: opener}, nil
}

// encode prepares a payload for transmission.
func (c *tunnelCodec) encode(payload []byte) ([]byte, error) {
	if c == nil {
		return payload, nil
	}
	return c.sealer.Seal(payload)
}

// decode recovers a received payload.
func (c *tunnelCodec) decode(frame []byte) ([]byte, error) {
	if c == nil {
		return frame, nil
	}
	return c.opener.Open(frame)
}

// encrypted reports whether this codec actually protects anything.
func (c *tunnelCodec) encrypted() bool { return c != nil }

// tunnelSender delivers one encoded frame to the peer.
type tunnelSender func(frame []byte) error

// relaySender routes frames through the relay, tagged with the tunnel ID.
func relaySender(c *Client, tunnelID string) tunnelSender {
	return func(frame []byte) error {
		return c.SendTunnelData(tunnelID, frame)
	}
}

// directSender routes frames over an established direct connection.
func directSender(conn *directConn) tunnelSender {
	return func(frame []byte) error {
		return conn.WriteFrame(frame)
	}
}

// send encodes a payload and hands it to the transport.
func send(codec *tunnelCodec, sender tunnelSender, payload []byte) error {
	frame, err := codec.encode(payload)
	if err != nil {
		return fmt.Errorf("encoding tunnel frame: %w", err)
	}
	return sender(frame)
}

// receiveLoop reads frames from a direct connection, decodes them and hands
// each payload to deliver. It returns when the connection ends, when the
// tunnel is torn down, or on the first frame that fails to authenticate.
func receiveLoop(conn *directConn, codec *tunnelCodec, deliver func([]byte) error, done <-chan struct{}, label string) {
	for {
		select {
		case <-done:
			return
		default:
		}

		frame, err := conn.ReadFrame()
		if err != nil {
			select {
			case <-done:
			default:
				log.Printf("[direct] %s: read ended: %v", label, err)
			}
			return
		}

		payload, err := codec.decode(frame)
		if err != nil {
			// A frame that fails to authenticate means the stream is either
			// corrupt or being tampered with. Either way this tunnel cannot
			// be trusted to carry USB traffic any further.
			log.Printf("[direct] %s: dropping connection: %v", label, err)
			return
		}

		if err := deliver(payload); err != nil {
			log.Printf("[direct] %s: delivery failed: %v", label, err)
			return
		}
	}
}
