package authn

import (
	"bytes"
	"crypto/ed25519"
	"crypto/x509"
	"encoding/binary"
	"encoding/pem"
	"errors"
	"fmt"
	"os"
)

const (
	// ResponseDomainMarkerV1 binds the v1 server response signature to the
	// Galaxy gateway transport contract.
	ResponseDomainMarkerV1 = "galaxy-response-v1"
)

var (
	// ErrInvalidResponsePrivateKeyPEM reports that the configured response
	// signer private key is not a strict PKCS#8 PEM-encoded private key.
	ErrInvalidResponsePrivateKeyPEM = errors.New("response signer private key is not a valid PKCS#8 PEM block")

	// ErrInvalidResponsePrivateKey reports that the configured response signer
	// private key is not an Ed25519 private key.
	ErrInvalidResponsePrivateKey = errors.New("response signer private key must be an Ed25519 PKCS#8 private key")

	// ErrInvalidResponseSignature reports that a server response signature is
	// not a raw Ed25519 signature for the canonical response signing input.
	ErrInvalidResponseSignature = errors.New("invalid response signature")
)

// ResponseSigningFields contains the canonical v1 response fields that are
// bound into the server signing input.
type ResponseSigningFields struct {
	// ProtocolVersion identifies the transport envelope version.
	ProtocolVersion string

	// RequestID is the transport correlation identifier copied from the
	// authenticated request.
	RequestID string

	// TimestampMS carries the server response timestamp in milliseconds.
	TimestampMS int64

	// ResultCode is the opaque downstream result code returned to the client.
	ResultCode string

	// PayloadHash is the raw SHA-256 digest of response payload bytes.
	PayloadHash []byte
}

// ResponseSigner signs authenticated unary responses and client-facing stream
// events with one server-side key.
type ResponseSigner interface {
	// SignResponse returns the raw Ed25519 signature for the canonical response
	// signing input built from fields.
	SignResponse(fields ResponseSigningFields) ([]byte, error)

	// SignEvent returns the raw Ed25519 signature for the canonical event
	// signing input built from fields.
	SignEvent(fields EventSigningFields) ([]byte, error)
}

// Ed25519ResponseSigner signs authenticated responses with one Ed25519 private
// key loaded during process startup.
type Ed25519ResponseSigner struct {
	privateKey ed25519.PrivateKey
}

// NewEd25519ResponseSigner validates privateKey and constructs a signer using
// a defensive key copy.
func NewEd25519ResponseSigner(privateKey ed25519.PrivateKey) (*Ed25519ResponseSigner, error) {
	if len(privateKey) != ed25519.PrivateKeySize {
		return nil, ErrInvalidResponsePrivateKey
	}

	return &Ed25519ResponseSigner{
		privateKey: bytes.Clone(privateKey),
	}, nil
}

// LoadEd25519ResponseSignerFromPEMFile loads a strict PKCS#8 PEM-encoded
// Ed25519 private key from path and constructs a signer.
func LoadEd25519ResponseSignerFromPEMFile(path string) (*Ed25519ResponseSigner, error) {
	pemBytes, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read response signer private key PEM: %w", err)
	}

	signer, err := ParseEd25519ResponseSignerPEM(pemBytes)
	if err != nil {
		return nil, err
	}

	return signer, nil
}

// ParseEd25519ResponseSignerPEM parses one strict PKCS#8 PEM-encoded Ed25519
// private key and constructs a signer from it.
func ParseEd25519ResponseSignerPEM(pemBytes []byte) (*Ed25519ResponseSigner, error) {
	block, rest := pem.Decode(pemBytes)
	if block == nil || block.Type != "PRIVATE KEY" || len(bytes.TrimSpace(rest)) > 0 {
		return nil, ErrInvalidResponsePrivateKeyPEM
	}

	parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return nil, ErrInvalidResponsePrivateKeyPEM
	}

	privateKey, ok := parsedKey.(ed25519.PrivateKey)
	if !ok {
		return nil, ErrInvalidResponsePrivateKey
	}

	return NewEd25519ResponseSigner(privateKey)
}

// PublicKey returns the Ed25519 public key that corresponds to the configured
// response signer private key.
func (s *Ed25519ResponseSigner) PublicKey() ed25519.PublicKey {
	if s == nil {
		return nil
	}

	publicKey, _ := s.privateKey.Public().(ed25519.PublicKey)
	return bytes.Clone(publicKey)
}

// SignResponse signs the canonical v1 response signing input built from
// fields.
func (s *Ed25519ResponseSigner) SignResponse(fields ResponseSigningFields) ([]byte, error) {
	if s == nil || len(s.privateKey) != ed25519.PrivateKeySize {
		return nil, ErrInvalidResponsePrivateKey
	}

	signature := ed25519.Sign(s.privateKey, BuildResponseSigningInput(fields))
	return bytes.Clone(signature), nil
}

// SignEvent signs the canonical v1 stream-event signing input built from
// fields.
func (s *Ed25519ResponseSigner) SignEvent(fields EventSigningFields) ([]byte, error) {
	if s == nil || len(s.privateKey) != ed25519.PrivateKeySize {
		return nil, ErrInvalidResponsePrivateKey
	}

	signature := ed25519.Sign(s.privateKey, BuildEventSigningInput(fields))
	return bytes.Clone(signature), nil
}

// BuildResponseSigningInput returns the canonical byte sequence the v1 server
// response signature covers. String and byte fields are length-prefixed with
// uvarint(len(field)) followed by raw bytes, while TimestampMS is appended as
// an 8-byte big-endian uint64.
func BuildResponseSigningInput(fields ResponseSigningFields) []byte {
	size := len(ResponseDomainMarkerV1) +
		len(fields.ProtocolVersion) +
		len(fields.RequestID) +
		len(fields.ResultCode) +
		len(fields.PayloadHash) +
		(5 * binary.MaxVarintLen64) +
		8

	buf := make([]byte, 0, size)
	buf = appendLengthPrefixedString(buf, ResponseDomainMarkerV1)
	buf = appendLengthPrefixedString(buf, fields.ProtocolVersion)
	buf = appendLengthPrefixedString(buf, fields.RequestID)
	buf = binary.BigEndian.AppendUint64(buf, uint64(fields.TimestampMS))
	buf = appendLengthPrefixedString(buf, fields.ResultCode)
	buf = appendLengthPrefixedBytes(buf, fields.PayloadHash)

	return buf
}

// VerifyResponseSignature verifies that signature authenticates fields under
// publicKey using the canonical v1 response signing input.
func VerifyResponseSignature(publicKey ed25519.PublicKey, signature []byte, fields ResponseSigningFields) error {
	if len(publicKey) != ed25519.PublicKeySize || len(signature) != ed25519.SignatureSize {
		return ErrInvalidResponseSignature
	}
	if !ed25519.Verify(publicKey, BuildResponseSigningInput(fields), signature) {
		return ErrInvalidResponseSignature
	}

	return nil
}
