package localapi

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

// certValidity is the self-signed leaf lifetime. It is long because the leaf's SHA-256
// fingerprint is baked into every guest's bootstrap for pinning — rotating the cert means
// re-issuing bootstraps, so this is deliberately decoupled from short-lived TLS norms. Cert
// rotation is an operator action (slice 10+), not an automatic expiry event.
const certValidity = 10 * 365 * 24 * time.Hour

// EnsureLeaf loads the agent's local-API leaf from certPath/keyPath, generating and persisting
// a fresh self-signed ECDSA-P256 leaf (SAN = bridgeHost, when it is an IP/host) if either file
// is absent. It returns the tls.Certificate to serve and the leaf's SHA-256 fingerprint (the
// agent's pin convention: lowercase hex of the leaf DER) for baking into bootstraps.
//
// Persisting the generated pair keeps the fingerprint STABLE across agent restarts — a fresh
// cert each boot would silently invalidate every already-issued bootstrap's pin.
func EnsureLeaf(certPath, keyPath, bridgeHost string) (tls.Certificate, string, error) {
	if fileExists(certPath) && fileExists(keyPath) {
		cert, err := tls.LoadX509KeyPair(certPath, keyPath)
		if err != nil {
			return tls.Certificate{}, "", fmt.Errorf("localapi: load leaf %s: %w", certPath, err)
		}
		fp, err := leafFingerprint(cert)
		if err != nil {
			return tls.Certificate{}, "", err
		}
		return cert, fp, nil
	}
	return generateLeaf(certPath, keyPath, bridgeHost)
}

// generateLeaf creates a self-signed ECDSA-P256 leaf, writes the cert (0644) + key (0600) to
// disk, and returns the loaded pair + its fingerprint.
func generateLeaf(certPath, keyPath, bridgeHost string) (tls.Certificate, string, error) {
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return tls.Certificate{}, "", fmt.Errorf("localapi: gen key: %w", err)
	}
	serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
	if err != nil {
		return tls.Certificate{}, "", fmt.Errorf("localapi: gen serial: %w", err)
	}
	notBefore := time.Now().Add(-1 * time.Hour) // small backdate for clock skew
	tmpl := &x509.Certificate{
		SerialNumber:          serial,
		Subject:               pkix.Name{CommonName: "felhom-agent local-api"},
		NotBefore:             notBefore,
		NotAfter:              notBefore.Add(certValidity),
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
	}
	// SAN: the bridge host (an IP in practice). The controller pins the leaf SHA-256, so the
	// SAN is not load-bearing for trust, but a correct SAN keeps standard tooling happy.
	if host := bridgeHost; host != "" {
		if ip := net.ParseIP(host); ip != nil {
			tmpl.IPAddresses = append(tmpl.IPAddresses, ip)
		} else {
			tmpl.DNSNames = append(tmpl.DNSNames, host)
		}
	}
	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
	if err != nil {
		return tls.Certificate{}, "", fmt.Errorf("localapi: create cert: %w", err)
	}
	keyDER, err := x509.MarshalECPrivateKey(key)
	if err != nil {
		return tls.Certificate{}, "", fmt.Errorf("localapi: marshal key: %w", err)
	}
	if err := os.MkdirAll(filepath.Dir(certPath), 0o700); err != nil {
		return tls.Certificate{}, "", fmt.Errorf("localapi: cert dir: %w", err)
	}
	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
	if err := os.WriteFile(certPath, certPEM, 0o644); err != nil {
		return tls.Certificate{}, "", fmt.Errorf("localapi: write cert: %w", err)
	}
	if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil {
		return tls.Certificate{}, "", fmt.Errorf("localapi: write key: %w", err)
	}
	syncDir(filepath.Dir(certPath))
	cert, err := tls.X509KeyPair(certPEM, keyPEM)
	if err != nil {
		return tls.Certificate{}, "", fmt.Errorf("localapi: reload generated leaf: %w", err)
	}
	fp, err := leafFingerprint(cert)
	if err != nil {
		return tls.Certificate{}, "", err
	}
	return cert, fp, nil
}

// leafFingerprint returns the lowercase-hex SHA-256 of the leaf certificate DER — the same pin
// convention the agent uses for the Proxmox/PBS host certs.
func leafFingerprint(cert tls.Certificate) (string, error) {
	if len(cert.Certificate) == 0 {
		return "", fmt.Errorf("localapi: certificate has no leaf")
	}
	sum := sha256.Sum256(cert.Certificate[0]) // Certificate[0] is the leaf DER
	return hex.EncodeToString(sum[:]), nil
}

func fileExists(p string) bool {
	if p == "" {
		return false
	}
	_, err := os.Stat(p)
	return err == nil
}
