// Package authn defines the authenticated transport helpers used by
// the gateway edge verification pipeline. The package is public so
// that external clients (notably the integration test suite under
// `galaxy/integration/testenv`) can reuse the canonical signing
// input builders and the response/event verifiers without having to
// duplicate the wire contract documented in
// `../../docs/ARCHITECTURE.md` §15.
package authn

import (
	"bytes"
	"crypto/sha256"
	"encoding/binary"
	"errors"
)

const (
	// RequestDomainMarkerV1 binds the v1 client request signature to the Galaxy
	// gateway transport contract.
	RequestDomainMarkerV1 = "galaxy-request-v1"
)

var (
	// ErrInvalidPayloadHash reports that payloadHash is not a raw SHA-256 digest.
	ErrInvalidPayloadHash = errors.New("payload_hash must be a 32-byte SHA-256 digest")

	// ErrPayloadHashMismatch reports that payloadHash does not match payloadBytes.
	ErrPayloadHashMismatch = errors.New("payload_hash does not match payload_bytes")
)

// RequestSigningFields contains the canonical v1 request fields that are bound
// into the client signing input after the gateway validates and normalizes the
// request envelope.
type RequestSigningFields struct {
	// ProtocolVersion identifies the transport envelope version.
	ProtocolVersion string

	// DeviceSessionID identifies the authenticated device session bound to the
	// request.
	DeviceSessionID string

	// MessageType is the stable downstream routing key.
	MessageType string

	// TimestampMS carries the client request timestamp in milliseconds.
	TimestampMS int64

	// RequestID is the transport correlation and anti-replay identifier.
	RequestID string

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

// BuildRequestSigningInput returns the canonical byte sequence the v1 client
// request 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. The caller is expected to pass fields that have
// already passed earlier envelope validation.
func BuildRequestSigningInput(fields RequestSigningFields) []byte {
	size := len(RequestDomainMarkerV1) +
		len(fields.ProtocolVersion) +
		len(fields.DeviceSessionID) +
		len(fields.MessageType) +
		len(fields.RequestID) +
		len(fields.PayloadHash) +
		(6 * binary.MaxVarintLen64) +
		8

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

	return buf
}

// VerifyPayloadHash checks that payloadHash is the raw SHA-256 digest of
// payloadBytes. Empty payloadBytes are valid and must use sha256.Sum256(nil).
func VerifyPayloadHash(payloadBytes, payloadHash []byte) error {
	if len(payloadHash) != sha256.Size {
		return ErrInvalidPayloadHash
	}

	sum := sha256.Sum256(payloadBytes)
	if !bytes.Equal(sum[:], payloadHash) {
		return ErrPayloadHashMismatch
	}

	return nil
}

func appendLengthPrefixedString(dst []byte, value string) []byte {
	return appendLengthPrefixedBytes(dst, []byte(value))
}

func appendLengthPrefixedBytes(dst []byte, value []byte) []byte {
	dst = binary.AppendUvarint(dst, uint64(len(value)))
	dst = append(dst, value...)

	return dst
}
