// Package agehd derives deterministic X25519 age identities from a
// BIP-39 seed using a vendor/application-scoped BIP-85 path:
//
//	m / 83696968′ / <vendor id>′ / <application id>′ / n′
//
// • vendor id  = 592 366 788  (sha256("berlin.sneak")  & 0x7fffffff)
// • app id     = 733 482 323  (sha256("secret")        & 0x7fffffff)
// • n          = sequential index (0,1,…)
package agehd

import (
	"fmt"
	"strings"

	"filippo.io/age"
	"git.eeqj.de/sneak/secret/pkg/bip85"
	"github.com/btcsuite/btcd/btcutil/hdkeychain"
	"github.com/btcsuite/btcd/chaincfg"
	"github.com/btcsuite/btcutil/bech32"
	"github.com/tyler-smith/go-bip39"
)

const (
	purpose  = uint32(83696968)  // fixed by BIP-85 ("bip")
	vendorID = uint32(592366788) // berlin.sneak
	appID    = uint32(733482323) // secret
	hrp      = "age-secret-key-" // Bech32 HRP used by age
)

// clamp applies RFC-7748 clamping to a 32-byte scalar.
func clamp(k []byte) {
	k[0] &= 248
	k[31] &= 127
	k[31] |= 64
}

// IdentityFromEntropy converts 32 deterministic bytes into an
// *age.X25519Identity by round-tripping through Bech32.
func IdentityFromEntropy(ent []byte) (*age.X25519Identity, error) {
	if len(ent) != 32 {
		return nil, fmt.Errorf("need 32-byte scalar, got %d", len(ent))
	}

	// Make a copy to avoid modifying the original
	key := make([]byte, 32)
	copy(key, ent)
	clamp(key)

	data, err := bech32.ConvertBits(key, 8, 5, true)
	if err != nil {
		return nil, fmt.Errorf("bech32 convert: %w", err)
	}
	s, err := bech32.Encode(hrp, data)
	if err != nil {
		return nil, fmt.Errorf("bech32 encode: %w", err)
	}
	return age.ParseX25519Identity(strings.ToUpper(s))
}

// DeriveEntropy derives 32 bytes of application-scoped entropy from the
// supplied BIP-39 mnemonic and index n using BIP85.
func DeriveEntropy(mnemonic string, n uint32) ([]byte, error) {
	// Convert mnemonic to seed
	seed := bip39.NewSeed(mnemonic, "")

	// Create master key from seed
	masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
	if err != nil {
		return nil, fmt.Errorf("failed to create master key: %w", err)
	}

	// Build the BIP85 derivation path: m/83696968'/vendor'/app'/n'
	path := fmt.Sprintf("m/%d'/%d'/%d'/%d'", purpose, vendorID, appID, n)

	// Derive BIP85 entropy (64 bytes)
	entropy, err := bip85.DeriveBIP85Entropy(masterKey, path)
	if err != nil {
		return nil, fmt.Errorf("failed to derive BIP85 entropy: %w", err)
	}

	// Use BIP85 DRNG to generate deterministic 32 bytes for the age key
	drng := bip85.NewBIP85DRNG(entropy)
	key := make([]byte, 32)
	_, err = drng.Read(key)
	if err != nil {
		return nil, fmt.Errorf("failed to read from DRNG: %w", err)
	}

	return key, nil
}

// DeriveEntropyFromXPRV derives 32 bytes of application-scoped entropy from the
// supplied extended private key (xprv) and index n using BIP85.
func DeriveEntropyFromXPRV(xprv string, n uint32) ([]byte, error) {
	// Parse the extended private key
	masterKey, err := bip85.ParseMasterKey(xprv)
	if err != nil {
		return nil, fmt.Errorf("failed to parse master key: %w", err)
	}

	// Build the BIP85 derivation path: m/83696968'/vendor'/app'/n'
	path := fmt.Sprintf("m/%d'/%d'/%d'/%d'", purpose, vendorID, appID, n)

	// Derive BIP85 entropy (64 bytes)
	entropy, err := bip85.DeriveBIP85Entropy(masterKey, path)
	if err != nil {
		return nil, fmt.Errorf("failed to derive BIP85 entropy: %w", err)
	}

	// Use BIP85 DRNG to generate deterministic 32 bytes for the age key
	drng := bip85.NewBIP85DRNG(entropy)
	key := make([]byte, 32)
	_, err = drng.Read(key)
	if err != nil {
		return nil, fmt.Errorf("failed to read from DRNG: %w", err)
	}

	return key, nil
}

// DeriveIdentity is the primary public helper that derives a deterministic
// age identity from a BIP39 mnemonic and index.
func DeriveIdentity(mnemonic string, n uint32) (*age.X25519Identity, error) {
	ent, err := DeriveEntropy(mnemonic, n)
	if err != nil {
		return nil, err
	}
	return IdentityFromEntropy(ent)
}

// DeriveIdentityFromXPRV derives a deterministic age identity from an
// extended private key (xprv) and index.
func DeriveIdentityFromXPRV(xprv string, n uint32) (*age.X25519Identity, error) {
	ent, err := DeriveEntropyFromXPRV(xprv, n)
	if err != nil {
		return nil, err
	}
	return IdentityFromEntropy(ent)
}