// Package mtls provides the mTLS/CA security foundation for the Pangolin control plane.
// It implements a lightweight self-signed CA, CSR signing, one-time bootstrap tokens,
// CRL revocation, and gRPC identity extraction interceptors.
package mtls

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"math/big"
	"os"
	"path/filepath"
	"time"
)

// CAConfig specifies where to persist the CA key and certificate.
// Both paths must be writable on first run; subsequent runs load from disk.
type CAConfig struct {
	// KeyPath is the file path for the CA private key (PEM-encoded ECDSA P-256).
	// The key never leaves the control-plane disk.
	KeyPath string
	// CertPath is the file path for the CA certificate (PEM-encoded X.509).
	CertPath string
}

// CA is a lightweight self-signed certificate authority.
// It signs node client certificates and provides the CA cert for agent validation.
type CA struct {
	key     *ecdsa.PrivateKey
	cert    *x509.Certificate
	certPEM []byte
}

// NewCA loads an existing CA from disk or generates a new ECDSA P-256 CA.
// Generated key and certificate are persisted to the paths in cfg.
func NewCA(cfg CAConfig) (*CA, error) {
	keyPEM, errKey := os.ReadFile(cfg.KeyPath)
	certPEM, errCert := os.ReadFile(cfg.CertPath)

	if errKey == nil && errCert == nil {
		key, err := parseECPrivateKey(keyPEM)
		if err != nil {
			return nil, fmt.Errorf("mtls: parse CA key: %w", err)
		}
		cert, err := parseCertificate(certPEM)
		if err != nil {
			return nil, fmt.Errorf("mtls: parse CA cert: %w", err)
		}
		return &CA{key: key, cert: cert, certPEM: certPEM}, nil
	}

	// Generate a new CA key pair and self-signed certificate.
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("mtls: generate CA key: %w", err)
	}

	serial, err := randomSerial()
	if err != nil {
		return nil, err
	}

	now := time.Now()
	template := &x509.Certificate{
		SerialNumber: serial,
		Subject: pkix.Name{
			Organization: []string{"Pangolin"},
			CommonName:   "Pangolin Node CA",
		},
		NotBefore:             now.Add(-time.Minute), // slight back-date for clock skew
		NotAfter:              now.Add(10 * 365 * 24 * time.Hour),
		IsCA:                  true,
		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
		BasicConstraintsValid: true,
	}

	certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
	if err != nil {
		return nil, fmt.Errorf("mtls: self-sign CA: %w", err)
	}

	cert, err := x509.ParseCertificate(certDER)
	if err != nil {
		return nil, fmt.Errorf("mtls: parse self-signed CA: %w", err)
	}

	keyDER, err := x509.MarshalECPrivateKey(key)
	if err != nil {
		return nil, fmt.Errorf("mtls: marshal CA key: %w", err)
	}
	keyPEMBytes := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
	certPEMBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})

	// Persist to disk; create parent directories as needed.
	if err := os.MkdirAll(filepath.Dir(cfg.KeyPath), 0o700); err != nil {
		return nil, fmt.Errorf("mtls: mkdir for CA key: %w", err)
	}
	if err := os.MkdirAll(filepath.Dir(cfg.CertPath), 0o755); err != nil {
		return nil, fmt.Errorf("mtls: mkdir for CA cert: %w", err)
	}
	// Key: owner-only read (0600)
	if err := os.WriteFile(cfg.KeyPath, keyPEMBytes, 0o600); err != nil {
		return nil, fmt.Errorf("mtls: write CA key: %w", err)
	}
	if err := os.WriteFile(cfg.CertPath, certPEMBytes, 0o644); err != nil {
		return nil, fmt.Errorf("mtls: write CA cert: %w", err)
	}

	return &CA{key: key, cert: cert, certPEM: certPEMBytes}, nil
}

// SignCSR signs a PEM-encoded CSR and issues a client certificate.
// The certificate's Subject CN is overridden to nodeUUID regardless of what
// the CSR requests. Validity is 90 days; EKU = ClientAuth only.
func (ca *CA) SignCSR(csrPEM []byte, nodeUUID string) ([]byte, error) {
	block, _ := pem.Decode(csrPEM)
	if block == nil {
		return nil, fmt.Errorf("mtls: invalid CSR PEM")
	}
	csr, err := x509.ParseCertificateRequest(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("mtls: parse CSR: %w", err)
	}
	if err := csr.CheckSignature(); err != nil {
		return nil, fmt.Errorf("mtls: CSR signature invalid: %w", err)
	}

	serial, err := randomSerial()
	if err != nil {
		return nil, err
	}

	now := time.Now()
	template := &x509.Certificate{
		SerialNumber: serial,
		Subject: pkix.Name{
			CommonName: nodeUUID,
		},
		NotBefore:   now.Add(-time.Minute),
		NotAfter:    now.Add(90 * 24 * time.Hour),
		KeyUsage:    x509.KeyUsageDigitalSignature,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}

	certDER, err := x509.CreateCertificate(rand.Reader, template, ca.cert, csr.PublicKey, ca.key)
	if err != nil {
		return nil, fmt.Errorf("mtls: sign CSR: %w", err)
	}

	return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}), nil
}

// CAPEM returns the CA certificate in PEM format.
// This is sent to agents during Enroll so they can pin the server's CA.
func (ca *CA) CAPEM() []byte {
	return ca.certPEM
}

// CACert returns the parsed CA certificate for building x509.CertPool entries.
func (ca *CA) CACert() *x509.Certificate {
	return ca.cert
}

// ─── helpers ─────────────────────────────────────────────────────────────────

func randomSerial() (*big.Int, error) {
	serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
	if err != nil {
		return nil, fmt.Errorf("mtls: generate serial: %w", err)
	}
	return serial, nil
}

func parseECPrivateKey(pemData []byte) (*ecdsa.PrivateKey, error) {
	block, _ := pem.Decode(pemData)
	if block == nil {
		return nil, fmt.Errorf("no PEM block found")
	}
	return x509.ParseECPrivateKey(block.Bytes)
}

func parseCertificate(pemData []byte) (*x509.Certificate, error) {
	block, _ := pem.Decode(pemData)
	if block == nil {
		return nil, fmt.Errorf("no PEM block found")
	}
	return x509.ParseCertificate(block.Bytes)
}
