package crypto

import (
	"crypto/rand"
	"crypto/sha256"
	"errors"
	"fmt"
	"io"
	"log"

	"github.com/awnumar/memguard"
	"github.com/tyler-smith/go-bip39"
	"golang.org/x/crypto/argon2"
	"golang.org/x/crypto/nacl/box"
)

// GenerateRandomKey generates a new random key using crypto_secretbox_keygen
// JavaScript equivalent: sodium.randombytes_buf(crypto.MasterKeySize)
func GenerateRandomKey(size int) ([]byte, error) {
	if size <= 0 {
		return nil, errors.New("key size must be positive")
	}

	key := make([]byte, size)
	_, err := io.ReadFull(rand.Reader, key)
	if err != nil {
		return nil, fmt.Errorf("failed to generate random key: %w", err)
	}
	return key, nil
}

// GenerateKeyPair generates a public/private key pair using NaCl box
// JavaScript equivalent: sodium.crypto_box_keypair()
func GenerateKeyPair() (publicKey, privateKey []byte, verificationID string, err error) {
	pubKey, privKey, err := box.GenerateKey(rand.Reader)
	if err != nil {
		return nil, nil, "", fmt.Errorf("failed to generate key pair: %w", err)
	}

	// Convert from fixed-size arrays to slices
	publicKey = pubKey[:]
	privateKey = privKey[:]

	// Generate deterministic verification ID
	verificationID, err = GenerateVerificationID(publicKey[:])
	if err != nil {
		return nil, nil, "", fmt.Errorf("failed to generate verification ID: %w", err)
	}

	return publicKey, privateKey, verificationID, nil
}

// DeriveKeyFromPassword derives a key encryption key from a password using Argon2id
// JavaScript equivalent: sodium.crypto_pwhash()
// SECURITY: Password bytes are wiped from memory after key derivation.
func DeriveKeyFromPassword(password string, salt []byte) ([]byte, error) {
	if len(salt) != Argon2SaltSize {
		return nil, fmt.Errorf("invalid salt size: expected %d, got %d", Argon2SaltSize, len(salt))
	}

	// Convert password to bytes for wiping
	passwordBytes := []byte(password)
	defer memguard.WipeBytes(passwordBytes) // SECURITY: Wipe password bytes after use

	// These parameters must match between Go and JavaScript
	key := argon2.IDKey(
		passwordBytes,
		salt,
		Argon2OpsLimit,
		Argon2MemLimit,
		Argon2Parallelism,
		Argon2KeySize,
	)

	return key, nil
}

// GenerateRandomNonce generates a random nonce for ChaCha20-Poly1305 encryption operations
// JavaScript equivalent: sodium.randombytes_buf(crypto.NonceSize)
func GenerateRandomNonce() ([]byte, error) {
	nonce := make([]byte, NonceSize) // NonceSize is now 12 for ChaCha20-Poly1305
	_, err := io.ReadFull(rand.Reader, nonce)
	if err != nil {
		return nil, fmt.Errorf("failed to generate random nonce: %w", err)
	}
	return nonce, nil
}

// GenerateVerificationID creates a human-readable representation of a public key
// JavaScript equivalent: The same BIP39 mnemonic implementation
// Generate VerificationID from public key (deterministic)
func GenerateVerificationID(publicKey []byte) (string, error) {
	if len(publicKey) == 0 {
		return "", errors.New("public key cannot be empty")
	}

	// 1. Hash the public key with SHA256
	hash := sha256.Sum256(publicKey)

	// 2. Use the hash as entropy for BIP39
	mnemonic, err := bip39.NewMnemonic(hash[:])
	if err != nil {
		return "", fmt.Errorf("failed to generate verification ID: %w", err)
	}

	return mnemonic, nil
}

// VerifyVerificationID checks if a verification ID matches a public key
func VerifyVerificationID(publicKey []byte, verificationID string) bool {
	expectedID, err := GenerateVerificationID(publicKey)
	if err != nil {
		log.Printf("pkg.crypto.VerifyVerificationID - Failed to generate verification ID with error: %v\n", err)
		return false
	}
	return expectedID == verificationID
}
