// Cryptographic key types, generation, loading, and box encryption helpers. Uses Ed25519
// for signing and NaCl box (Curve25519) for payload encryption.
package cryptography

import (
	"crypto/rand"
	"fmt"
	"os"
	"path/filepath"

	"github.com/libp2p/go-libp2p/core/crypto"
	"github.com/libp2p/go-libp2p/core/peer"
	"golang.org/x/crypto/curve25519"
	"golang.org/x/crypto/nacl/box"
)

// KeyPair holds an Ed25519 private/public keypair used for libp2p identity and signing.
type KeyPair struct {
	PrivateKey crypto.PrivKey
	PublicKey  crypto.PubKey
}

// BoxKeyPair holds a NaCl box (Curve25519) keypair for anonymous sealed-box encryption.
type BoxKeyPair struct {
	PrivateKey *[32]byte
	PublicKey  *[32]byte
}

// OperatorKey bundles the Ed25519 identity, NaCl box keys for message decryption,
// and a 32-byte auth token shared with implants at build time.
type OperatorKey struct {
	KeyPair
	PeerID    peer.ID
	BoxKeys   *BoxKeyPair
	AuthToken []byte
}

// ImplantKey bundles the Ed25519 identity and stores the operator's public key for
// verifying incoming command signatures.
type ImplantKey struct {
	KeyPair
	PeerID          peer.ID
	OperatorPubKey crypto.PubKey
}

// GenerateBoxKey creates a fresh NaCl box keypair for anonymous sealed-box encryption.
func GenerateBoxKey() (*BoxKeyPair, error) {
	pub, priv, err := box.GenerateKey(rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("generate box key: %w", err)
	}
	return &BoxKeyPair{PrivateKey: priv, PublicKey: pub}, nil
}

// LoadBoxKey derives a box keypair from a 32-byte private key seed.
func LoadBoxKey(data []byte) *BoxKeyPair {
	var priv [32]byte
	copy(priv[:], data)
	pub, err := curve25519.X25519(priv[:], curve25519.Basepoint)
	if err != nil {
		return nil
	}
	var pubArr [32]byte
	copy(pubArr[:], pub)
	return &BoxKeyPair{PrivateKey: &priv, PublicKey: &pubArr}
}

// Marshal returns the box private key as 32 raw bytes.
func (kp *BoxKeyPair) Marshal() []byte {
	return kp.PrivateKey[:]
}

// GenerateOperatorKey creates a new Ed25519 identity, box keypair, and 32-byte auth token.
func GenerateOperatorKey() (*OperatorKey, error) {
	priv, pub, err := crypto.GenerateEd25519Key(rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("generate operator key: %w", err)
	}
	pid, err := peer.IDFromPublicKey(pub)
	if err != nil {
		return nil, fmt.Errorf("peer id from pubkey: %w", err)
	}
	boxKeys, err := GenerateBoxKey()
	if err != nil {
		return nil, fmt.Errorf("generate box key: %w", err)
	}
	token := make([]byte, 32)
	if _, err := rand.Read(token); err != nil {
		return nil, fmt.Errorf("generate auth token: %w", err)
	}
	return &OperatorKey{
		KeyPair:   KeyPair{PrivateKey: priv, PublicKey: pub},
		PeerID:    pid,
		BoxKeys:   boxKeys,
		AuthToken: token,
	}, nil
}

// GenerateImplantKey creates a fresh Ed25519 keypair and stores a reference to the
// operator's public key for signature verification.
func GenerateImplantKey(operatorPub crypto.PubKey) (*ImplantKey, error) {
	priv, pub, err := crypto.GenerateEd25519Key(rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("generate implant key: %w", err)
	}
	pid, err := peer.IDFromPublicKey(pub)
	if err != nil {
		return nil, fmt.Errorf("peer id from pubkey: %w", err)
	}
	return &ImplantKey{
		KeyPair:        KeyPair{PrivateKey: priv, PublicKey: pub},
		PeerID:         pid,
		OperatorPubKey: operatorPub,
	}, nil
}

// LoadPrivateKey deserializes an Ed25519 private key from protobuf wire format.
func LoadPrivateKey(data []byte) (crypto.PrivKey, error) {
	return crypto.UnmarshalPrivateKey(data)
}

// MarshalPrivateKey serializes a private key to protobuf wire format.
func MarshalPrivateKey(priv crypto.PrivKey) ([]byte, error) {
	return crypto.MarshalPrivateKey(priv)
}

// PubKeyFromBytes deserializes a public key from protobuf wire format.
func PubKeyFromBytes(data []byte) (crypto.PubKey, error) {
	return crypto.UnmarshalPublicKey(data)
}

// Verify checks an Ed25519 signature against the given public key and data.
func Verify(pub crypto.PubKey, data []byte, sig []byte) (bool, error) {
	return pub.Verify(data, sig)
}

// BoxKeyPath returns the path to the operator's box private key file.
func BoxKeyPath(dir string) string {
	return filepath.Join(dir, "operator.boxkey")
}

// BoxPubKeyPath returns the path to the operator's box public key file.
func BoxPubKeyPath(dir string) string {
	return filepath.Join(dir, "operator.boxpub")
}

// AuthTokenPath returns the path to the operator's auth token file.
func AuthTokenPath(dir string) string {
	return filepath.Join(dir, "operator.authtoken")
}

// LoadOrGenerateOperatorKey tries to load an existing key pair from disk; if not found,
// generates a new one and persists it alongside the box keypair and auth token.
func LoadOrGenerateOperatorKey(path string) (*OperatorKey, error) {
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0700); err != nil {
		return nil, fmt.Errorf("create key directory: %w", err)
	}

	data, err := os.ReadFile(path)
	if err == nil {
		priv, err := LoadPrivateKey(data)
		if err != nil {
			return nil, fmt.Errorf("load private key from %s: %w", path, err)
		}
		pub := priv.GetPublic()
		pid, err := peer.IDFromPublicKey(pub)
		if err != nil {
			return nil, fmt.Errorf("peer id from loaded key: %w", err)
		}

		boxPrivData, boxErr := os.ReadFile(BoxKeyPath(dir))
		var boxKeys *BoxKeyPair
		if boxErr == nil && len(boxPrivData) == 32 {
			boxKeys = LoadBoxKey(boxPrivData)
		}
		if boxKeys == nil {
			boxKeys, boxErr = GenerateBoxKey()
			if boxErr == nil {
				os.WriteFile(BoxKeyPath(dir), boxKeys.Marshal(), 0600)
				os.WriteFile(BoxPubKeyPath(dir), boxKeys.BoxPubKeyBytes(), 0644)
			}
		}

		tokenData, tokenErr := os.ReadFile(AuthTokenPath(dir))
		var authToken []byte
		if tokenErr == nil && len(tokenData) == 32 {
			authToken = tokenData
		} else {
			authToken = make([]byte, 32)
			if _, err := rand.Read(authToken); err != nil {
				return nil, fmt.Errorf("generate auth token: %w", err)
			}
			os.WriteFile(AuthTokenPath(dir), authToken, 0600)
		}

		return &OperatorKey{
			KeyPair:   KeyPair{PrivateKey: priv, PublicKey: pub},
			PeerID:    pid,
			BoxKeys:   boxKeys,
			AuthToken: authToken,
		}, nil
	}

	if !os.IsNotExist(err) {
		return nil, fmt.Errorf("read key file %s: %w", path, err)
	}

	key, err := GenerateOperatorKey()
	if err != nil {
		return nil, err
	}

	marshaled, err := MarshalPrivateKey(key.PrivateKey)
	if err != nil {
		return nil, fmt.Errorf("marshal key: %w", err)
	}

	if err := os.WriteFile(path, marshaled, 0600); err != nil {
		return nil, fmt.Errorf("write key to %s: %w", path, err)
	}
	if err := os.WriteFile(BoxKeyPath(dir), key.BoxKeys.Marshal(), 0600); err != nil {
		return nil, fmt.Errorf("write box key: %w", err)
	}
	if err := os.WriteFile(BoxPubKeyPath(dir), key.BoxKeys.BoxPubKeyBytes(), 0644); err != nil {
		return nil, fmt.Errorf("write box pubkey: %w", err)
	}
	if err := os.WriteFile(AuthTokenPath(dir), key.AuthToken, 0600); err != nil {
		return nil, fmt.Errorf("write auth token: %w", err)
	}

	return key, nil
}

// BoxPubKeyBytes returns the box public key as 32 raw bytes.
func (kp *BoxKeyPair) BoxPubKeyBytes() []byte {
	return kp.PublicKey[:]
}

// EncryptMessage encrypts plaintext using NaCl box anonymous sealed boxes.
func EncryptMessage(plaintext []byte, boxPub *[32]byte) ([]byte, error) {
	if len(plaintext) == 0 {
		return plaintext, nil
	}
	sealed, err := box.SealAnonymous(nil, plaintext, boxPub, rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("seal anonymous: %w", err)
	}
	return sealed, nil
}

// DecryptMessage decrypts a NaCl box anonymous sealed box using the provided keypair.
func DecryptMessage(ciphertext []byte, boxKeys *BoxKeyPair) ([]byte, error) {
	if len(ciphertext) == 0 {
		return ciphertext, nil
	}
	opened, ok := box.OpenAnonymous(nil, ciphertext, boxKeys.PublicKey, boxKeys.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("open sealed box failed")
	}
	return opened, nil
}


