// Package crypto provides cryptographic utilities for FetchML
package crypto

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"os"

	"github.com/jfraeys/fetch_ml/internal/fileutil"
)

// ManifestSigner provides Ed25519 signing for run manifests
type ManifestSigner struct {
	keyID      string
	privateKey ed25519.PrivateKey
	publicKey  ed25519.PublicKey
}

// SigningResult contains the signature and metadata
type SigningResult struct {
	Signature string `json:"signature"`
	KeyID     string `json:"key_id"`
	Algorithm string `json:"algorithm"`
}

// GenerateSigningKeys creates a new Ed25519 keypair for manifest signing
// This should be done once and the keys stored securely
func GenerateSigningKeys() (publicKey, privateKey []byte, err error) {
	return ed25519.GenerateKey(rand.Reader)
}

// NewManifestSigner creates a signer from a private key
func NewManifestSigner(privateKey []byte, keyID string) (*ManifestSigner, error) {
	if len(privateKey) != ed25519.PrivateKeySize {
		return nil, fmt.Errorf("invalid private key size: expected %d, got %d",
			ed25519.PrivateKeySize, len(privateKey))
	}

	// Extract public key from private key
	pubKey := make([]byte, ed25519.PublicKeySize)
	copy(pubKey, privateKey[32:])

	return &ManifestSigner{
		privateKey: ed25519.PrivateKey(privateKey),
		publicKey:  ed25519.PublicKey(pubKey),
		keyID:      keyID,
	}, nil
}

// SignManifest signs a manifest and returns the signing result
// The manifest is canonicalized to JSON before signing
func (s *ManifestSigner) SignManifest(manifest any) (*SigningResult, error) {
	// Marshal to canonical JSON (sorted keys)
	data, err := json.Marshal(manifest)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal manifest: %w", err)
	}

	// Sign the data
	signature := ed25519.Sign(s.privateKey, data)

	return &SigningResult{
		Signature: base64.StdEncoding.EncodeToString(signature),
		KeyID:     s.keyID,
		Algorithm: "Ed25519",
	}, nil
}

// SignManifestBytes signs raw bytes directly (for pre-serialized manifests)
func (s *ManifestSigner) SignManifestBytes(data []byte) (*SigningResult, error) {
	signature := ed25519.Sign(s.privateKey, data)

	return &SigningResult{
		Signature: base64.StdEncoding.EncodeToString(signature),
		KeyID:     s.keyID,
		Algorithm: "Ed25519",
	}, nil
}

// VerifyManifest verifies a manifest signature
func VerifyManifest(manifest any, result *SigningResult, publicKey []byte) (bool, error) {
	if result.Algorithm != "Ed25519" {
		return false, fmt.Errorf("unsupported algorithm: %s", result.Algorithm)
	}

	// Marshal manifest to same format used for signing
	data, err := json.Marshal(manifest)
	if err != nil {
		return false, fmt.Errorf("failed to marshal manifest: %w", err)
	}

	// Decode signature
	signature, err := base64.StdEncoding.DecodeString(result.Signature)
	if err != nil {
		return false, fmt.Errorf("failed to decode signature: %w", err)
	}

	// Verify
	return ed25519.Verify(ed25519.PublicKey(publicKey), data, signature), nil
}

// VerifyManifestBytes verifies raw bytes directly
func VerifyManifestBytes(data []byte, result *SigningResult, publicKey []byte) (bool, error) {
	if result.Algorithm != "Ed25519" {
		return false, fmt.Errorf("unsupported algorithm: %s", result.Algorithm)
	}

	signature, err := base64.StdEncoding.DecodeString(result.Signature)
	if err != nil {
		return false, fmt.Errorf("failed to decode signature: %w", err)
	}

	return ed25519.Verify(ed25519.PublicKey(publicKey), data, signature), nil
}

// GetPublicKey returns the signer's public key
func (s *ManifestSigner) GetPublicKey() []byte {
	return s.publicKey
}

// GetKeyID returns the signer's key ID
func (s *ManifestSigner) GetKeyID() string {
	return s.keyID
}

// SavePrivateKeyToFile saves a private key to a file with restricted permissions and crash safety (fsync)
func SavePrivateKeyToFile(key []byte, path string) error {
	// Write with restricted permissions (owner read/write only) and fsync
	if err := fileutil.WriteFileSafe(path, key, 0600); err != nil {
		return fmt.Errorf("failed to write private key: %w", err)
	}
	return nil
}

// LoadPrivateKeyFromFile loads a private key from a file
func LoadPrivateKeyFromFile(path string) ([]byte, error) {
	key, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read private key: %w", err)
	}

	if len(key) != ed25519.PrivateKeySize {
		return nil, fmt.Errorf("invalid private key size: expected %d, got %d",
			ed25519.PrivateKeySize, len(key))
	}

	return key, nil
}

// SavePublicKeyToFile saves a public key to a file with crash safety (fsync)
func SavePublicKeyToFile(key []byte, path string) error {
	if err := fileutil.WriteFileSafe(path, key, 0644); err != nil {
		return fmt.Errorf("failed to write public key: %w", err)
	}
	return nil
}

// LoadPublicKeyFromFile loads a public key from a file
func LoadPublicKeyFromFile(path string) ([]byte, error) {
	key, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read public key: %w", err)
	}

	if len(key) != ed25519.PublicKeySize {
		return nil, fmt.Errorf("invalid public key size: expected %d, got %d",
			ed25519.PublicKeySize, len(key))
	}

	return key, nil
}

// EncodeKeyToBase64 encodes a key to base64 for storage/transmission
func EncodeKeyToBase64(key []byte) string {
	return base64.StdEncoding.EncodeToString(key)
}

// DecodeKeyFromBase64 decodes a key from base64
func DecodeKeyFromBase64(encoded string) ([]byte, error) {
	return base64.StdEncoding.DecodeString(encoded)
}
