package identity

import (
	"context"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"net/url"
	"strings"
)

var (
	ErrStepCLI          = errors.New("identity: step CLI failed")
	ErrSpiffeURIMissing = errors.New("identity: spiffe URI SAN missing")
)

const (
	SpiffeTrustDomain  = "orca.local"
	SVIDNotAfter       = "24h"
	DefaultProvisioner = "orca-admin"
)

type execer interface {
	Exec(ctx context.Context, peer string, cmd string) ([]byte, error)
}

func SpiffeURI(namespace, sa, allocID string) string {
	return fmt.Sprintf("spiffe://%s/ns/%s/sa/%s/%s", SpiffeTrustDomain, namespace, sa, allocID)
}

func MintSVID(ctx context.Context, transport execer, leadPeer, namespace, sa, allocID string) (certPEM, keyPEM []byte, err error) {
	if transport == nil {
		return nil, nil, errors.New("identity: transport is nil")
	}
	if leadPeer == "" {
		return nil, nil, errors.New("identity: lead peer not set")
	}
	spiffeID := SpiffeURI(namespace, sa, allocID)
	certOut := "/etc/orca/step-tmp/orca-svid-" + sanitize(spiffeID) + ".crt"
	keyOut := "/etc/orca/step-tmp/orca-svid-" + sanitize(spiffeID) + ".key"
	var sb strings.Builder
	sb.WriteString("step ca certificate ")
	sb.WriteString(shellQuote(spiffeID))
	sb.WriteString(" ")
	sb.WriteString(shellQuote(certOut))
	sb.WriteString(" ")
	sb.WriteString(shellQuote(keyOut))
	sb.WriteString(" --san ")
	sb.WriteString(shellQuote(spiffeID))
	sb.WriteString(" --not-after ")
	sb.WriteString(shellQuote(SVIDNotAfter))
	sb.WriteString(" --provisioner ")
	sb.WriteString(shellQuote("orca-oidc"))
	sb.WriteString(" --force")
	cmd := sb.String()
	if _, err := transport.Exec(ctx, leadPeer, cmd); err != nil {
		return nil, nil, fmt.Errorf("identity: mint %s: %w", spiffeID, err)
	}
	certOut2, err := transport.Exec(ctx, leadPeer, fmt.Sprintf("cat %s", shellQuote(certOut)))
	if err != nil {
		return nil, nil, fmt.Errorf("identity: read cert: %w", err)
	}
	if len(certOut2) == 0 {
		return nil, nil, fmt.Errorf("identity: empty cert at %s: %w", certOut, ErrStepCLI)
	}
	keyOut2, err := transport.Exec(ctx, leadPeer, fmt.Sprintf("cat %s", shellQuote(keyOut)))
	if err != nil {
		return nil, nil, fmt.Errorf("identity: read key: %w", err)
	}
	if len(keyOut2) == 0 {
		return nil, nil, fmt.Errorf("identity: empty key at %s: %w", keyOut, ErrStepCLI)
	}
	_, _ = transport.Exec(ctx, leadPeer, fmt.Sprintf("rm -f %s %s", shellQuote(certOut), shellQuote(keyOut)))
	if verr := VerifySVID(certOut2, spiffeID); verr != nil {
		return nil, nil, verr
	}
	return certOut2, keyOut2, nil
}

func VerifySVID(certPEM []byte, spiffeID string) error {
	block, _ := pem.Decode(certPEM)
	if block == nil {
		return fmt.Errorf("identity: parse cert: PEM decode failed: %w", ErrStepCLI)
	}
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return fmt.Errorf("identity: parse cert: %w", err)
	}
	want, err := url.Parse(spiffeID)
	if err != nil {
		return fmt.Errorf("identity: parse spiffe id: %w", err)
	}
	for _, u := range cert.URIs {
		if u.String() == want.String() {
			return nil
		}
	}
	return fmt.Errorf("identity: cert missing %q: %w", spiffeID, ErrSpiffeURIMissing)
}

// VerifySVIDWithChain validates the SVID cert chain against the CA
// pool AND checks the SPIFFE URI SAN (REQ-126, F9). The CA pool is the
// cluster root CA (or the step-ca root). Rejects certs signed by
// unknown CAs even with a correct URI. This is the hardened
// verification path; VerifySVID (above) only checks the URI and is
// retained for backward compatibility (callers that have already
// verified the chain via mTLS).
func VerifySVIDWithChain(certPEM []byte, spiffeID string, caPool *x509.CertPool) error {
	if caPool == nil {
		return fmt.Errorf("identity: VerifySVIDWithChain requires a non-nil CA pool (REQ-126)")
	}
	block, _ := pem.Decode(certPEM)
	if block == nil {
		return fmt.Errorf("identity: parse cert: PEM decode failed: %w", ErrStepCLI)
	}
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return fmt.Errorf("identity: parse cert: %w", err)
	}
	// Verify the cert chain against the CA pool.
	if _, err := cert.Verify(x509.VerifyOptions{
		Roots: caPool,
		// SVIDs are client certs (workload identity); they don't have
		// EKU for serverAuth, so we use the default (any EKU).
		KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
	}); err != nil {
		return fmt.Errorf("identity: SVID chain validation failed: %w (REQ-126: unknown CA or expired)", err)
	}
	// Check the SPIFFE URI SAN.
	want, err := url.Parse(spiffeID)
	if err != nil {
		return fmt.Errorf("identity: parse spiffe id: %w", err)
	}
	for _, u := range cert.URIs {
		if u.String() == want.String() {
			return nil
		}
	}
	return fmt.Errorf("identity: cert missing %q: %w", spiffeID, ErrSpiffeURIMissing)
}

func SpiffeIDFromCert(cert *x509.Certificate) string {
	for _, u := range cert.URIs {
		if u.Scheme == "spiffe" {
			return u.String()
		}
	}
	return ""
}

func SubjectFromSpiffe(spiffeID string) (namespace, sa, allocID string, err error) {
	u, err := url.Parse(spiffeID)
	if err != nil {
		return "", "", "", fmt.Errorf("identity: parse spiffe id: %w", err)
	}
	if u.Scheme != "spiffe" {
		return "", "", "", fmt.Errorf("identity: not a spiffe URI: %q", spiffeID)
	}
	if u.Host != SpiffeTrustDomain {
		return "", "", "", fmt.Errorf("identity: wrong trust domain %q, want %q", u.Host, SpiffeTrustDomain)
	}
	parts := strings.Split(strings.TrimPrefix(u.Path, "/"), "/")
	if len(parts) != 5 || parts[0] != "ns" || parts[2] != "sa" {
		return "", "", "", fmt.Errorf("identity: malformed spiffe path %q", u.Path)
	}
	return parts[1], parts[3], parts[4], nil
}

func sanitize(s string) string {
	r := strings.NewReplacer("://", "-", "/", "_", ":", "_", " ", "_")
	return r.Replace(s)
}

func shellQuote(s string) string {
	return "'" + strings.ReplaceAll(s, "'", "'\\''") + "'"
}
