package vault

import (
	"crypto/ed25519"
	"errors"
	"fmt"

	"git.wntrmute.dev/mc/mcias/internal/crypto"
	"git.wntrmute.dev/mc/mcias/internal/db"
)

// DeriveFromPassphrase derives the master encryption key from a passphrase
// using the Argon2id KDF with a salt stored in the database.
//
// Security: The Argon2id parameters used by crypto.DeriveKey exceed OWASP 2023
// minimums (time=3, memory=128MiB, threads=4). The salt is 32 random bytes
// stored in the database on first run.
func DeriveFromPassphrase(passphrase string, database *db.DB) ([]byte, error) {
	salt, err := database.ReadMasterKeySalt()
	if errors.Is(err, db.ErrNotFound) {
		return nil, fmt.Errorf("no master key salt in database (first-run requires startup passphrase)")
	}
	if err != nil {
		return nil, fmt.Errorf("read master key salt: %w", err)
	}

	key, err := crypto.DeriveKey(passphrase, salt)
	if err != nil {
		return nil, fmt.Errorf("derive master key: %w", err)
	}
	return key, nil
}

// DecryptSigningKey decrypts the Ed25519 signing key pair from the database
// using the provided master key.
//
// Security: The private key is stored AES-256-GCM encrypted in the database.
// A fresh random nonce is used for each encryption. The plaintext key only
// exists in memory during the process lifetime.
func DecryptSigningKey(database *db.DB, masterKey []byte) (ed25519.PrivateKey, ed25519.PublicKey, error) {
	enc, nonce, err := database.ReadServerConfig()
	if err != nil {
		return nil, nil, fmt.Errorf("read server config: %w", err)
	}
	if enc == nil || nonce == nil {
		return nil, nil, fmt.Errorf("no signing key in database (first-run requires startup passphrase)")
	}

	privPEM, err := crypto.OpenAESGCM(masterKey, nonce, enc)
	if err != nil {
		return nil, nil, fmt.Errorf("decrypt signing key: %w", err)
	}

	priv, err := crypto.ParsePrivateKeyPEM(privPEM)
	if err != nil {
		return nil, nil, fmt.Errorf("parse signing key PEM: %w", err)
	}

	// Security: ed25519.PrivateKey.Public() always returns ed25519.PublicKey,
	// but we use the ok form to make the type assertion explicit and safe.
	pub, ok := priv.Public().(ed25519.PublicKey)
	if !ok {
		return nil, nil, fmt.Errorf("signing key has unexpected public key type")
	}

	return priv, pub, nil
}
