//go:build qa

// Package integration_test provides the certctl V2.1 Release QA suite.
//
// This file automates every scriptable test from docs/testing-guide.md against
// a running Docker Compose demo stack. Tests that require a browser, external
// service (Vault, DigiCert, Sectigo, Google CAS), Windows, or Kubernetes are
// skipped with a reason.
//
// Run:
//
//	cd deploy && docker compose -f docker-compose.yml -f docker-compose.demo.yml up --build -d
//	# Wait for healthy state (~15s)
//	cd deploy/test && go test -tags qa -v -timeout 10m ./...
//
// Run a single Part:
//
//	go test -tags qa -v -run TestQA/Part14 ./...
//
// Environment overrides:
//
//	CERTCTL_QA_SERVER_URL     (default: https://localhost:8443)
//	CERTCTL_QA_API_KEY        (default: change-me-in-production)
//	CERTCTL_QA_DB_URL         (default: postgres://certctl:certctl@localhost:5432/certctl?sslmode=disable)
//	CERTCTL_QA_REPO_DIR       (default: ../.. — the certctl repo root)
//	CERTCTL_QA_CA_BUNDLE      (default: ./certs/ca.crt — the demo stack's init container writes here)
//	CERTCTL_QA_INSECURE       (default: false — set to "true" to skip TLS verify, e.g. before the init container finishes)
//
// TLS note (HTTPS-Everywhere M-007, Phase 6): the demo compose stack now
// listens on https://localhost:8443 with a self-signed cert written by the
// tls-init container. This suite pins the issuing CA via
// CERTCTL_QA_CA_BUNDLE so cert rotation or a tampered proxy fails the
// handshake instead of being silently trusted. CERTCTL_QA_INSECURE="true"
// is an explicit opt-out for bootstrap scenarios — there is no silent
// plaintext downgrade, matching the server-side pre-flight guard added in
// Phase 5 (task #203).
package integration_test

import (
	"crypto/tls"
	"crypto/x509"
	"database/sql"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

// ---------------------------------------------------------------------------
// QA Configuration
// ---------------------------------------------------------------------------

func qaEnv(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}

var (
	qaServerURL    = qaEnv("CERTCTL_QA_SERVER_URL", "https://localhost:8443")
	qaAPIKey       = qaEnv("CERTCTL_QA_API_KEY", "change-me-in-production")
	qaDBURL        = qaEnv("CERTCTL_QA_DB_URL", "postgres://certctl:certctl@localhost:5432/certctl?sslmode=disable")
	qaRepoDir      = qaEnv("CERTCTL_QA_REPO_DIR", filepath.Join("..", ".."))
	qaCABundlePath = qaEnv("CERTCTL_QA_CA_BUNDLE", "./certs/ca.crt")
	qaInsecure     = strings.EqualFold(os.Getenv("CERTCTL_QA_INSECURE"), "true")
)

// ---------------------------------------------------------------------------
// QA HTTP client
// ---------------------------------------------------------------------------

type qaClient struct {
	http    *http.Client
	baseURL string
	apiKey  string
}

// buildQATLSConfig returns the *tls.Config used by every qaClient. TLS 1.3
// minimum matches the server-side config pinned in Phase 2 (cmd/server).
// When CERTCTL_QA_INSECURE=true we skip verification entirely — useful
// when running against a compose stack where the tls-init container hasn't
// written ca.crt yet, or when pointing at a dev server with a rotated cert.
// Otherwise we pin CERTCTL_QA_CA_BUNDLE and panic on read/parse failure
// rather than silently downgrading to the system trust store (which would
// mask a missing init container).
func buildQATLSConfig() *tls.Config {
	cfg := &tls.Config{MinVersion: tls.VersionTLS13}
	if qaInsecure {
		cfg.InsecureSkipVerify = true
		return cfg
	}
	pem, err := os.ReadFile(qaCABundlePath)
	if err != nil {
		panic(fmt.Sprintf("qa test: read CA bundle %q: %v — set CERTCTL_QA_CA_BUNDLE or CERTCTL_QA_INSECURE=true", qaCABundlePath, err))
	}
	pool := x509.NewCertPool()
	if !pool.AppendCertsFromPEM(pem) {
		panic(fmt.Sprintf("qa test: no PEM certificates parsed from %q", qaCABundlePath))
	}
	cfg.RootCAs = pool
	return cfg
}

func newQAClient() *qaClient {
	return &qaClient{
		http: &http.Client{
			Timeout:   30 * time.Second,
			Transport: &http.Transport{TLSClientConfig: buildQATLSConfig()},
		},
		baseURL: qaServerURL,
		apiKey:  qaAPIKey,
	}
}

func (c *qaClient) do(method, path string, body string) (*http.Response, error) {
	var r io.Reader
	if body != "" {
		r = strings.NewReader(body)
	}
	req, err := http.NewRequest(method, c.baseURL+path, r)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+c.apiKey)
	if body != "" {
		req.Header.Set("Content-Type", "application/json")
	}
	return c.http.Do(req)
}

func (c *qaClient) get(path string) (*http.Response, error)            { return c.do("GET", path, "") }
func (c *qaClient) post(path, body string) (*http.Response, error)     { return c.do("POST", path, body) }
func (c *qaClient) put(path, body string) (*http.Response, error)      { return c.do("PUT", path, body) }
func (c *qaClient) delete(path string) (*http.Response, error)         { return c.do("DELETE", path, "") }

// statusCode makes a request and returns the HTTP status code.
func (c *qaClient) statusCode(method, path, body string) (int, error) {
	resp, err := c.do(method, path, body)
	if err != nil {
		return 0, err
	}
	resp.Body.Close()
	return resp.StatusCode, nil
}

// getJSON makes a GET request and decodes the JSON response.
func (c *qaClient) getJSON(t *testing.T, path string, v interface{}) {
	t.Helper()
	resp, err := c.get(path)
	if err != nil {
		t.Fatalf("GET %s: %v", path, err)
	}
	if resp.StatusCode != 200 {
		body, _ := io.ReadAll(resp.Body)
		resp.Body.Close()
		t.Fatalf("GET %s: status %d, body: %s", path, resp.StatusCode, string(body))
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	if err := json.Unmarshal(data, v); err != nil {
		t.Fatalf("GET %s: decode JSON: %v (body: %s)", path, err, string(data))
	}
}

// bodyStr makes a request and returns the body as a string.
func (c *qaClient) bodyStr(t *testing.T, method, path, body string) (int, string) {
	t.Helper()
	resp, err := c.do(method, path, body)
	if err != nil {
		t.Fatalf("%s %s: %v", method, path, err)
	}
	defer resp.Body.Close()
	b, _ := io.ReadAll(resp.Body)
	return resp.StatusCode, string(b)
}

// timedGet makes a GET request and returns the duration.
func (c *qaClient) timedGet(path string) (time.Duration, int, error) {
	start := time.Now()
	resp, err := c.do("GET", path, "")
	elapsed := time.Since(start)
	if err != nil {
		return elapsed, 0, err
	}
	resp.Body.Close()
	return elapsed, resp.StatusCode, nil
}

// ---------------------------------------------------------------------------
// JSON response helpers (lightweight, no internal imports)
// ---------------------------------------------------------------------------

type qaPagedResponse struct {
	Data    json.RawMessage `json:"data"`
	Total   int             `json:"total"`
	Page    int             `json:"page"`
	PerPage int             `json:"per_page"`
}

type qaCert struct {
	ID         string  `json:"id"`
	CommonName string  `json:"common_name"`
	Status     string  `json:"status"`
	IssuerID   string  `json:"issuer_id"`
	OwnerID    *string `json:"owner_id"`
	ProfileID  *string `json:"certificate_profile_id"`
}

type qaJob struct {
	ID              string  `json:"id"`
	Type            string  `json:"type"`
	Status          string  `json:"status"`
	CertificateID   string  `json:"certificate_id"`
	AgentID         *string `json:"agent_id"`
}

type qaIssuer struct {
	ID      string          `json:"id"`
	Name    string          `json:"name"`
	Type    string          `json:"type"`
	Source  string          `json:"source"`
	Enabled bool            `json:"enabled"`
	Config  json.RawMessage `json:"config"`
}

type qaTarget struct {
	ID      string `json:"id"`
	Name    string `json:"name"`
	Type    string `json:"type"`
	Source  string `json:"source"`
	Enabled bool   `json:"enabled"`
}

type qaAgent struct {
	ID     string `json:"id"`
	Name   string `json:"name"`
	Status string `json:"status"`
	OS     string `json:"os"`
	Arch   string `json:"architecture"`
}

type qaNotification struct {
	ID     string `json:"id"`
	Read   bool   `json:"read"`
}

type qaStats struct {
	TotalCertificates int `json:"total_certificates"`
	ActiveCertificates int `json:"active_certificates"`
	ExpiringCertificates int `json:"expiring_certificates"`
	TotalAgents int `json:"total_agents"`
}

type qaMetrics struct {
	Gauge   map[string]interface{} `json:"gauge"`
	Counter map[string]interface{} `json:"counter"`
	Uptime  float64                `json:"uptime_seconds"`
}

type qaDiscoveredCert struct {
	ID          string `json:"id"`
	Status      string `json:"status"`
	CommonName  string `json:"common_name"`
	Fingerprint string `json:"fingerprint_sha256"`
}

type qaDiscoverySummary struct {
	Unmanaged int `json:"unmanaged"`
	Managed   int `json:"managed"`
	Dismissed int `json:"dismissed"`
}

type qaNetworkScanTarget struct {
	ID    string   `json:"id"`
	Name  string   `json:"name"`
	CIDRs []string `json:"cidrs"`
}

// ---------------------------------------------------------------------------
// Source file helper
// ---------------------------------------------------------------------------

func repoFile(relPath string) string {
	return filepath.Join(qaRepoDir, relPath)
}

func fileContains(t *testing.T, relPath, substr string) {
	t.Helper()
	data, err := os.ReadFile(repoFile(relPath))
	if err != nil {
		t.Fatalf("read %s: %v", relPath, err)
	}
	if !strings.Contains(string(data), substr) {
		t.Fatalf("%s does not contain %q", relPath, substr)
	}
}

func fileExists(t *testing.T, relPath string) {
	t.Helper()
	if _, err := os.Stat(repoFile(relPath)); os.IsNotExist(err) {
		t.Fatalf("file does not exist: %s", relPath)
	}
}

// ---------------------------------------------------------------------------
// Database helper
// ---------------------------------------------------------------------------

type qaDB struct {
	db *sql.DB
}

func openQADB(t *testing.T) *qaDB {
	t.Helper()
	db, err := sql.Open("postgres", qaDBURL)
	if err != nil {
		t.Fatalf("connect to QA DB: %v", err)
	}
	if err := db.Ping(); err != nil {
		t.Fatalf("ping QA DB: %v", err)
	}
	return &qaDB{db: db}
}

func (d *qaDB) queryInt(t *testing.T, query string) int {
	t.Helper()
	var n int
	if err := d.db.QueryRow(query).Scan(&n); err != nil {
		t.Fatalf("queryInt: %v\nquery: %s", err, query)
	}
	return n
}

func (d *qaDB) close() { d.db.Close() }

// ===========================================================================
// QA Test Suite
// ===========================================================================

func TestQA(t *testing.T) {
	c := newQAClient()

	// Verify server is reachable before running anything.
	resp, err := c.get("/health")
	if err != nil {
		t.Fatalf("Server unreachable at %s: %v\nIs the Docker Compose stack running?", qaServerURL, err)
	}
	resp.Body.Close()
	if resp.StatusCode != 200 {
		t.Fatalf("Server unhealthy: GET /health returned %d", resp.StatusCode)
	}

	// ===================================================================
	// Part 1: Infrastructure & Deployment
	// ===================================================================
	t.Run("Part01_Infrastructure", func(t *testing.T) {
		db := openQADB(t)
		defer db.close()

		t.Run("PostgreSQL_TableCount", func(t *testing.T) {
			n := db.queryInt(t, `SELECT count(*) FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE'`)
			if n < 19 {
				t.Fatalf("table count = %d, want >= 19", n)
			}
		})

		t.Run("HealthEndpoint", func(t *testing.T) {
			code, _ := c.statusCode("GET", "/health", "")
			if code != 200 {
				t.Fatalf("GET /health = %d, want 200", code)
			}
		})

		t.Run("ReadyEndpoint", func(t *testing.T) {
			code, _ := c.statusCode("GET", "/ready", "")
			if code != 200 {
				t.Fatalf("GET /ready = %d, want 200", code)
			}
		})

		t.Run("SeedData_Certs", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/certificates", &pr)
			if pr.Total < 10 {
				t.Fatalf("seed certs = %d, want >= 10", pr.Total)
			}
		})

		t.Run("SeedData_Agents", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/agents", &pr)
			if pr.Total < 3 {
				t.Fatalf("seed agents = %d, want >= 3", pr.Total)
			}
		})

		t.Run("SeedData_Issuers", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/issuers", &pr)
			if pr.Total < 3 {
				t.Fatalf("seed issuers = %d, want >= 3", pr.Total)
			}
		})

		t.Run("SeedData_Targets", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/targets", &pr)
			if pr.Total < 3 {
				t.Fatalf("seed targets = %d, want >= 3", pr.Total)
			}
		})

		t.Run("SeedData_Policies", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/policies", &pr)
			if pr.Total < 1 {
				t.Fatalf("seed policies = %d, want >= 1", pr.Total)
			}
		})
	})

	// ===================================================================
	// Part 2: Authentication & Security
	// ===================================================================
	t.Run("Part02_Auth", func(t *testing.T) {
		t.Run("NoAuth_Returns401", func(t *testing.T) {
			req, _ := http.NewRequest("GET", qaServerURL+"/api/v1/certificates", nil)
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("request: %v", err)
			}
			resp.Body.Close()
			if resp.StatusCode != 401 {
				t.Fatalf("no-auth status = %d, want 401", resp.StatusCode)
			}
		})

		t.Run("BadKey_Returns401", func(t *testing.T) {
			req, _ := http.NewRequest("GET", qaServerURL+"/api/v1/certificates", nil)
			req.Header.Set("Authorization", "Bearer wrong-key")
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("request: %v", err)
			}
			resp.Body.Close()
			if resp.StatusCode != 401 {
				t.Fatalf("bad-key status = %d, want 401", resp.StatusCode)
			}
		})

		t.Run("HealthEndpoint_NoAuth", func(t *testing.T) {
			req, _ := http.NewRequest("GET", qaServerURL+"/health", nil)
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("request: %v", err)
			}
			resp.Body.Close()
			if resp.StatusCode != 200 {
				t.Fatalf("/health without auth = %d, want 200", resp.StatusCode)
			}
		})

		t.Run("PrivateKey_NotInCertDetail", func(t *testing.T) {
			_, body := c.bodyStr(t, "GET", "/api/v1/certificates?per_page=1", "")
			if strings.Contains(body, "PRIVATE KEY") {
				t.Fatal("API response contains private key material")
			}
		})
	})

	// ===================================================================
	// Part 3: Certificate Lifecycle (CRUD)
	// ===================================================================
	t.Run("Part03_CertCRUD", func(t *testing.T) {
		t.Run("Create_Minimal", func(t *testing.T) {
			// C-001 scope-expansion: the handler's ValidateRequired
			// contract now gates common_name, owner_id, team_id,
			// issuer_id, name, and renewal_policy_id. A 3-field
			// payload would 400 regardless of the id hint, so the
			// "minimal" variant carries every required field.
			code, body := c.bodyStr(t, "POST", "/api/v1/certificates", `{
				"id": "mc-qa-minimal",
				"name": "qa-minimal",
				"common_name": "qa-minimal.example.com",
				"issuer_id": "iss-local",
				"owner_id": "o-alice",
				"team_id": "t-platform",
				"renewal_policy_id": "rp-standard"
			}`)
			if code != 201 && code != 200 {
				t.Fatalf("create cert: status %d, body: %s", code, body)
			}
		})

		t.Run("Create_Full", func(t *testing.T) {
			code, body := c.bodyStr(t, "POST", "/api/v1/certificates", `{
				"id": "mc-qa-full",
				"name": "qa-full",
				"common_name": "qa-full.example.com",
				"sans": ["qa-full-alt.example.com"],
				"issuer_id": "iss-local",
				"environment": "staging",
				"owner_id": "o-alice",
				"team_id": "t-platform",
				"renewal_policy_id": "rp-standard"
			}`)
			if code != 201 && code != 200 {
				t.Fatalf("create cert: status %d, body: %s", code, body)
			}
		})

		t.Run("Get_ByID", func(t *testing.T) {
			var cert qaCert
			c.getJSON(t, "/api/v1/certificates/mc-qa-minimal", &cert)
			if cert.CommonName != "qa-minimal.example.com" {
				t.Fatalf("CN = %q, want qa-minimal.example.com", cert.CommonName)
			}
		})

		t.Run("Get_NotFound", func(t *testing.T) {
			code, _ := c.statusCode("GET", "/api/v1/certificates/nonexistent-cert-id", "")
			if code != 404 {
				t.Fatalf("nonexistent cert = %d, want 404", code)
			}
		})

		t.Run("List_Pagination", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/certificates?per_page=5", &pr)
			if pr.PerPage != 5 {
				t.Fatalf("per_page = %d, want 5", pr.PerPage)
			}
			if pr.Total < 10 {
				t.Fatalf("total = %d, want >= 10", pr.Total)
			}
		})

		t.Run("Filter_ByStatus", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/certificates?status=Active", &pr)
			var certs []qaCert
			json.Unmarshal(pr.Data, &certs)
			for _, cert := range certs {
				if cert.Status != "Active" {
					t.Fatalf("filter returned non-Active cert: %s status=%s", cert.ID, cert.Status)
				}
			}
		})

		t.Run("Filter_ByIssuer", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/certificates?issuer_id=iss-local", &pr)
			var certs []qaCert
			json.Unmarshal(pr.Data, &certs)
			for _, cert := range certs {
				if cert.IssuerID != "iss-local" {
					t.Fatalf("filter returned wrong issuer: %s issuer=%s", cert.ID, cert.IssuerID)
				}
			}
		})

		t.Run("SparseFields", func(t *testing.T) {
			_, body := c.bodyStr(t, "GET", "/api/v1/certificates?fields=id,common_name&per_page=1", "")
			if !strings.Contains(body, "id") || !strings.Contains(body, "common_name") {
				t.Fatalf("sparse fields missing expected fields: %s", body)
			}
		})

		t.Run("Update", func(t *testing.T) {
			code, _ := c.bodyStr(t, "PUT", "/api/v1/certificates/mc-qa-minimal", `{"environment":"production"}`)
			if code != 200 {
				t.Fatalf("update cert = %d, want 200", code)
			}
		})

		t.Run("Archive", func(t *testing.T) {
			code, _ := c.statusCode("DELETE", "/api/v1/certificates/mc-qa-full", "")
			if code != 204 && code != 200 {
				t.Fatalf("archive cert = %d, want 204 or 200", code)
			}
		})

		// Cleanup
		t.Cleanup(func() {
			c.delete("/api/v1/certificates/mc-qa-minimal")
			c.delete("/api/v1/certificates/mc-qa-full")
		})
	})

	// ===================================================================
	// Part 4: Renewal Workflow
	// ===================================================================
	t.Run("Part04_Renewal", func(t *testing.T) {
		t.Run("TriggerRenewal_CreatesJob", func(t *testing.T) {
			code, body := c.bodyStr(t, "POST", "/api/v1/certificates/mc-web-prod/renew", "")
			if code != 200 && code != 201 && code != 202 {
				t.Fatalf("trigger renewal = %d, body: %s", code, body)
			}
		})

		t.Run("Renewal_NonexistentCert_404", func(t *testing.T) {
			code, _ := c.statusCode("POST", "/api/v1/certificates/nonexistent/renew", "")
			if code != 404 {
				t.Fatalf("renew nonexistent = %d, want 404", code)
			}
		})

		t.Run("AgentWork_ReturnsPendingJobs", func(t *testing.T) {
			// Use a known agent from seed data (ag-web-prod in seed_demo.sql)
			_, body := c.bodyStr(t, "GET", "/api/v1/agents/ag-web-prod/work", "")
			// Should return JSON array (even if empty)
			if !strings.HasPrefix(strings.TrimSpace(body), "[") && !strings.HasPrefix(strings.TrimSpace(body), "null") {
				t.Fatalf("agent work not a JSON array: %s", body[:min(len(body), 100)])
			}
		})
	})

	// ===================================================================
	// Part 5: Revocation
	// ===================================================================
	t.Run("Part05_Revocation", func(t *testing.T) {
		t.Run("Revoke_DefaultReason", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/certificates/mc-blog-prod/revoke", `{}`)
			if code != 200 {
				t.Fatalf("revoke = %d, want 200", code)
			}
		})

		t.Run("Revoke_AlreadyRevoked", func(t *testing.T) {
			code, _ := c.statusCode("POST", "/api/v1/certificates/mc-blog-prod/revoke", `{"reason":"keyCompromise"}`)
			if code != 200 && code != 409 {
				t.Fatalf("re-revoke = %d, want 200 or 409", code)
			}
		})

		t.Run("Revoke_Nonexistent", func(t *testing.T) {
			code, _ := c.statusCode("POST", "/api/v1/certificates/nonexistent/revoke", `{}`)
			if code != 404 {
				t.Fatalf("revoke nonexistent = %d, want 404", code)
			}
		})

		t.Run("Revoke_InvalidReason", func(t *testing.T) {
			code, _ := c.statusCode("POST", "/api/v1/certificates/mc-grpc-prod/revoke", `{"reason":"madeUpReason"}`)
			if code != 400 {
				t.Fatalf("invalid reason = %d, want 400", code)
			}
		})

		// M-006: The non-standard JSON CRL endpoint was removed. RFC 5280 §5
		// defines only the DER wire format, now served unauthenticated at
		// `/.well-known/pki/crl/{issuer_id}` per RFC 8615. Use a plain
		// http.Get — no Bearer — to prove the endpoint is reachable by
		// relying parties with no API credentials.
		t.Run("CRL_DER_Unauthenticated", func(t *testing.T) {
			resp, err := http.Get(qaServerURL + "/.well-known/pki/crl/iss-local")
			if err != nil {
				t.Fatalf("GET DER CRL: %v", err)
			}
			defer resp.Body.Close()
			if resp.StatusCode != 200 {
				b, _ := io.ReadAll(resp.Body)
				t.Fatalf("CRL = %d (body=%s)", resp.StatusCode, string(b))
			}
			if ct := resp.Header.Get("Content-Type"); ct != "application/pkix-crl" {
				t.Errorf("Content-Type: got %q, want %q", ct, "application/pkix-crl")
			}
			body, err := io.ReadAll(resp.Body)
			if err != nil {
				t.Fatalf("read CRL body: %v", err)
			}
			if len(body) == 0 {
				t.Fatal("CRL body empty")
			}
			crl, err := x509.ParseRevocationList(body)
			if err != nil {
				t.Fatalf("parse DER CRL: %v", err)
			}
			if len(crl.RevokedCertificateEntries) < 1 {
				t.Fatalf("CRL entries: got %d, want >= 1", len(crl.RevokedCertificateEntries))
			}
		})
	})

	// ===================================================================
	// Part 6: Policies & Profiles
	// ===================================================================
	t.Run("Part06_Policies", func(t *testing.T) {
		t.Run("ListPolicies", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/policies", &pr)
			if pr.Total < 1 {
				t.Fatalf("policies = %d, want >= 1", pr.Total)
			}
		})

		t.Run("CreatePolicy", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/policies", `{
				"id": "rp-qa", "name": "QA Policy", "type": "AllowedDomains",
				"config": {"domains": ["*.example.com"]}
			}`)
			if code != 201 && code != 200 {
				t.Fatalf("create policy = %d", code)
			}
		})

		t.Run("InvalidPolicyType", func(t *testing.T) {
			code, _ := c.statusCode("POST", "/api/v1/policies", `{
				"id": "rp-bad", "name": "Bad", "type": "invalid_type",
				"config": {}
			}`)
			if code != 400 {
				t.Fatalf("invalid type = %d, want 400", code)
			}
		})

		t.Run("DeletePolicy", func(t *testing.T) {
			code, _ := c.statusCode("DELETE", "/api/v1/policies/rp-qa", "")
			if code != 204 && code != 200 {
				t.Fatalf("delete policy = %d", code)
			}
		})

		t.Run("ListProfiles", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/profiles", &pr)
			if pr.Total < 1 {
				t.Fatalf("profiles = %d, want >= 1", pr.Total)
			}
		})

		t.Run("CreateProfile", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/profiles", `{
				"id": "prof-qa", "name": "QA Profile",
				"allowed_key_algorithms": [{"algorithm":"RSA","min_size":2048},{"algorithm":"ECDSA","min_size":256}],
				"max_ttl_seconds": 7776000
			}`)
			if code != 201 && code != 200 {
				t.Fatalf("create profile = %d", code)
			}
			t.Cleanup(func() { c.delete("/api/v1/profiles/prof-qa") })
		})
	})

	// ===================================================================
	// Part 7: Ownership, Teams & Agent Groups
	// ===================================================================
	t.Run("Part07_Ownership", func(t *testing.T) {
		t.Run("ListTeams", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/teams", &pr)
			if pr.Total < 1 {
				t.Fatalf("teams = %d, want >= 1", pr.Total)
			}
		})

		t.Run("TeamCRUD", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/teams", `{"id":"t-qa","name":"QA Team"}`)
			if code != 201 && code != 200 {
				t.Fatalf("create team = %d", code)
			}
			code, _ = c.statusCode("DELETE", "/api/v1/teams/t-qa", "")
			if code != 204 && code != 200 {
				t.Fatalf("delete team = %d", code)
			}
		})

		t.Run("OwnerCRUD", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/owners", `{
				"id":"o-qa","name":"QA Owner","email":"qa@example.com","team_id":"t-platform"
			}`)
			if code != 201 && code != 200 {
				t.Fatalf("create owner = %d", code)
			}
			t.Cleanup(func() { c.delete("/api/v1/owners/o-qa") })
		})

		t.Run("ListAgentGroups", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/agent-groups", &pr)
			if pr.Total < 1 {
				t.Fatalf("agent groups = %d, want >= 1", pr.Total)
			}
		})
	})

	// ===================================================================
	// Part 8: Job System
	// ===================================================================
	t.Run("Part08_Jobs", func(t *testing.T) {
		t.Run("ListJobs", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/jobs", &pr)
			if pr.Total < 1 {
				t.Fatalf("jobs = %d, want >= 1", pr.Total)
			}
		})

		t.Run("GetNonexistentJob", func(t *testing.T) {
			code, _ := c.statusCode("GET", "/api/v1/jobs/nonexistent", "")
			if code != 404 {
				t.Fatalf("nonexistent job = %d, want 404", code)
			}
		})
	})

	// ===================================================================
	// Part 9: Issuer Connectors
	// ===================================================================
	t.Run("Part09_Issuers", func(t *testing.T) {
		t.Run("ListIssuers", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/issuers", &pr)
			if pr.Total < 3 {
				t.Fatalf("issuers = %d, want >= 3", pr.Total)
			}
		})

		t.Run("GetIssuerDetail", func(t *testing.T) {
			var iss qaIssuer
			c.getJSON(t, "/api/v1/issuers/iss-local", &iss)
			if iss.ID != "iss-local" {
				t.Fatalf("issuer ID = %q, want iss-local", iss.ID)
			}
		})

		t.Run("CreateIssuer", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/issuers", `{
				"id":"iss-qa","name":"QA Issuer","type":"GenericCA","config":{}
			}`)
			if code != 201 && code != 200 {
				t.Fatalf("create issuer = %d", code)
			}
			t.Cleanup(func() { c.delete("/api/v1/issuers/iss-qa") })
		})

		t.Run("CreateIssuer_MissingName", func(t *testing.T) {
			code, _ := c.statusCode("POST", "/api/v1/issuers", `{"type":"GenericCA","config":{}}`)
			if code != 400 {
				t.Fatalf("missing name = %d, want 400", code)
			}
		})
	})

	// ===================================================================
	// Part 10-11: Sub-CA Mode, ARI — mostly manual (need real CA setup)
	// ===================================================================
	t.Run("Part10_SubCA", func(t *testing.T) {
		t.Skip("Requires CA cert+key setup — manual test")
	})

	t.Run("Part11_ARI", func(t *testing.T) {
		t.Skip("Requires ACME CA with ARI support — manual test")
	})

	// ===================================================================
	// Part 12-13: Vault PKI, DigiCert — require external services
	// ===================================================================
	t.Run("Part12_VaultPKI", func(t *testing.T) {
		t.Skip("Requires live Vault server — manual test")
	})

	t.Run("Part13_DigiCert", func(t *testing.T) {
		t.Skip("Requires DigiCert sandbox — manual test")
	})

	// ===================================================================
	// Part 14: Target Connectors & Deployment
	// ===================================================================
	t.Run("Part14_Targets", func(t *testing.T) {
		t.Run("ListTargets", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/targets", &pr)
			if pr.Total < 3 {
				t.Fatalf("targets = %d, want >= 3", pr.Total)
			}
		})

		t.Run("CreateNGINXTarget", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/targets", `{
				"id":"tgt-qa-nginx","name":"QA NGINX","type":"NGINX",
				"config":{"cert_path":"/etc/nginx/ssl/cert.pem","key_path":"/etc/nginx/ssl/key.pem","reload_command":"nginx -s reload"}
			}`)
			if code != 201 && code != 200 {
				t.Fatalf("create target = %d", code)
			}
			t.Cleanup(func() { c.delete("/api/v1/targets/tgt-qa-nginx") })
		})

		t.Run("DeleteTarget_204", func(t *testing.T) {
			c.post("/api/v1/targets", `{"id":"tgt-qa-del","name":"Delete Me","type":"NGINX","config":{}}`)
			code, _ := c.statusCode("DELETE", "/api/v1/targets/tgt-qa-del", "")
			if code != 204 {
				t.Fatalf("delete target = %d, want 204", code)
			}
		})
	})

	// ===================================================================
	// Part 15-17: Apache/HAProxy, Traefik/Caddy, IIS — need real services or Windows
	// ===================================================================

	// ===================================================================
	// Part 18: Agent Operations
	// ===================================================================
	t.Run("Part18_Agents", func(t *testing.T) {
		t.Run("RegisterAgent", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/agents/ag-qa-new/heartbeat", `{
				"os":"linux","architecture":"amd64","version":"1.0.0"
			}`)
			if code != 200 {
				t.Fatalf("heartbeat = %d, want 200", code)
			}
		})

		t.Run("AgentMetadata", func(t *testing.T) {
			var agent qaAgent
			c.getJSON(t, "/api/v1/agents/ag-qa-new", &agent)
			if agent.OS != "linux" {
				t.Fatalf("agent OS = %q, want linux", agent.OS)
			}
			if agent.Arch != "amd64" {
				t.Fatalf("agent arch = %q, want amd64", agent.Arch)
			}
		})

		t.Run("HeartbeatNonexistent", func(t *testing.T) {
			// Heartbeat auto-creates agents, so this should succeed
			code, _ := c.statusCode("POST", "/api/v1/agents/ag-qa-ghost/heartbeat", `{}`)
			if code != 200 {
				t.Fatalf("ghost heartbeat = %d, want 200", code)
			}
			t.Cleanup(func() {
				c.delete("/api/v1/agents/ag-qa-new")
				c.delete("/api/v1/agents/ag-qa-ghost")
			})
		})
	})

	// ===================================================================
	// Part 19: Agent Work Routing
	// ===================================================================
	t.Run("Part19_WorkRouting", func(t *testing.T) {
		t.Run("EmptyWork_NoTargets", func(t *testing.T) {
			// Register agent with no targets
			c.post("/api/v1/agents/ag-qa-notargets/heartbeat", `{}`)
			_, body := c.bodyStr(t, "GET", "/api/v1/agents/ag-qa-notargets/work", "")
			body = strings.TrimSpace(body)
			if body != "[]" && body != "null" {
				t.Fatalf("expected empty work, got: %s", body[:min(len(body), 200)])
			}
			t.Cleanup(func() { c.delete("/api/v1/agents/ag-qa-notargets") })
		})
	})

	// ===================================================================
	// Part 20: Post-Deployment TLS Verification
	// ===================================================================
	t.Run("Part20_Verification", func(t *testing.T) {
		t.Run("GetVerification_NoJob", func(t *testing.T) {
			code, _ := c.statusCode("GET", "/api/v1/jobs/nonexistent/verification", "")
			if code != 404 {
				t.Fatalf("verification for nonexistent job = %d, want 404", code)
			}
		})
	})

	// ===================================================================
	// Part 21: EST Server (RFC 7030)
	// ===================================================================
	t.Run("Part21_EST", func(t *testing.T) {
		t.Run("CACerts", func(t *testing.T) {
			// EST routes use r.Register() which applies full middleware (incl. auth)
			resp, err := c.get("/.well-known/est/cacerts")
			if err != nil {
				t.Fatalf("GET cacerts: %v", err)
			}
			defer resp.Body.Close()
			if resp.StatusCode != 200 {
				t.Fatalf("cacerts = %d, want 200", resp.StatusCode)
			}
			ct := resp.Header.Get("Content-Type")
			if !strings.Contains(ct, "pkcs7") && !strings.Contains(ct, "application") {
				t.Logf("cacerts content-type: %s (expected pkcs7-mime)", ct)
			}
		})

		t.Run("CSRAttrs", func(t *testing.T) {
			resp, err := c.get("/.well-known/est/csrattrs")
			if err != nil {
				t.Fatalf("GET csrattrs: %v", err)
			}
			resp.Body.Close()
			if resp.StatusCode != 200 && resp.StatusCode != 204 {
				t.Fatalf("csrattrs = %d, want 200 or 204", resp.StatusCode)
			}
		})
	})

	// ===================================================================
	// Part 22: Certificate Export
	// ===================================================================
	t.Run("Part22_Export", func(t *testing.T) {
		t.Run("ExportPEM", func(t *testing.T) {
			code, body := c.bodyStr(t, "GET", "/api/v1/certificates/mc-web-prod/export/pem", "")
			if code != 200 {
				t.Fatalf("export PEM = %d", code)
			}
			if !strings.Contains(body, "certificate") && !strings.Contains(body, "pem") {
				t.Logf("PEM export body (first 200 chars): %s", body[:min(len(body), 200)])
			}
		})

		t.Run("ExportPKCS12", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/certificates/mc-web-prod/export/pkcs12", `{"password":"test123"}`)
			// PKCS12 may fail if no cert version exists
			if code != 200 && code != 404 {
				t.Fatalf("export PKCS12 = %d, want 200 or 404", code)
			}
		})

		t.Run("Export_Nonexistent", func(t *testing.T) {
			code, _ := c.statusCode("GET", "/api/v1/certificates/nonexistent/export/pem", "")
			if code != 404 {
				t.Fatalf("export nonexistent = %d, want 404", code)
			}
		})
	})

	// ===================================================================
	// Part 25: Certificate Discovery
	// ===================================================================
	t.Run("Part25_Discovery", func(t *testing.T) {
		t.Run("ListDiscovered", func(t *testing.T) {
			code, _ := c.bodyStr(t, "GET", "/api/v1/discovered-certificates", "")
			if code != 200 {
				t.Fatalf("list discovered = %d", code)
			}
		})

		t.Run("DiscoverySummary", func(t *testing.T) {
			code, body := c.bodyStr(t, "GET", "/api/v1/discovery-summary", "")
			if code != 200 {
				t.Fatalf("discovery summary = %d", code)
			}
			if !strings.Contains(body, "unmanaged") {
				t.Fatalf("summary missing unmanaged field")
			}
		})

		t.Run("ListNetworkScanTargets", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/network-scan-targets", &pr)
			if pr.Total < 3 {
				t.Fatalf("scan targets = %d, want >= 3", pr.Total)
			}
		})

		t.Run("CreateNetworkScanTarget", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/network-scan-targets", `{
				"id":"nst-qa","name":"QA Scan","cidrs":["10.0.0.0/24"],"ports":[443]
			}`)
			if code != 201 && code != 200 {
				t.Fatalf("create scan target = %d", code)
			}
			t.Cleanup(func() { c.delete("/api/v1/network-scan-targets/nst-qa") })
		})

		t.Run("InvalidCIDR", func(t *testing.T) {
			code, _ := c.statusCode("POST", "/api/v1/network-scan-targets", `{
				"name":"Bad","cidrs":["not-a-cidr"],"ports":[443]
			}`)
			if code != 400 {
				t.Fatalf("invalid CIDR = %d, want 400", code)
			}
		})
	})

	// ===================================================================
	// Part 26: Enhanced Query API
	// ===================================================================
	t.Run("Part26_QueryAPI", func(t *testing.T) {
		t.Run("SortDescending", func(t *testing.T) {
			code, _ := c.bodyStr(t, "GET", "/api/v1/certificates?sort=-createdAt&per_page=5", "")
			if code != 200 {
				t.Fatalf("sort desc = %d", code)
			}
		})

		t.Run("CursorPagination", func(t *testing.T) {
			code, body := c.bodyStr(t, "GET", "/api/v1/certificates?page_size=3", "")
			if code != 200 {
				t.Fatalf("cursor page = %d", code)
			}
			// Should have next_cursor or data
			if !strings.Contains(body, "data") {
				t.Fatalf("cursor response missing data")
			}
		})

		t.Run("TimeRangeFilter", func(t *testing.T) {
			code, _ := c.bodyStr(t, "GET", "/api/v1/certificates?expires_before=2030-01-01T00:00:00Z", "")
			if code != 200 {
				t.Fatalf("time range = %d", code)
			}
		})

		t.Run("InvalidSortField", func(t *testing.T) {
			code, _ := c.statusCode("GET", "/api/v1/certificates?sort=notAField", "")
			if code != 400 {
				t.Logf("invalid sort field = %d (may return 200 ignoring bad sort)", code)
			}
		})
	})

	// ===================================================================
	// Part 27: Request Body Size Limits
	// ===================================================================
	t.Run("Part27_BodyLimits", func(t *testing.T) {
		t.Run("OversizedBody_Rejected", func(t *testing.T) {
			// Send a 2MB body (default limit is 1MB)
			bigBody := `{"name":"` + strings.Repeat("x", 2*1024*1024) + `"}`
			code, _ := c.statusCode("POST", "/api/v1/certificates", bigBody)
			if code != 413 && code != 400 {
				t.Fatalf("oversize body = %d, want 413 or 400", code)
			}
		})
	})

	// ===================================================================
	// Part 28-29: CLI, MCP — require compiled binaries
	// ===================================================================
	t.Run("Part28_CLI", func(t *testing.T) {
		t.Skip("Requires compiled certctl-cli binary — manual test")
	})

	t.Run("Part29_MCP", func(t *testing.T) {
		t.Skip("Requires compiled mcp-server binary + stdio — manual test")
	})

	// ===================================================================
	// Part 30: Observability
	// ===================================================================
	t.Run("Part30_Observability", func(t *testing.T) {
		t.Run("DashboardSummary", func(t *testing.T) {
			var stats qaStats
			c.getJSON(t, "/api/v1/stats/summary", &stats)
			if stats.TotalCertificates < 10 {
				t.Fatalf("total certs = %d, want >= 10", stats.TotalCertificates)
			}
		})

		t.Run("CertsByStatus", func(t *testing.T) {
			code, body := c.bodyStr(t, "GET", "/api/v1/stats/certificates-by-status", "")
			if code != 200 {
				t.Fatalf("certs by status = %d", code)
			}
			if !strings.Contains(body, "Active") {
				t.Fatalf("missing Active status in response")
			}
		})

		t.Run("ExpirationTimeline", func(t *testing.T) {
			code, _ := c.bodyStr(t, "GET", "/api/v1/stats/expiration-timeline?days=90", "")
			if code != 200 {
				t.Fatalf("expiration timeline = %d", code)
			}
		})

		t.Run("JobTrends", func(t *testing.T) {
			code, _ := c.bodyStr(t, "GET", "/api/v1/stats/job-trends?days=30", "")
			if code != 200 {
				t.Fatalf("job trends = %d", code)
			}
		})

		t.Run("IssuanceRate", func(t *testing.T) {
			code, _ := c.bodyStr(t, "GET", "/api/v1/stats/issuance-rate?days=30", "")
			if code != 200 {
				t.Fatalf("issuance rate = %d", code)
			}
		})

		t.Run("JSONMetrics", func(t *testing.T) {
			var m qaMetrics
			c.getJSON(t, "/api/v1/metrics", &m)
			if m.Uptime <= 0 {
				t.Fatalf("uptime = %f, want > 0", m.Uptime)
			}
			if len(m.Gauge) == 0 {
				t.Fatal("no gauge metrics")
			}
		})

		t.Run("Prometheus_ContentType", func(t *testing.T) {
			resp, err := c.get("/api/v1/metrics/prometheus")
			if err != nil {
				t.Fatalf("GET prometheus: %v", err)
			}
			ct := resp.Header.Get("Content-Type")
			resp.Body.Close()
			if !strings.Contains(ct, "text/plain") {
				t.Fatalf("prometheus content-type = %q, want text/plain", ct)
			}
		})

		t.Run("Prometheus_HasMetrics", func(t *testing.T) {
			_, body := c.bodyStr(t, "GET", "/api/v1/metrics/prometheus", "")
			for _, metric := range []string{
				"certctl_certificate_total",
				"certctl_agent_total",
				"certctl_job_pending",
				"certctl_uptime_seconds",
			} {
				if !strings.Contains(body, metric) {
					t.Errorf("prometheus output missing %s", metric)
				}
			}
		})
	})

	// ===================================================================
	// Part 31: Notifications
	// ===================================================================
	t.Run("Part31_Notifications", func(t *testing.T) {
		t.Run("ListNotifications", func(t *testing.T) {
			code, _ := c.bodyStr(t, "GET", "/api/v1/notifications", "")
			if code != 200 {
				t.Fatalf("list notifications = %d", code)
			}
		})

		t.Run("GetNonexistent", func(t *testing.T) {
			code, _ := c.statusCode("GET", "/api/v1/notifications/nonexistent", "")
			if code != 404 {
				t.Fatalf("nonexistent notification = %d, want 404", code)
			}
		})
	})

	// ===================================================================
	// Part 32: Audit Trail
	// ===================================================================
	t.Run("Part32_Audit", func(t *testing.T) {
		t.Run("ListEvents", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/audit", &pr)
			if pr.Total < 10 {
				t.Fatalf("audit events = %d, want >= 10", pr.Total)
			}
		})

		t.Run("Immutability_NoPUT", func(t *testing.T) {
			code, _ := c.statusCode("PUT", "/api/v1/audit/any-event-id", `{"action":"hack"}`)
			if code == 200 {
				t.Fatal("PUT /events should not return 200 — audit trail must be immutable")
			}
		})

		t.Run("Immutability_NoDELETE", func(t *testing.T) {
			code, _ := c.statusCode("DELETE", "/api/v1/audit/any-event-id", "")
			if code == 200 || code == 204 {
				t.Fatal("DELETE /events should not succeed — audit trail must be immutable")
			}
		})
	})

	// ===================================================================
	// Part 33: Background Scheduler (log-based checks)
	// ===================================================================
	t.Run("Part33_Scheduler", func(t *testing.T) {
		t.Skip("Scheduler tests are timing-dependent — verify via Docker logs manually")
	})

	// ===================================================================
	// Part 34: Structured Logging
	// ===================================================================
	t.Run("Part34_Logging", func(t *testing.T) {
		t.Skip("Requires Docker log inspection — manual test")
	})

	// ===================================================================
	// Part 35: GUI Testing
	// ===================================================================
	t.Run("Part35_GUI", func(t *testing.T) {
		t.Skip("Requires browser — manual test")
	})

	// ===================================================================
	// Part 36-37: Issuer Catalog, Frontend Audit
	// ===================================================================
	t.Run("Part36_IssuerCatalog", func(t *testing.T) {
		t.Skip("Requires browser — manual test")
	})

	t.Run("Part37_FrontendAudit", func(t *testing.T) {
		t.Skip("Requires browser — manual test")
	})

	// ===================================================================
	// Part 38: Error Handling
	// ===================================================================
	t.Run("Part38_ErrorHandling", func(t *testing.T) {
		t.Run("MalformedJSON", func(t *testing.T) {
			code, _ := c.statusCode("POST", "/api/v1/certificates", "this is not json")
			if code != 400 {
				t.Fatalf("malformed JSON = %d, want 400", code)
			}
		})

		t.Run("MissingRequiredField", func(t *testing.T) {
			code, _ := c.statusCode("POST", "/api/v1/certificates", `{"id":"mc-qa-noCN"}`)
			if code != 400 {
				t.Fatalf("missing CN = %d, want 400", code)
			}
		})

		t.Run("MethodNotAllowed", func(t *testing.T) {
			code, _ := c.statusCode("PATCH", "/api/v1/certificates", "")
			if code != 405 {
				t.Logf("PATCH /certificates = %d (server may not distinguish 405 from 404)", code)
			}
		})

		t.Run("UTF8InCommonName", func(t *testing.T) {
			code, _ := c.bodyStr(t, "POST", "/api/v1/certificates", `{
				"id":"mc-qa-utf8","common_name":"日本語.example.com","issuer_id":"iss-local"
			}`)
			if code == 500 {
				t.Fatal("server crashed on UTF-8 common name")
			}
			t.Cleanup(func() { c.delete("/api/v1/certificates/mc-qa-utf8") })
		})

		t.Run("EmptyBody", func(t *testing.T) {
			code, _ := c.statusCode("POST", "/api/v1/certificates", "")
			if code == 500 {
				t.Fatal("server crashed on empty body")
			}
		})
	})

	// ===================================================================
	// Part 39: Performance Spot Checks
	// ===================================================================
	t.Run("Part39_Performance", func(t *testing.T) {
		t.Run("ListCerts_Under200ms", func(t *testing.T) {
			d, code, err := c.timedGet("/api/v1/certificates?per_page=15")
			if err != nil {
				t.Fatalf("request: %v", err)
			}
			if code != 200 {
				t.Fatalf("status = %d", code)
			}
			if d > 200*time.Millisecond {
				t.Fatalf("took %v, want < 200ms", d)
			}
		})

		t.Run("StatsSummary_Under500ms", func(t *testing.T) {
			d, code, _ := c.timedGet("/api/v1/stats/summary")
			if code != 200 {
				t.Fatalf("status = %d", code)
			}
			if d > 500*time.Millisecond {
				t.Fatalf("took %v, want < 500ms", d)
			}
		})

		t.Run("Metrics_Under200ms", func(t *testing.T) {
			d, code, _ := c.timedGet("/api/v1/metrics")
			if code != 200 {
				t.Fatalf("status = %d", code)
			}
			if d > 200*time.Millisecond {
				t.Fatalf("took %v, want < 200ms", d)
			}
		})

		t.Run("Prometheus_Under300ms", func(t *testing.T) {
			d, code, _ := c.timedGet("/api/v1/metrics/prometheus")
			if code != 200 {
				t.Fatalf("status = %d", code)
			}
			if d > 300*time.Millisecond {
				t.Fatalf("took %v, want < 300ms", d)
			}
		})

		t.Run("AuditTrail_Under500ms", func(t *testing.T) {
			d, code, _ := c.timedGet("/api/v1/audit?per_page=50")
			if code != 200 {
				t.Fatalf("status = %d", code)
			}
			if d > 500*time.Millisecond {
				t.Fatalf("took %v, want < 500ms", d)
			}
		})
	})

	// ===================================================================
	// Part 40: Documentation Verification (source checks)
	// ===================================================================
	t.Run("Part40_Docs", func(t *testing.T) {
		t.Run("README_Exists", func(t *testing.T) {
			fileExists(t, "README.md")
		})

		t.Run("Quickstart_Exists", func(t *testing.T) {
			fileExists(t, "docs/quickstart.md")
		})

		t.Run("Architecture_Exists", func(t *testing.T) {
			fileExists(t, "docs/architecture.md")
		})

		t.Run("Connectors_Exists", func(t *testing.T) {
			fileExists(t, "docs/connectors.md")
		})

		t.Run("Compliance_Exists", func(t *testing.T) {
			fileExists(t, "docs/compliance.md")
		})

		t.Run("MigrationGuides_Exist", func(t *testing.T) {
			for _, guide := range []string{
				"docs/migrate-from-certbot.md",
				"docs/migrate-from-acmesh.md",
				"docs/certctl-for-cert-manager-users.md",
			} {
				fileExists(t, guide)
			}
		})

		t.Run("IssuerTypes_InDocs", func(t *testing.T) {
			data, err := os.ReadFile(repoFile("docs/connectors.md"))
			if err != nil {
				t.Fatalf("read connectors.md: %v", err)
			}
			doc := string(data)
			for _, typ := range []string{"ACME", "Vault", "step-ca", "DigiCert", "Sectigo", "Google CAS", "Local CA", "OpenSSL"} {
				if !strings.Contains(doc, typ) {
					t.Errorf("connectors.md missing issuer type: %s", typ)
				}
			}
		})

		t.Run("TargetTypes_InDocs", func(t *testing.T) {
			data, err := os.ReadFile(repoFile("docs/connectors.md"))
			if err != nil {
				t.Fatalf("read connectors.md: %v", err)
			}
			doc := string(data)
			for _, typ := range []string{"NGINX", "Apache", "HAProxy", "Traefik", "Caddy", "Envoy", "F5", "IIS", "SSH", "Postfix", "Java Keystore"} {
				if !strings.Contains(doc, typ) {
					t.Errorf("connectors.md missing target type: %s", typ)
				}
			}
		})
	})

	// ===================================================================
	// Part 41: Regression Tests
	// ===================================================================
	t.Run("Part41_Regression", func(t *testing.T) {
		t.Run("DELETE_Returns204", func(t *testing.T) {
			c.post("/api/v1/targets", `{"id":"tgt-qa-regr","name":"Regression","type":"NGINX","config":{}}`)
			code, _ := c.statusCode("DELETE", "/api/v1/targets/tgt-qa-regr", "")
			if code != 204 {
				t.Fatalf("DELETE target = %d, want 204", code)
			}
		})

		t.Run("PerPage_MaxFallback", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/certificates?per_page=9999", &pr)
			if pr.PerPage != 50 {
				t.Fatalf("per_page = %d, want 50 (default fallback)", pr.PerPage)
			}
		})

		t.Run("SeedNetworkScanTargets", func(t *testing.T) {
			var pr qaPagedResponse
			c.getJSON(t, "/api/v1/network-scan-targets", &pr)
			if pr.Total < 3 {
				t.Fatalf("scan targets = %d, want >= 3", pr.Total)
			}
		})

		t.Run("NoErrors_Is_With_New", func(t *testing.T) {
			// Verify no test files use the broken errors.Is(err, errors.New(...)) pattern
			data, err := os.ReadFile(repoFile("internal/service"))
			if err != nil {
				// Can't read a directory, use filepath.Walk
				var found int
				filepath.Walk(repoFile("internal/service"), func(path string, info os.FileInfo, err error) error {
					if err != nil || !strings.HasSuffix(path, "_test.go") {
						return nil
					}
					content, _ := os.ReadFile(path)
					if strings.Contains(string(content), "errors.Is") && strings.Contains(string(content), "errors.New") {
						// Check if they're on the same line
						for _, line := range strings.Split(string(content), "\n") {
							if strings.Contains(line, "errors.Is") && strings.Contains(line, "errors.New") {
								found++
							}
						}
					}
					return nil
				})
				_ = data
				if found > 0 {
					t.Fatalf("found %d instances of errors.Is(err, errors.New(...)) anti-pattern", found)
				}
				return
			}
		})
	})

	// ===================================================================
	// Part 42: Envoy Target Connector (source checks)
	// ===================================================================
	t.Run("Part42_Envoy", func(t *testing.T) {
		t.Run("DomainType", func(t *testing.T) {
			fileContains(t, "internal/domain/connector.go", "TargetTypeEnvoy")
		})

		t.Run("ConnectorExists", func(t *testing.T) {
			fileExists(t, "internal/connector/target/envoy/envoy.go")
		})

		t.Run("TestFileExists", func(t *testing.T) {
			fileExists(t, "internal/connector/target/envoy/envoy_test.go")
		})

		t.Run("InOpenAPI", func(t *testing.T) {
			fileContains(t, "api/openapi.yaml", "Envoy")
		})

		t.Run("AgentDispatch", func(t *testing.T) {
			fileContains(t, "cmd/agent/main.go", "envoy")
		})
	})

	// ===================================================================
	// Part 43: Postfix & Dovecot
	// ===================================================================
	t.Run("Part43_PostfixDovecot", func(t *testing.T) {
		t.Run("DomainTypes", func(t *testing.T) {
			fileContains(t, "internal/domain/connector.go", "TargetTypePostfix")
			fileContains(t, "internal/domain/connector.go", "TargetTypeDovecot")
		})

		t.Run("ConnectorExists", func(t *testing.T) {
			fileExists(t, "internal/connector/target/postfix/postfix.go")
		})

		t.Run("InOpenAPI", func(t *testing.T) {
			fileContains(t, "api/openapi.yaml", "Postfix")
			fileContains(t, "api/openapi.yaml", "Dovecot")
		})
	})

	// ===================================================================
	// Part 44: SSH Target Connector
	// ===================================================================
	t.Run("Part44_SSH", func(t *testing.T) {
		t.Run("DomainType", func(t *testing.T) {
			fileContains(t, "internal/domain/connector.go", "TargetTypeSSH")
		})

		t.Run("ConnectorExists", func(t *testing.T) {
			fileExists(t, "internal/connector/target/ssh/ssh.go")
		})

		t.Run("AgentDispatch", func(t *testing.T) {
			fileContains(t, "cmd/agent/main.go", "sshconn")
		})

		t.Run("InOpenAPI", func(t *testing.T) {
			fileContains(t, "api/openapi.yaml", "SSH")
		})
	})

	// ===================================================================
	// Part 45: Windows Certificate Store
	// ===================================================================
	t.Run("Part45_WinCertStore", func(t *testing.T) {
		t.Run("DomainType", func(t *testing.T) {
			fileContains(t, "internal/domain/connector.go", "TargetTypeWinCertStore")
		})

		t.Run("ConnectorExists", func(t *testing.T) {
			fileExists(t, "internal/connector/target/wincertstore/wincertstore.go")
		})

		t.Run("SharedCertutil", func(t *testing.T) {
			fileExists(t, "internal/connector/target/certutil/certutil.go")
			fileExists(t, "internal/connector/target/certutil/certutil_test.go")
		})
	})

	// ===================================================================
	// Part 46: Java Keystore
	// ===================================================================
	t.Run("Part46_JavaKeystore", func(t *testing.T) {
		t.Run("DomainType", func(t *testing.T) {
			fileContains(t, "internal/domain/connector.go", "TargetTypeJavaKeystore")
		})

		t.Run("ConnectorExists", func(t *testing.T) {
			fileExists(t, "internal/connector/target/javakeystore/javakeystore.go")
		})

		t.Run("InOpenAPI", func(t *testing.T) {
			fileContains(t, "api/openapi.yaml", "JavaKeystore")
		})
	})

	// ===================================================================
	// Part 47: Certificate Digest Email
	// ===================================================================
	t.Run("Part47_Digest", func(t *testing.T) {
		t.Run("PreviewEndpoint", func(t *testing.T) {
			code, _ := c.bodyStr(t, "GET", "/api/v1/digest/preview", "")
			// 200 if SMTP configured, 503 if not
			if code != 200 && code != 503 {
				t.Fatalf("digest preview = %d, want 200 or 503", code)
			}
		})

		t.Run("ServiceExists", func(t *testing.T) {
			fileExists(t, "internal/service/digest.go")
		})

		t.Run("AdapterExists", func(t *testing.T) {
			fileExists(t, "internal/connector/notifier/email/adapter.go")
		})
	})

	// ===================================================================
	// Part 48: Dynamic Issuer Configuration
	// ===================================================================
	t.Run("Part48_DynamicIssuers", func(t *testing.T) {
		t.Run("CryptoPackage", func(t *testing.T) {
			fileExists(t, "internal/crypto/crypto.go")
		})

		t.Run("CreateIssuerViaAPI", func(t *testing.T) {
			code, body := c.bodyStr(t, "POST", "/api/v1/issuers", `{
				"name":"QA Dynamic ACME","type":"ACME",
				"config":{"directory_url":"https://acme-staging-v02.api.letsencrypt.org/directory","email":"qa@example.com"}
			}`)
			if code != 201 && code != 200 {
				t.Fatalf("create dynamic issuer = %d, body: %s", code, body)
			}
			// Extract ID for cleanup
			var resp map[string]interface{}
			json.Unmarshal([]byte(body), &resp)
			if id, ok := resp["id"].(string); ok {
				t.Cleanup(func() { c.delete("/api/v1/issuers/" + id) })
			}
		})

		t.Run("ConfigRedacted", func(t *testing.T) {
			// Check that sensitive fields are masked in list responses
			_, body := c.bodyStr(t, "GET", "/api/v1/issuers", "")
			// If vault token or api_key appears unmasked, it's a security issue
			if strings.Contains(body, "s.") && strings.Contains(body, "vault_token") {
				// Heuristic — real vault tokens start with "s."
				t.Log("WARNING: Vault token may be exposed in API response")
			}
		})

		t.Run("Migration_Exists", func(t *testing.T) {
			fileExists(t, "migrations/000009_issuer_config.up.sql")
		})
	})

	// ===================================================================
	// Part 49: Dynamic Target Configuration
	// ===================================================================
	t.Run("Part49_DynamicTargets", func(t *testing.T) {
		t.Run("CreateTargetViaAPI", func(t *testing.T) {
			code, body := c.bodyStr(t, "POST", "/api/v1/targets", `{
				"name":"QA Dynamic NGINX","type":"NGINX",
				"config":{"cert_path":"/etc/ssl/cert.pem","key_path":"/etc/ssl/key.pem","reload_command":"nginx -s reload"}
			}`)
			if code != 201 && code != 200 {
				t.Fatalf("create dynamic target = %d, body: %s", code, body)
			}
			var resp map[string]interface{}
			json.Unmarshal([]byte(body), &resp)
			if id, ok := resp["id"].(string); ok {
				t.Cleanup(func() { c.delete("/api/v1/targets/" + id) })
			}
		})

		t.Run("Migration_Exists", func(t *testing.T) {
			fileExists(t, "migrations/000010_target_config.up.sql")
		})
	})

	// ===================================================================
	// Part 50: Onboarding Wizard
	// ===================================================================
	t.Run("Part50_Onboarding", func(t *testing.T) {
		t.Run("WizardComponent_Exists", func(t *testing.T) {
			fileExists(t, "web/src/pages/OnboardingWizard.tsx")
		})

		t.Run("DockerCompose_Split", func(t *testing.T) {
			// Clean compose should NOT reference seed_demo
			data, _ := os.ReadFile(repoFile("deploy/docker-compose.yml"))
			if strings.Contains(string(data), "seed_demo") {
				t.Fatal("docker-compose.yml should not reference seed_demo.sql")
			}
			// Demo override SHOULD reference seed_demo
			data, _ = os.ReadFile(repoFile("deploy/docker-compose.demo.yml"))
			if !strings.Contains(string(data), "seed_demo") {
				t.Fatal("docker-compose.demo.yml should reference seed_demo.sql")
			}
		})
	})

	// ===================================================================
	// Part 51: ACME Profile Selection
	// ===================================================================
	t.Run("Part51_ACMEProfiles", func(t *testing.T) {
		t.Run("ProfileModule_Exists", func(t *testing.T) {
			fileExists(t, "internal/connector/issuer/acme/profile.go")
			fileExists(t, "internal/connector/issuer/acme/profile_test.go")
		})

		t.Run("ProfileConfig_InFrontend", func(t *testing.T) {
			fileContains(t, "web/src/config/issuerTypes.ts", "profile")
		})

		t.Run("ARI_RFC9773_NoOldRefs", func(t *testing.T) {
			// Verify no remaining references to old RFC 9702
			files := []string{
				"internal/connector/issuer/acme/ari.go",
				"internal/domain/ari.go",
				"internal/service/renewal.go",
			}
			for _, f := range files {
				data, err := os.ReadFile(repoFile(f))
				if err != nil {
					continue
				}
				if strings.Contains(string(data), "9702") {
					t.Errorf("%s still references RFC 9702 (should be 9773)", f)
				}
			}
		})
	})

	// ===================================================================
	// Part 52: Helm Chart
	// ===================================================================
	t.Run("Part52_HelmChart", func(t *testing.T) {
		t.Run("ChartYAML_Exists", func(t *testing.T) {
			fileExists(t, "deploy/helm/certctl/Chart.yaml")
		})

		t.Run("ValuesYAML_Exists", func(t *testing.T) {
			fileExists(t, "deploy/helm/certctl/values.yaml")
		})

		t.Run("Templates_Exist", func(t *testing.T) {
			for _, tmpl := range []string{
				"deploy/helm/certctl/templates/server-deployment.yaml",
				"deploy/helm/certctl/templates/server-service.yaml",
				"deploy/helm/certctl/templates/postgres-statefulset.yaml",
				"deploy/helm/certctl/templates/agent-daemonset.yaml",
			} {
				fileExists(t, tmpl)
			}
		})

		t.Run("SecurityContext_InTemplates", func(t *testing.T) {
			fileContains(t, "deploy/helm/certctl/templates/server-deployment.yaml", "securityContext")
		})

		t.Run("HealthProbes_InTemplates", func(t *testing.T) {
			fileContains(t, "deploy/helm/certctl/templates/server-deployment.yaml", "livenessProbe")
			fileContains(t, "deploy/helm/certctl/templates/server-deployment.yaml", "readinessProbe")
		})
	})

	// ===================================================================
	t.Run("Part53_KubernetesSecrets", func(t *testing.T) {
		t.Run("ConnectorPackageExists", func(t *testing.T) {
			fileExists(t, "internal/connector/target/k8ssecret/k8ssecret.go")
		})

		t.Run("TestFileExists", func(t *testing.T) {
			fileExists(t, "internal/connector/target/k8ssecret/k8ssecret_test.go")
		})

		t.Run("DomainTypeRegistered", func(t *testing.T) {
			fileContains(t, "internal/domain/connector.go", `TargetTypeKubernetesSecrets`)
		})

		t.Run("ServiceValidationEntry", func(t *testing.T) {
			fileContains(t, "internal/service/target.go", `TargetTypeKubernetesSecrets`)
		})

		t.Run("AgentDispatchCase", func(t *testing.T) {
			fileContains(t, "cmd/agent/main.go", `"KubernetesSecrets"`)
		})

		t.Run("FrontendTypeLabel", func(t *testing.T) {
			fileContains(t, "web/src/pages/TargetsPage.tsx", `KubernetesSecrets`)
		})

		t.Run("OpenAPIEnum", func(t *testing.T) {
			fileContains(t, "api/openapi.yaml", `KubernetesSecrets`)
		})

		t.Run("HelmRBAC", func(t *testing.T) {
			fileContains(t, "deploy/helm/certctl/templates/serviceaccount.yaml", `secrets`)
		})
	})

	// ===================================================================
	t.Run("Part54_AWSACMPCA", func(t *testing.T) {
		t.Run("ConnectorPackageExists", func(t *testing.T) {
			fileExists(t, "internal/connector/issuer/awsacmpca/awsacmpca.go")
		})

		t.Run("TestFileExists", func(t *testing.T) {
			fileExists(t, "internal/connector/issuer/awsacmpca/awsacmpca_test.go")
		})

		t.Run("DomainTypeRegistered", func(t *testing.T) {
			fileContains(t, "internal/domain/connector.go", `IssuerTypeAWSACMPCA`)
		})

		t.Run("ServiceValidationEntry", func(t *testing.T) {
			fileContains(t, "internal/service/issuer.go", `IssuerTypeAWSACMPCA`)
		})

		t.Run("FactoryCase", func(t *testing.T) {
			fileContains(t, "internal/connector/issuerfactory/factory.go", `"AWSACMPCA"`)
		})

		t.Run("ConfigStruct", func(t *testing.T) {
			fileContains(t, "internal/config/config.go", `AWSACMPCAConfig`)
		})

		t.Run("EnvVarSeed", func(t *testing.T) {
			fileContains(t, "internal/service/issuer.go", `iss-awsacmpca`)
		})

		t.Run("FrontendIssuerType", func(t *testing.T) {
			fileContains(t, "web/src/config/issuerTypes.ts", `AWSACMPCA`)
		})

		t.Run("OpenAPIEnum", func(t *testing.T) {
			fileContains(t, "api/openapi.yaml", `AWSACMPCA`)
		})

		t.Run("SeedDemoData", func(t *testing.T) {
			fileContains(t, "migrations/seed_demo.sql", `iss-awsacmpca`)
		})
	})
}

// Note: uses Go 1.21+ built-in min() — no custom definition needed.
