package dedicatedapi

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

	"github.com/tidbcloud/controlplane9/internal/host"
)

// ComponentNames are the runtime identities allowed to access the Dedicated
// API. Each receives a distinct client certificate and kubeconfig.
var ComponentNames = []string{
	"controller-manager",
	"regional-server",
	"cluster-control-server",
	"cluster-controller",
	"webhook-server",
	"o11y-agent-control-server",
	"ticdc-service",
	"classic-provider",
	"readiness",
	"cleanup",
}

const (
	apiCAName  = "cp9 Dedicated API client CA"
	etcdCAName = "cp9 Dedicated etcd CA"
)

// ComponentKubeconfigPath returns the durable path for one client identity.
func ComponentKubeconfigPath(state *host.DedicatedAPISession, name string) string {
	if state == nil {
		return ""
	}
	if name == "admin" {
		return state.AdminKubeconfigPath
	}
	return filepath.Join(filepath.Dir(state.PKIDir), "kubeconfigs", name+".kubeconfig")
}

// MaterializePKI creates the Dedicated API's private identity exactly once.
// A complete existing identity is validated and reused; partial state fails
// closed so resume never silently rotates the API out from under etcd data.
func MaterializePKI(selected *host.Session, now time.Time) error {
	if selected == nil || selected.DedicatedAPI == nil {
		return fmt.Errorf("materialize Dedicated API PKI: prepared session state is required")
	}
	state := selected.DedicatedAPI
	if err := validate(state); err != nil {
		return fmt.Errorf("materialize Dedicated API PKI: %w", err)
	}
	root := filepath.Dir(state.PKIDir)
	if _, err := os.Stat(root); err == nil {
		return validateExistingPKI(state)
	} else if !os.IsNotExist(err) {
		return fmt.Errorf("materialize Dedicated API PKI: stat %s: %w", root, err)
	}

	parent := filepath.Dir(root)
	if err := os.MkdirAll(parent, 0o700); err != nil {
		return fmt.Errorf("materialize Dedicated API PKI: create parent: %w", err)
	}
	tmp, err := os.MkdirTemp(parent, ".dedicated-api-pki-")
	if err != nil {
		return fmt.Errorf("materialize Dedicated API PKI: create staging directory: %w", err)
	}
	defer os.RemoveAll(tmp)
	if err := os.Chmod(tmp, 0o700); err != nil {
		return fmt.Errorf("materialize Dedicated API PKI: secure staging directory: %w", err)
	}

	apiCA, apiCAKey, apiCAPEM, apiCAKeyPEM, err := newCA(apiCAName, now)
	if err != nil {
		return err
	}
	etcdCA, etcdCAKey, etcdCAPEM, etcdCAKeyPEM, err := newCA(etcdCAName, now)
	if err != nil {
		return err
	}
	pkiDir := filepath.Join(tmp, "pki")
	if err := os.MkdirAll(pkiDir, 0o700); err != nil {
		return fmt.Errorf("materialize Dedicated API PKI: create pki dir: %w", err)
	}
	for name, data := range map[string][]byte{
		"api-ca.crt":  apiCAPEM,
		"api-ca.key":  apiCAKeyPEM,
		"etcd-ca.crt": etcdCAPEM,
		"etcd-ca.key": etcdCAKeyPEM,
	} {
		if err := writePrivate(filepath.Join(pkiDir, name), data); err != nil {
			return err
		}
	}

	apiServerCert, apiServerKey, err := newSignedCertificate(apiCA, apiCAKey, &certificateRequest{
		CommonName: "kube-apiserver",
		DNSNames: []string{
			"dedicated-kube-apiserver",
			"dedicated-kube-apiserver.cp9-system",
			"dedicated-kube-apiserver.cp9-system.svc",
		},
		IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
		ServerAuth:  true,
	}, now)
	if err != nil {
		return err
	}
	etcdServerCert, etcdServerKey, err := newSignedCertificate(etcdCA, etcdCAKey, &certificateRequest{
		CommonName: "dedicated-etcd",
		DNSNames: []string{
			"dedicated-etcd",
			"dedicated-etcd.cp9-system",
			"dedicated-etcd.cp9-system.svc",
		},
		IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
		ServerAuth:  true,
	}, now)
	if err != nil {
		return err
	}
	etcdClientCert, etcdClientKey, err := newSignedCertificate(etcdCA, etcdCAKey, &certificateRequest{
		CommonName: "kube-apiserver-etcd-client",
		ClientAuth: true,
	}, now)
	if err != nil {
		return err
	}
	for name, data := range map[string][]byte{
		"apiserver.crt":   apiServerCert,
		"apiserver.key":   apiServerKey,
		"etcd-server.crt": etcdServerCert,
		"etcd-server.key": etcdServerKey,
		"etcd-client.crt": etcdClientCert,
		"etcd-client.key": etcdClientKey,
	} {
		if err := writePrivate(filepath.Join(pkiDir, name), data); err != nil {
			return err
		}
	}

	serviceAccountKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return fmt.Errorf("generate service-account key: %w", err)
	}
	serviceAccountPrivate := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(serviceAccountKey)})
	serviceAccountPublic, err := x509.MarshalPKIXPublicKey(&serviceAccountKey.PublicKey)
	if err != nil {
		return fmt.Errorf("marshal service-account public key: %w", err)
	}
	if err := writePrivate(filepath.Join(pkiDir, "service-account.key"), serviceAccountPrivate); err != nil {
		return err
	}
	if err := writePrivate(
		filepath.Join(pkiDir, "service-account.pub"),
		pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: serviceAccountPublic}),
	); err != nil {
		return err
	}

	kubeconfigDir := filepath.Join(tmp, "kubeconfigs")
	if err := os.MkdirAll(kubeconfigDir, 0o700); err != nil {
		return fmt.Errorf("materialize Dedicated API PKI: create kubeconfig dir: %w", err)
	}
	clients := append([]string{"admin"}, ComponentNames...)
	for _, name := range clients {
		commonName := "cp9:dedicated:" + name
		organizations := []string{"cp9:dedicated:" + name}
		switch name {
		case "admin":
			organizations = []string{"system:masters"}
		case "controller-manager":
			commonName = "system:kube-controller-manager"
			organizations = []string{"system:kube-controller-manager"}
		}
		certPEM, keyPEM, err := newSignedCertificate(apiCA, apiCAKey, &certificateRequest{
			CommonName:    commonName,
			Organizations: organizations,
			ClientAuth:    true,
		}, now)
		if err != nil {
			return err
		}
		if err := writePrivate(filepath.Join(pkiDir, name+".crt"), certPEM); err != nil {
			return err
		}
		if err := writePrivate(filepath.Join(pkiDir, name+".key"), keyPEM); err != nil {
			return err
		}
		endpoint := state.InternalEndpoint
		path := filepath.Join(kubeconfigDir, name+".kubeconfig")
		if name == "admin" {
			endpoint = state.LocalEndpoint
			path = filepath.Join(tmp, "admin.kubeconfig")
		}
		if err := writePrivate(path, renderKubeconfig(endpoint, commonName, apiCAPEM, certPEM, keyPEM)); err != nil {
			return err
		}
	}
	internalKubeconfig, err := os.ReadFile(filepath.Join(kubeconfigDir, "readiness.kubeconfig"))
	if err != nil {
		return fmt.Errorf("read generated internal kubeconfig: %w", err)
	}
	if err := writePrivate(filepath.Join(tmp, "internal.kubeconfig"), internalKubeconfig); err != nil {
		return err
	}

	if err := os.Rename(tmp, root); err != nil {
		return fmt.Errorf("materialize Dedicated API PKI: publish identity: %w", err)
	}
	state.CAFingerprint = certificateFingerprint(apiCA)
	return nil
}

type certificateRequest struct {
	CommonName    string
	Organizations []string
	DNSNames      []string
	IPAddresses   []net.IP
	ServerAuth    bool
	ClientAuth    bool
}

func newCA(commonName string, now time.Time) (
	certificate *x509.Certificate,
	privateKey *ecdsa.PrivateKey,
	certificatePEM []byte,
	privateKeyPEM []byte,
	err error,
) {
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("generate %s key: %w", commonName, err)
	}
	serial, err := randomSerial()
	if err != nil {
		return nil, nil, nil, nil, err
	}
	template := &x509.Certificate{
		SerialNumber:          serial,
		NotBefore:             now.Add(-5 * time.Minute),
		NotAfter:              now.Add(365 * 24 * time.Hour),
		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
		BasicConstraintsValid: true,
		IsCA:                  true,
	}
	template.Subject.CommonName = commonName
	der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("create %s certificate: %w", commonName, err)
	}
	parsed, err := x509.ParseCertificate(der)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("parse generated %s certificate: %w", commonName, err)
	}
	keyDER, err := x509.MarshalPKCS8PrivateKey(key)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("marshal %s key: %w", commonName, err)
	}
	return parsed, key,
		pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}),
		pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), nil
}

func newSignedCertificate(
	ca *x509.Certificate,
	caKey *ecdsa.PrivateKey,
	request *certificateRequest,
	now time.Time,
) (certificatePEM, privateKeyPEM []byte, err error) {
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, nil, fmt.Errorf("generate %s key: %w", request.CommonName, err)
	}
	serial, err := randomSerial()
	if err != nil {
		return nil, nil, err
	}
	usage := []x509.ExtKeyUsage{}
	if request.ServerAuth {
		usage = append(usage, x509.ExtKeyUsageServerAuth)
	}
	if request.ClientAuth {
		usage = append(usage, x509.ExtKeyUsageClientAuth)
	}
	template := &x509.Certificate{
		SerialNumber:          serial,
		NotBefore:             now.Add(-5 * time.Minute),
		NotAfter:              now.Add(30 * 24 * time.Hour),
		DNSNames:              append([]string(nil), request.DNSNames...),
		IPAddresses:           append([]net.IP(nil), request.IPAddresses...),
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:           usage,
		BasicConstraintsValid: true,
	}
	template.Subject.CommonName = request.CommonName
	template.Subject.Organization = append([]string(nil), request.Organizations...)
	der, err := x509.CreateCertificate(rand.Reader, template, ca, &key.PublicKey, caKey)
	if err != nil {
		return nil, nil, fmt.Errorf("create %s certificate: %w", request.CommonName, err)
	}
	keyDER, err := x509.MarshalPKCS8PrivateKey(key)
	if err != nil {
		return nil, nil, fmt.Errorf("marshal %s key: %w", request.CommonName, err)
	}
	return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}),
		pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), nil
}

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("generate certificate serial: %w", err)
	}
	return serial, nil
}

func renderKubeconfig(endpoint, username string, ca, cert, key []byte) []byte {
	return fmt.Appendf(nil, `apiVersion: v1
kind: Config
clusters:
- name: cp9-dedicated
  cluster:
    server: %s
    certificate-authority-data: %s
users:
- name: %s
  user:
    client-certificate-data: %s
    client-key-data: %s
contexts:
- name: cp9-dedicated
  context:
    cluster: cp9-dedicated
    user: %s
current-context: cp9-dedicated
`, endpoint, base64.StdEncoding.EncodeToString(ca), username,
		base64.StdEncoding.EncodeToString(cert), base64.StdEncoding.EncodeToString(key), username)
}

func validateExistingPKI(state *host.DedicatedAPISession) error {
	required := []string{
		filepath.Join(state.PKIDir, "api-ca.crt"),
		filepath.Join(state.PKIDir, "api-ca.key"),
		filepath.Join(state.PKIDir, "apiserver.crt"),
		filepath.Join(state.PKIDir, "apiserver.key"),
		filepath.Join(state.PKIDir, "etcd-ca.crt"),
		filepath.Join(state.PKIDir, "etcd-ca.key"),
		filepath.Join(state.PKIDir, "etcd-server.crt"),
		filepath.Join(state.PKIDir, "etcd-server.key"),
		filepath.Join(state.PKIDir, "etcd-client.crt"),
		filepath.Join(state.PKIDir, "etcd-client.key"),
		filepath.Join(state.PKIDir, "service-account.key"),
		filepath.Join(state.PKIDir, "service-account.pub"),
		state.AdminKubeconfigPath,
		state.InternalKubeconfigPath,
	}
	for _, name := range ComponentNames {
		required = append(required,
			filepath.Join(state.PKIDir, name+".crt"),
			filepath.Join(state.PKIDir, name+".key"),
			ComponentKubeconfigPath(state, name),
		)
	}
	for _, path := range required {
		info, err := os.Stat(path)
		if err != nil {
			return fmt.Errorf("dedicated API identity is incomplete at %s: %w; recreate the session", path, err)
		}
		if info.Mode().Perm() != 0o600 {
			return fmt.Errorf("dedicated API identity file %s has mode %#o, want 0600", path, info.Mode().Perm())
		}
	}
	cert, err := parseCertificateFile(filepath.Join(state.PKIDir, "api-ca.crt"))
	if err != nil {
		return err
	}
	fingerprint := certificateFingerprint(cert)
	if state.CAFingerprint != "" && state.CAFingerprint != fingerprint {
		return fmt.Errorf("dedicated API CA fingerprint changed: persisted %s, found %s", state.CAFingerprint, fingerprint)
	}
	state.CAFingerprint = fingerprint
	return nil
}

func parseCertificateFile(path string) (*x509.Certificate, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read certificate %s: %w", path, err)
	}
	block, _ := pem.Decode(data)
	if block == nil {
		return nil, fmt.Errorf("decode certificate %s", path)
	}
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("parse certificate %s: %w", path, err)
	}
	return cert, nil
}

func certificateFingerprint(cert *x509.Certificate) string {
	digest := sha256.Sum256(cert.Raw)
	return hex.EncodeToString(digest[:])
}

func writePrivate(path string, data []byte) error {
	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		return fmt.Errorf("create private directory for %s: %w", path, err)
	}
	// #nosec G703 -- every caller constructs this path beneath a session-owned
	// state directory; the helper deliberately accepts the resolved path so the
	// atomic staging tree and persisted resume tree share one writer.
	if err := os.WriteFile(path, data, 0o600); err != nil {
		return fmt.Errorf("write private file %s: %w", path, err)
	}
	if err := os.Chmod(path, 0o600); err != nil {
		return fmt.Errorf("secure private file %s: %w", path, err)
	}
	return nil
}
