// Package identity manages Ed25519 cryptographic identities
package identity

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

	"golang.org/x/crypto/scrypt"
	"golang.org/x/crypto/nacl/secretbox"
)

// Identity represents a cryptographic identity
type Identity struct {
	PublicKey  ed25519.PublicKey
	PrivateKey ed25519.PrivateKey
}

// encryptedIdentity is the encrypted on-disk format
type encryptedIdentity struct {
	Nonce      [24]byte `json:"nonce"`
	Ciphertext []byte   `json:"ciphertext"`
	Salt       []byte   `json:"salt"`
}

// NewIdentity generates a new Ed25519 identity
func NewIdentity() (*Identity, error) {
	pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("failed to generate keypair: %w", err)
	}

	return &Identity{
		PublicKey:  pubKey,
		PrivateKey: privKey,
	}, nil
}

// LoadIdentity loads an encrypted identity from disk
func LoadIdentity(path string, passphrase []byte) (*Identity, error) {
	// Read encrypted file
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read identity file: %w", err)
	}

	// Parse JSON
	var encrypted encryptedIdentity
	if err := json.Unmarshal(data, &encrypted); err != nil {
		return nil, fmt.Errorf("failed to parse identity file: %w", err)
	}

	// Derive encryption key from passphrase
	key, err := scrypt.Key(passphrase, encrypted.Salt, 32768, 8, 1, 32)
	if err != nil {
		return nil, fmt.Errorf("failed to derive key: %w", err)
	}

	// Decrypt private key
	var keyArray [32]byte
	copy(keyArray[:], key)

	plaintext, ok := secretbox.Open(nil, encrypted.Ciphertext, &encrypted.Nonce, &keyArray)
	if !ok {
		return nil, fmt.Errorf("decryption failed: wrong passphrase or corrupted file")
	}

	// Parse private key
	if len(plaintext) != ed25519.PrivateKeySize {
		return nil, fmt.Errorf("invalid private key size")
	}

	privKey := ed25519.PrivateKey(plaintext)
	pubKey := privKey.Public().(ed25519.PublicKey)

	return &Identity{
		PublicKey:  pubKey,
		PrivateKey: privKey,
	}, nil
}

// Save encrypts and saves the identity to disk
func (id *Identity) Save(path string, passphrase []byte) error {
	// Generate random salt
	salt := make([]byte, 32)
	if _, err := rand.Read(salt); err != nil {
		return fmt.Errorf("failed to generate salt: %w", err)
	}

	// Derive encryption key
	key, err := scrypt.Key(passphrase, salt, 32768, 8, 1, 32)
	if err != nil {
		return fmt.Errorf("failed to derive key: %w", err)
	}

	// Generate random nonce
	var nonce [24]byte
	if _, err := rand.Read(nonce[:]); err != nil {
		return fmt.Errorf("failed to generate nonce: %w", err)
	}

	// Encrypt private key
	var keyArray [32]byte
	copy(keyArray[:], key)

	ciphertext := secretbox.Seal(nil, id.PrivateKey, &nonce, &keyArray)

	// Create encrypted structure
	encrypted := encryptedIdentity{
		Nonce:      nonce,
		Ciphertext: ciphertext,
		Salt:       salt,
	}

	// Marshal to JSON
	data, err := json.MarshalIndent(encrypted, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal identity: %w", err)
	}

	// Write to file with secure permissions
	if err := os.WriteFile(path, data, 0600); err != nil {
		return fmt.Errorf("failed to write identity file: %w", err)
	}

	return nil
}

// GetPublicKeyHex returns the public key as hex string
func (id *Identity) GetPublicKeyHex() string {
	return hex.EncodeToString(id.PublicKey)
}

// GetPrivateKeyHex returns the private key as hex string (use with caution!)
func (id *Identity) GetPrivateKeyHex() string {
	return hex.EncodeToString(id.PrivateKey)
}

// Sign signs a message with the private key
func (id *Identity) Sign(message []byte) []byte {
	return ed25519.Sign(id.PrivateKey, message)
}

// Verify verifies a signature
func (id *Identity) Verify(message, signature []byte) bool {
	return ed25519.Verify(id.PublicKey, message, signature)
}

// SecureWipe zeros out sensitive key material from memory
func (id *Identity) SecureWipe() {
	// Zero out private key
	for i := range id.PrivateKey {
		id.PrivateKey[i] = 0
	}
	// Public key is not sensitive, but wipe it anyway
	for i := range id.PublicKey {
		id.PublicKey[i] = 0
	}
}

// String returns a safe string representation
func (id *Identity) String() string {
	return fmt.Sprintf("Identity(pubkey=%s...)", id.GetPublicKeyHex()[:16])
}
