package identity

import (
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"math/big"
	"net/url"
	"strings"
	"testing"
	"time"
)

type mockExec struct {
	responses []mockResp
	calls     []string
}

type mockResp struct {
	match string
	out   []byte
	err   error
}

func (m *mockExec) Exec(ctx context.Context, peer string, cmd string) ([]byte, error) {
	m.calls = append(m.calls, cmd)
	for _, r := range m.responses {
		if r.match == "" || strings.Contains(cmd, r.match) {
			return r.out, r.err
		}
	}
	return nil, nil
}

func containsCall(t *testing.T, mx *mockExec, want string) {
	t.Helper()
	for _, c := range mx.calls {
		if strings.Contains(c, want) {
			return
		}
	}
	t.Errorf("no exec call contained %q; calls were:\n%s", want, strings.Join(mx.calls, "\n"))
}

// mintTestSVIDCert builds a self-signed cert carrying the SPIFFE URI
// SAN spiffeID and returns its PEM encoding. The private key is
// discarded (only the cert is needed for parsing tests).
func mintTestSVIDCert(t *testing.T, spiffeID string) []byte {
	t.Helper()
	uri, err := url.Parse(spiffeID)
	if err != nil {
		t.Fatalf("parse spiffe id: %v", err)
	}
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("generate key: %v", err)
	}
	tmpl := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject:      pkix.Name{CommonName: spiffeID},
		URIs:         []*url.URL{uri},
		NotBefore:    time.Now().Add(-time.Minute),
		NotAfter:     time.Now().Add(24 * time.Hour),
		KeyUsage:     x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
	}
	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
	if err != nil {
		t.Fatalf("create cert: %v", err)
	}
	return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
}

func TestSpiffeURI(t *testing.T) {
	cases := []struct {
		ns, sa, alloc, want string
	}{
		{"_defaults", "web", "abc123", "spiffe://orca.local/ns/_defaults/sa/web/abc123"},
		{"prod", "api", "0", "spiffe://orca.local/ns/prod/sa/api/0"},
	}
	for _, tc := range cases {
		got := SpiffeURI(tc.ns, tc.sa, tc.alloc)
		if got != tc.want {
			t.Errorf("SpiffeURI(%q,%q,%q) = %q, want %q", tc.ns, tc.sa, tc.alloc, got, tc.want)
		}
	}
}

func TestSubjectFromSpiffe(t *testing.T) {
	ns, sa, alloc, err := SubjectFromSpiffe("spiffe://orca.local/ns/prod/sa/api/0")
	if err != nil {
		t.Fatalf("SubjectFromSpiffe: %v", err)
	}
	if ns != "prod" || sa != "api" || alloc != "0" {
		t.Errorf("got ns=%q sa=%q alloc=%q", ns, sa, alloc)
	}
}

func TestSubjectFromSpiffe_Malformed(t *testing.T) {
	cases := []string{
		"https://orca.local/ns/prod/sa/api/0",
		"spiffe://other/ns/prod/sa/api/0",
		"spiffe://orca.local/ns/prod/api/0",
	}
	for _, c := range cases {
		if _, _, _, err := SubjectFromSpiffe(c); err == nil {
			t.Errorf("SubjectFromSpiffe(%q): expected error, got nil", c)
		}
	}
}

func TestVerifySVID_Present(t *testing.T) {
	spiffeID := "spiffe://orca.local/ns/_defaults/sa/web/abc123"
	certPEM := mintTestSVIDCert(t, spiffeID)
	if err := VerifySVID(certPEM, spiffeID); err != nil {
		t.Errorf("VerifySVID: %v", err)
	}
}

func TestVerifySVID_Missing(t *testing.T) {
	certPEM := mintTestSVIDCert(t, "spiffe://orca.local/ns/_defaults/sa/web/abc123")
	if err := VerifySVID(certPEM, "spiffe://orca.local/ns/prod/sa/api/0"); !errors.Is(err, ErrSpiffeURIMissing) {
		t.Errorf("VerifySVID wrong id: err = %v, want ErrSpiffeURIMissing", err)
	}
}

func TestVerifySVID_BadPEM(t *testing.T) {
	if err := VerifySVID([]byte("not-a-pem"), "spiffe://orca.local/x"); !errors.Is(err, ErrStepCLI) {
		t.Errorf("VerifySVID bad pem: err = %v, want ErrStepCLI", err)
	}
}

func TestSpiffeIDFromCert(t *testing.T) {
	spiffeID := "spiffe://orca.local/ns/_defaults/sa/web/abc123"
	certPEM := mintTestSVIDCert(t, spiffeID)
	block, _ := pem.Decode(certPEM)
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		t.Fatalf("parse cert: %v", err)
	}
	got := SpiffeIDFromCert(cert)
	if got != spiffeID {
		t.Errorf("SpiffeIDFromCert = %q, want %q", got, spiffeID)
	}
}

func TestMintSVID_Success(t *testing.T) {
	spiffeID := "spiffe://orca.local/ns/_defaults/sa/web/abc123"
	certPEM := mintTestSVIDCert(t, spiffeID)
	keyPEM := []byte("-----BEGIN PRIVATE KEY-----\nFAKE\n-----END PRIVATE KEY-----\n")
	mx := &mockExec{responses: []mockResp{
		{match: "step ca certificate", out: nil, err: nil},
		{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: certPEM, err: nil},
		{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: keyPEM, err: nil},
		{match: "rm -f", out: nil, err: nil},
	}}
	gotCert, gotKey, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123")
	if err != nil {
		t.Fatalf("MintSVID: %v", err)
	}
	if string(gotCert) != string(certPEM) {
		t.Error("cert PEM mismatch")
	}
	if string(gotKey) != string(keyPEM) {
		t.Error("key PEM mismatch")
	}
	containsCall(t, mx, "step ca certificate")
	containsCall(t, mx, "--san 'spiffe://orca.local/ns/_defaults/sa/web/abc123'")
	containsCall(t, mx, "--not-after '24h'")
	containsCall(t, mx, "--provisioner 'orca-oidc'")
}

func TestMintSVID_StepFails(t *testing.T) {
	mx := &mockExec{responses: []mockResp{
		{match: "step ca certificate", out: nil, err: errors.New("step: exit 1")},
	}}
	_, _, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123")
	if err == nil || !strings.Contains(err.Error(), "identity: mint") {
		t.Errorf("err = %v, want wrapped 'identity: mint'", err)
	}
}

func TestMintSVID_NilTransport(t *testing.T) {
	_, _, err := MintSVID(context.Background(), nil, "lead:22", "_defaults", "web", "abc123")
	if err == nil || !strings.Contains(err.Error(), "transport is nil") {
		t.Errorf("err = %v, want 'transport is nil'", err)
	}
}

func TestMintSVID_EmptyLead(t *testing.T) {
	mx := &mockExec{}
	_, _, err := MintSVID(context.Background(), mx, "", "_defaults", "web", "abc123")
	if err == nil || !strings.Contains(err.Error(), "lead peer not set") {
		t.Errorf("err = %v, want 'lead peer not set'", err)
	}
}

func TestMintSVID_EmptyCert(t *testing.T) {
	mx := &mockExec{responses: []mockResp{
		{match: "step ca certificate", out: nil, err: nil},
		{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: nil, err: nil},
		{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: []byte("KEY"), err: nil},
		{match: "rm -f", out: nil, err: nil},
	}}
	_, _, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123")
	if err == nil || !errors.Is(err, ErrStepCLI) {
		t.Errorf("err = %v, want ErrStepCLI", err)
	}
}

func TestMintSVID_URISANMissing(t *testing.T) {
	wrongCert := mintTestSVIDCert(t, "spiffe://orca.local/ns/other/sa/api/0")
	mx := &mockExec{responses: []mockResp{
		{match: "step ca certificate", out: nil, err: nil},
		{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: wrongCert, err: nil},
		{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: []byte("KEY"), err: nil},
		{match: "rm -f", out: nil, err: nil},
	}}
	_, _, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123")
	if err == nil || !errors.Is(err, ErrSpiffeURIMissing) {
		t.Errorf("err = %v, want ErrSpiffeURIMissing", err)
	}
}

func TestShellQuote(t *testing.T) {
	if got := shellQuote("a'b"); got != "'a'\\''b'" {
		t.Errorf("shellQuote = %q", got)
	}
}

func TestSanitize(t *testing.T) {
	got := sanitize("spiffe://orca.local/ns/_defaults/sa/web/abc123")
	want := "spiffe-orca.local_ns__defaults_sa_web_abc123"
	if got != want {
		t.Errorf("sanitize = %q, want %q", got, want)
	}
}

// --- REQ-126 / F9 SVID chain validation tests ---

// TestVerifySVIDWithChain_RejectsUnknownCA verifies a cert from a
// wrong CA is rejected.
func TestVerifySVIDWithChain_RejectsUnknownCA(t *testing.T) {
	// Generate a cert signed by a different CA (not the pool's CA).
	certPEM := mintTestSVIDCert(t, "spiffe://orca.local/ns/test/sa/web/alloc-1")
	// Empty CA pool (no trusted roots).
	emptyPool := x509.NewCertPool()
	err := VerifySVIDWithChain(certPEM, "spiffe://orca.local/ns/test/sa/web/alloc-1", emptyPool)
	if err == nil {
		t.Error("VerifySVIDWithChain should reject cert from unknown CA (REQ-126)")
	}
}

// TestVerifySVIDWithChain_NilPoolRejected verifies nil CA pool errors.
func TestVerifySVIDWithChain_NilPoolRejected(t *testing.T) {
	certPEM := mintTestSVIDCert(t, "spiffe://orca.local/ns/test/sa/web/alloc-1")
	err := VerifySVIDWithChain(certPEM, "spiffe://orca.local/ns/test/sa/web/alloc-1", nil)
	if err == nil {
		t.Error("nil CA pool should error (REQ-126)")
	}
}
