// Package crypto derives the keys that protect tunnel traffic and provides
// the authenticated framing used on direct peer-to-peer connections.
package crypto

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"strings"

	"golang.org/x/crypto/hkdf"
)

// keySize is the AES-256 key length.
const keySize = 32

// hkdfSalt separates this key schedule from any other use of the same tokens.
const hkdfSalt = "usb-server/tunnel/v1"

// TunnelSecret is the long-lived group secret derived from the three tokens.
//
// It deliberately is NOT the group hash. The relay is told the hash so it can
// group clients, which means anyone running the relay knows it — using it to
// encrypt would protect nothing from the party best positioned to look. The
// tokens themselves never leave the client, and the hash is a SHA-256 of them,
// so knowing the hash does not yield this secret.
type TunnelSecret struct {
	master []byte
}

// DeriveTunnelSecret builds the group secret from the three tokens.
// All three must be non-empty; a client configured with only the group hash
// cannot participate in encrypted tunnels.
func DeriveTunnelSecret(token1, token2, token3 string) (*TunnelSecret, error) {
	if token1 == "" || token2 == "" || token3 == "" {
		return nil, fmt.Errorf("all three tokens are required to derive the tunnel key")
	}

	// Same joining as the group hash, so both are bound to the same input.
	combined := strings.Join([]string{token1, token2, token3}, ":")

	master := make([]byte, keySize)
	r := hkdf.New(sha256.New, []byte(combined), []byte(hkdfSalt), []byte("master"))
	if _, err := io.ReadFull(r, master); err != nil {
		return nil, fmt.Errorf("deriving master key: %w", err)
	}

	return &TunnelSecret{master: master}, nil
}

// TunnelKey derives the key for one tunnel from its ID.
//
// Every tunnel gets a fresh random ID, so each connection gets a distinct key
// and nonces can restart from zero without ever repeating a (key, nonce) pair.
func (s *TunnelSecret) TunnelKey(tunnelID string) ([]byte, error) {
	key := make([]byte, keySize)
	r := hkdf.New(sha256.New, s.master, []byte(hkdfSalt), []byte("tunnel:"+tunnelID))
	if _, err := io.ReadFull(r, key); err != nil {
		return nil, fmt.Errorf("deriving tunnel key: %w", err)
	}
	return key, nil
}

// PeerToken produces a short value a peer can present to prove it knows the
// group secret, bound to the given context string.
//
// This authenticates direct connections: the relay can tell two clients how to
// reach each other, but it cannot forge this, so a peer that presents a valid
// token really is a group member rather than whoever happens to reach the port.
func (s *TunnelSecret) PeerToken(context string) string {
	r := hkdf.New(sha256.New, s.master, []byte(hkdfSalt), []byte("peer-token:"+context))
	token := make([]byte, 32)
	io.ReadFull(r, token)
	return hex.EncodeToString(token)
}
