package steme

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"os"
	"time"
)

// Signer handles Ed25519 signing for assertions.
//
// All assertions submitted to StemeDB must be signed by at least one agent.
// The Signer abstracts the cryptographic details and provides a simple API.
type Signer struct {
	privateKey ed25519.PrivateKey
	publicKey  ed25519.PublicKey
}

// NewSigner creates a Signer from a 32-byte Ed25519 private key seed.
//
// The seed should be exactly 32 bytes. The Ed25519 private key is derived
// from this seed, and the public key is computed from the private key.
//
// Returns ErrInvalidKeySize if the seed is not 32 bytes.
func NewSigner(seed []byte) (*Signer, error) {
	if len(seed) != ed25519.SeedSize {
		return nil, fmt.Errorf("%w: got %d bytes", ErrInvalidKeySize, len(seed))
	}

	privateKey := ed25519.NewKeyFromSeed(seed)
	publicKey := privateKey.Public().(ed25519.PublicKey)

	return &Signer{
		privateKey: privateKey,
		publicKey:  publicKey,
	}, nil
}

// NewSignerFromHex creates a Signer from a hex-encoded 32-byte seed.
//
// The hex string should be exactly 64 characters (32 bytes).
//
// Returns ErrInvalidHex if hex decoding fails.
// Returns ErrInvalidKeySize if the decoded seed is not 32 bytes.
func NewSignerFromHex(hexSeed string) (*Signer, error) {
	seed, err := hex.DecodeString(hexSeed)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", ErrInvalidHex, err)
	}

	return NewSigner(seed)
}

// GenerateSigner generates a new random Ed25519 keypair.
//
// This is useful for testing or creating new agent identities.
// The private key seed can be retrieved with Seed() for persistence.
func GenerateSigner() (*Signer, error) {
	publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("steme: failed to generate keypair: %w", err)
	}

	return &Signer{
		privateKey: privateKey,
		publicKey:  publicKey,
	}, nil
}

// SignerFromEnv creates a Signer from an environment variable.
//
// The environment variable should contain a hex-encoded 32-byte seed.
//
// Returns an error if the variable is not set, contains invalid hex,
// or is not exactly 32 bytes when decoded.
func SignerFromEnv(envVar string) (*Signer, error) {
	hexSeed := os.Getenv(envVar)
	if hexSeed == "" {
		return nil, fmt.Errorf("steme: environment variable %s not set", envVar)
	}

	return NewSignerFromHex(hexSeed)
}

// PublicKey returns the hex-encoded Ed25519 public key (32 bytes).
//
// This is the agent_id used in StemeDB.
func (s *Signer) PublicKey() string {
	return hex.EncodeToString(s.publicKey)
}

// PublicKeyBytes returns the raw Ed25519 public key bytes.
func (s *Signer) PublicKeyBytes() []byte {
	return s.publicKey
}

// Seed returns the hex-encoded Ed25519 private key seed (32 bytes).
//
// This should be stored securely for key recovery.
func (s *Signer) Seed() string {
	// Ed25519 private key is 64 bytes: [32-byte seed][32-byte public key]
	// Extract the seed portion
	return hex.EncodeToString(s.privateKey.Seed())
}

// Sign creates an Ed25519 signature over the message bytes.
//
// The signature is 64 bytes and is hex-encoded in the returned string.
func (s *Signer) Sign(message []byte) string {
	signature := ed25519.Sign(s.privateKey, message)
	return hex.EncodeToString(signature)
}

// CreateSignature creates a SignatureEntry for an assertion (v1 legacy format).
//
// The message should be the canonical serialization "{subject}:{predicate}".
// This only covers subject and predicate - other fields can be tampered.
// For full tamper protection, use CreateSignatureV2.
//
// The timestamp is set to the current Unix epoch.
func (s *Signer) CreateSignature(message []byte) SignatureEntry {
	timestamp := uint64(time.Now().Unix())

	return SignatureEntry{
		AgentID:   s.PublicKey(),
		Signature: s.Sign(message),
		Timestamp: timestamp,
		Version:   1,
	}
}

// CreateSignatureV2 creates a SignatureEntry using v2 enterprise format.
//
// The contentHash should be the BLAKE3 hash of the serialized assertion
// (without signatures). This protects ALL fields from tampering.
//
// The timestamp is set to the current Unix epoch.
func (s *Signer) CreateSignatureV2(contentHash []byte) SignatureEntry {
	timestamp := uint64(time.Now().Unix())

	return SignatureEntry{
		AgentID:   s.PublicKey(),
		Signature: s.Sign(contentHash),
		Timestamp: timestamp,
		Version:   2,
	}
}

// Verify verifies an Ed25519 signature.
//
// This is useful for validating signatures received from other agents.
//
// Returns ErrInvalidPublicKeySize if the public key is not 32 bytes.
// Returns ErrInvalidSignatureSize if the signature is not 64 bytes.
func Verify(publicKeyHex, signatureHex string, message []byte) error {
	publicKey, err := hex.DecodeString(publicKeyHex)
	if err != nil {
		return fmt.Errorf("%w: invalid public key hex: %v", ErrInvalidHex, err)
	}

	if len(publicKey) != ed25519.PublicKeySize {
		return fmt.Errorf("%w: public key is %d bytes", ErrInvalidPublicKeySize, len(publicKey))
	}

	signature, err := hex.DecodeString(signatureHex)
	if err != nil {
		return fmt.Errorf("%w: invalid signature hex: %v", ErrInvalidHex, err)
	}

	if len(signature) != ed25519.SignatureSize {
		return fmt.Errorf("%w: signature is %d bytes", ErrInvalidSignatureSize, len(signature))
	}

	if !ed25519.Verify(publicKey, message, signature) {
		return fmt.Errorf("steme: signature verification failed")
	}

	return nil
}
