package integration

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"io"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/shankar0123/certctl/internal/domain"
)

// TestStatsAndMetricsEndpoints exercises the M14 observability endpoints end-to-end.
func TestStatsAndMetricsEndpoints(t *testing.T) {
	server, _, _, _ := setupTestServer(t)

	t.Run("GetHealth", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/health")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
		var body map[string]string
		json.NewDecoder(resp.Body).Decode(&body)
		if body["status"] != "healthy" {
			t.Errorf("expected status=healthy, got %s", body["status"])
		}
	})

	t.Run("GetReady", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/ready")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
	})

	t.Run("GetMetrics", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/metrics")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var metrics map[string]interface{}
		json.NewDecoder(resp.Body).Decode(&metrics)
		if metrics["gauge"] == nil {
			t.Error("expected gauge in metrics response")
		}
		if metrics["counter"] == nil {
			t.Error("expected counter in metrics response")
		}
		if metrics["uptime"] == nil {
			t.Error("expected uptime in metrics response")
		}
	})

	t.Run("GetStatsSummary", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/stats/summary")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
	})

	t.Run("GetCertificatesByStatus", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/stats/certificates-by-status")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
	})

	t.Run("GetExpirationTimeline", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/stats/expiration-timeline?days=90")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
	})

	t.Run("GetExpirationTimeline_DefaultDays", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/stats/expiration-timeline")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
	})

	t.Run("GetJobTrends", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/stats/job-trends?days=30")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
	})

	t.Run("GetIssuanceRate", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/stats/issuance-rate?days=30")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
	})
}

// TestCrossResourceWorkflow exercises a multi-step workflow spanning certificates,
// policies, agents, jobs, audit trail, and notifications — verifying data flows
// correctly across service boundaries.
func TestCrossResourceWorkflow(t *testing.T) {
	server, certRepo, jobRepo, agentRepo := setupTestServer(t)

	// Step 1: Create a policy rule
	var policyID string
	t.Run("CreatePolicy", func(t *testing.T) {
		payload := map[string]interface{}{
			"name":        "Allowed Domains Policy",
			"type":        "AllowedDomains",
			"severity":    "High",
			"config":      json.RawMessage(`{"domains": ["example.com", "*.example.com"]}`),
			"description": "Restrict issuance to example.com domains",
		}
		body, _ := json.Marshal(payload)
		resp, err := http.Post(server.URL+"/api/v1/policies", "application/json", bytes.NewReader(body))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusCreated {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var rule domain.PolicyRule
		json.NewDecoder(resp.Body).Decode(&rule)
		policyID = rule.ID
		if policyID == "" {
			t.Fatal("expected policy ID")
		}
		t.Logf("Created policy: %s", policyID)
	})

	// Step 2: Create a certificate
	var certID string
	t.Run("CreateCertificate", func(t *testing.T) {
		now := time.Now()
		payload := map[string]interface{}{
			"name":              "Workflow Test Cert",
			"common_name":       "workflow.example.com",
			"sans":              []string{"www.workflow.example.com"},
			"environment":       "staging",
			"owner_id":          "owner-ops",
			"team_id":           "team-platform",
			"issuer_id":         "iss-local",
			"target_ids":        []string{},
			"renewal_policy_id": "policy-standard",
			"status":            "Pending",
			"expires_at":        now.AddDate(0, 3, 0),
			"tags":              map[string]string{"team": "platform"},
		}
		body, _ := json.Marshal(payload)
		resp, err := http.Post(server.URL+"/api/v1/certificates", "application/json", bytes.NewReader(body))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusCreated {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var cert domain.ManagedCertificate
		json.NewDecoder(resp.Body).Decode(&cert)
		certID = cert.ID
		t.Logf("Created certificate: %s", certID)
	})

	// Step 3: Register an agent
	var agentID string
	t.Run("RegisterAgent", func(t *testing.T) {
		payload := map[string]string{"name": "workflow-agent", "hostname": "workflow-host-01"}
		body, _ := json.Marshal(payload)
		resp, err := http.Post(server.URL+"/api/v1/agents", "application/json", bytes.NewReader(body))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusCreated {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var agent domain.Agent
		json.NewDecoder(resp.Body).Decode(&agent)
		agentID = agent.ID
		t.Logf("Registered agent: %s", agentID)
	})

	// Step 4: Trigger renewal
	t.Run("TriggerRenewal", func(t *testing.T) {
		resp, err := http.Post(server.URL+"/api/v1/certificates/"+certID+"/renew", "application/json", nil)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusAccepted {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 202, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
	})

	// Step 5: Verify jobs were created
	t.Run("VerifyJobsCreated", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/jobs?page=1&per_page=50")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var respBody map[string]interface{}
		json.NewDecoder(resp.Body).Decode(&respBody)
		// data may be null (nil) if no jobs exist, or an array
		if data, ok := respBody["data"].([]interface{}); ok && len(data) > 0 {
			t.Logf("Found %d jobs after renewal trigger", len(data))
		} else {
			t.Log("No jobs found after renewal trigger (expected — mock TriggerRenewal is async/no-op)")
		}
	})

	// Step 6: Agent heartbeat with metadata
	t.Run("AgentHeartbeatWithMetadata", func(t *testing.T) {
		payload := map[string]interface{}{
			"os":           "linux",
			"architecture": "amd64",
			"ip_address":   "10.0.1.50",
			"version":      "1.0.0",
		}
		body, _ := json.Marshal(payload)
		resp, err := http.Post(server.URL+"/api/v1/agents/"+agentID+"/heartbeat", "application/json", bytes.NewReader(body))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}

		// Verify metadata was stored
		agent, ok := agentRepo.agents[agentID]
		if !ok {
			t.Fatal("agent not found in repo after heartbeat")
		}
		if agent.LastHeartbeatAt == nil {
			t.Error("expected heartbeat timestamp to be set")
		}
	})

	// Step 7: Add a version to the cert so revocation works
	t.Run("AddCertVersion", func(t *testing.T) {
		now := time.Now()
		certRepo.versions[certID] = []*domain.CertificateVersion{
			{
				ID:            "cv-workflow-1",
				CertificateID: certID,
				SerialNumber:  "WORKFLOW-SERIAL-001",
				NotBefore:     now,
				NotAfter:      now.AddDate(0, 3, 0),
				CreatedAt:     now,
			},
		}
		// Update cert status to Active for revocation
		if cert, ok := certRepo.certs[certID]; ok {
			cert.Status = domain.CertificateStatusActive
		}
	})

	// Step 8: Revoke the certificate
	t.Run("RevokeCertificate", func(t *testing.T) {
		body := bytes.NewBufferString(`{"reason":"cessationOfOperation"}`)
		resp, err := http.Post(server.URL+"/api/v1/certificates/"+certID+"/revoke", "application/json", body)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}

		// Verify cert status changed to Revoked
		cert := certRepo.certs[certID]
		if cert.Status != domain.CertificateStatusRevoked {
			t.Errorf("expected Revoked status, got %s", cert.Status)
		}
	})

	// Step 9: Verify revoked cert cannot be renewed
	t.Run("CannotRenewRevoked", func(t *testing.T) {
		resp, err := http.Post(server.URL+"/api/v1/certificates/"+certID+"/renew", "application/json", nil)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		// Revoked cert should not accept renewal (expect error status)
		if resp.StatusCode == http.StatusAccepted {
			t.Log("Warning: revoked cert accepted renewal — may need business logic enforcement")
		}
	})

	// Step 10: Verify audit trail accumulated events
	t.Run("AuditTrailAccumulated", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/audit?page=1&per_page=100")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}
		var respBody map[string]interface{}
		json.NewDecoder(resp.Body).Decode(&respBody)
		data, ok := respBody["data"].([]interface{})
		if !ok {
			t.Fatal("expected data array")
		}
		// We should have at least cert_created, agent_registered, renewal_triggered, cert_revoked
		if len(data) < 3 {
			t.Logf("Warning: expected at least 3 audit events, got %d", len(data))
		}
		t.Logf("Total audit events from workflow: %d", len(data))

		// Verify event types
		eventTypes := make(map[string]int)
		for _, evt := range data {
			if eventMap, ok := evt.(map[string]interface{}); ok {
				if action, ok := eventMap["action"].(string); ok {
					eventTypes[action]++
				}
			}
		}
		t.Logf("Audit event types: %v", eventTypes)
	})

	// Summary
	t.Run("WorkflowSummary", func(t *testing.T) {
		certCount := len(certRepo.certs)
		jobCount := len(jobRepo.jobs)
		agentCount := len(agentRepo.agents)
		t.Logf("Cross-resource workflow complete: %d certs, %d jobs, %d agents", certCount, jobCount, agentCount)
	})
}

// TestJobApprovalWorkflow exercises the interactive approval flow (M11b).
func TestJobApprovalWorkflow(t *testing.T) {
	server, _, jobRepo, _ := setupTestServer(t)

	// Seed a job in AwaitingApproval state
	jobID := "job-approval-test-1"
	jobRepo.jobs[jobID] = &domain.Job{
		ID:            jobID,
		CertificateID: "mc-test",
		Type:          domain.JobTypeRenewal,
		Status:        domain.JobStatusAwaitingApproval,
		MaxAttempts:   3,
		Attempts:      0,
		CreatedAt:     time.Now(),
	}

	t.Run("ApproveJob_Success", func(t *testing.T) {
		payload := map[string]string{"reason": "Approved by ops team"}
		body, _ := json.Marshal(payload)
		req, _ := http.NewRequest(http.MethodPost, server.URL+"/api/v1/jobs/"+jobID+"/approve", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}

		// Verify job moved to Pending
		job := jobRepo.jobs[jobID]
		if job.Status != domain.JobStatusPending {
			t.Errorf("expected Pending after approval, got %s", job.Status)
		}
	})

	// Seed another job for rejection
	rejectJobID := "job-reject-test-1"
	jobRepo.jobs[rejectJobID] = &domain.Job{
		ID:            rejectJobID,
		CertificateID: "mc-test",
		Type:          domain.JobTypeRenewal,
		Status:        domain.JobStatusAwaitingApproval,
		MaxAttempts:   3,
		Attempts:      0,
		CreatedAt:     time.Now(),
	}

	t.Run("RejectJob_Success", func(t *testing.T) {
		payload := map[string]string{"reason": "Certificate no longer needed"}
		body, _ := json.Marshal(payload)
		req, _ := http.NewRequest(http.MethodPost, server.URL+"/api/v1/jobs/"+rejectJobID+"/reject", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}

		// Verify job moved to Cancelled
		job := jobRepo.jobs[rejectJobID]
		if job.Status != domain.JobStatusCancelled {
			t.Errorf("expected Cancelled after rejection, got %s", job.Status)
		}
	})

	t.Run("ApproveNonexistentJob", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodPost, server.URL+"/api/v1/jobs/job-ghost/approve", bytes.NewReader([]byte("{}")))
		req.Header.Set("Content-Type", "application/json")
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusNotFound {
			t.Errorf("expected 404, got %d", resp.StatusCode)
		}
	})

	t.Run("ApproveNonAwaitingJob", func(t *testing.T) {
		// The first job is already Pending (approved earlier) — approving again should fail
		req, _ := http.NewRequest(http.MethodPost, server.URL+"/api/v1/jobs/"+jobID+"/approve", bytes.NewReader([]byte("{}")))
		req.Header.Set("Content-Type", "application/json")
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode == http.StatusOK {
			t.Error("expected error when approving non-AwaitingApproval job")
		}
	})
}

// TestNotificationEndpoints exercises the M3 notification API.
func TestNotificationEndpoints(t *testing.T) {
	server, _, _, _ := setupTestServer(t)

	t.Run("ListNotifications_Empty", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/notifications?page=1&per_page=10")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
		var respBody map[string]interface{}
		json.NewDecoder(resp.Body).Decode(&respBody)
		total, ok := respBody["total"].(float64)
		if !ok {
			t.Log("Warning: total field not found or not a number")
		} else if total != 0 {
			t.Logf("Found %d notifications (expected 0 on fresh setup)", int(total))
		}
	})
}

// TestCRLEndpoint exercises the CRL listing endpoint (M15a).
func TestCRLEndpoint(t *testing.T) {
	server, _, _, _ := setupTestServer(t)

	t.Run("GetCRL_JSON", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/crl")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var crl map[string]interface{}
		json.NewDecoder(resp.Body).Decode(&crl)
		if crl["version"] == nil {
			t.Error("expected version field in CRL response")
		}
		if crl["entries"] == nil {
			t.Error("expected entries field in CRL response")
		}
		t.Logf("CRL response: version=%v, entries_count=%v", crl["version"], crl["total"])
	})
}

// TestPaginationAcrossEndpoints verifies pagination parameters work consistently.
func TestPaginationAcrossEndpoints(t *testing.T) {
	server, _, _, _ := setupTestServer(t)

	endpoints := []struct {
		name string
		url  string
	}{
		{"Certificates", "/api/v1/certificates?page=1&per_page=5"},
		{"Agents", "/api/v1/agents?page=1&per_page=5"},
		{"Jobs", "/api/v1/jobs?page=1&per_page=5"},
		{"Audit", "/api/v1/audit?page=1&per_page=5"},
		{"Notifications", "/api/v1/notifications?page=1&per_page=5"},
		{"Policies", "/api/v1/policies?page=1&per_page=5"},
	}

	for _, ep := range endpoints {
		t.Run(ep.name, func(t *testing.T) {
			resp, err := http.Get(server.URL + ep.url)
			if err != nil {
				t.Fatalf("request failed: %v", err)
			}
			defer resp.Body.Close()
			if resp.StatusCode != http.StatusOK {
				bodyBytes, _ := io.ReadAll(resp.Body)
				t.Errorf("expected 200 for %s, got %d: %s", ep.name, resp.StatusCode, string(bodyBytes))
			}
		})
	}
}

// TestIssuerAndTargetCRUD exercises issuer and target CRUD lifecycle.
func TestIssuerAndTargetCRUD(t *testing.T) {
	server, _, _, _ := setupTestServer(t)

	// Issuer CRUD
	var issuerID string
	t.Run("CreateIssuer", func(t *testing.T) {
		payload := map[string]interface{}{
			"id":     "iss-test-ca",
			"name":   "Test Local CA",
			"type":   "GenericCA",
			"config": json.RawMessage(`{"ca_common_name": "Test CA"}`),
		}
		body, _ := json.Marshal(payload)
		resp, err := http.Post(server.URL+"/api/v1/issuers", "application/json", bytes.NewReader(body))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusCreated {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var issuer domain.Issuer
		json.NewDecoder(resp.Body).Decode(&issuer)
		issuerID = issuer.ID
		t.Logf("Created issuer: %s", issuerID)
	})

	t.Run("GetIssuer", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/issuers/" + issuerID)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
	})

	t.Run("ListIssuers", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/issuers")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
	})

	// Target CRUD
	var targetID string
	t.Run("CreateTarget", func(t *testing.T) {
		payload := map[string]interface{}{
			"id":       "t-test-nginx",
			"name":     "Test NGINX",
			"type":     "NGINX",
			"agent_id": "agent-1",
			"config":   json.RawMessage(`{"cert_path": "/etc/nginx/ssl/cert.pem"}`),
		}
		body, _ := json.Marshal(payload)
		resp, err := http.Post(server.URL+"/api/v1/targets", "application/json", bytes.NewReader(body))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusCreated {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var target domain.DeploymentTarget
		json.NewDecoder(resp.Body).Decode(&target)
		targetID = target.ID
		t.Logf("Created target: %s", targetID)
	})

	t.Run("GetTarget", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/targets/" + targetID)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
	})

	t.Run("DeleteTarget", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodDelete, server.URL+"/api/v1/targets/"+targetID, nil)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		// Accept either 200 or 204
		if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
			t.Errorf("expected 200 or 204, got %d", resp.StatusCode)
		}
	})

	t.Run("DeleteIssuer", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodDelete, server.URL+"/api/v1/issuers/"+issuerID, nil)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
			t.Errorf("expected 200 or 204, got %d", resp.StatusCode)
		}
	})
}

// TestM20EnhancedQueryAPI exercises M20 query API enhancements: sorting, time-range filters,
// cursor pagination, sparse fields, profile/agent filters, and the deployments endpoint.
func TestM20EnhancedQueryAPI(t *testing.T) {
	server, certRepo, _, _ := setupTestServer(t)

	// Setup: Create a certificate for testing
	now := time.Now()
	cert := &domain.ManagedCertificate{
		ID:          "mc-m20-test-1",
		Name:        "M20 Test Cert",
		CommonName:  "m20.example.com",
		Environment: "production",
		Status:      domain.CertificateStatusActive,
		IssuerID:    "iss-local",
		OwnerID:     "owner-ops",
		TeamID:      "team-platform",
		CertificateProfileID: "prof-standard",
		CreatedAt:   now,
		UpdatedAt:   now,
	}
	certRepo.certs["mc-m20-test-1"] = cert

	t.Run("ListWithSortDescending", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/certificates?sort=-notAfter&page=1&per_page=10")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var respBody map[string]interface{}
		json.NewDecoder(resp.Body).Decode(&respBody)
		if _, ok := respBody["data"]; !ok {
			t.Error("expected data field in response")
		}
	})

	t.Run("ListWithSortAscending", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/certificates?sort=createdAt&page=1&per_page=10")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
		var respBody map[string]interface{}
		json.NewDecoder(resp.Body).Decode(&respBody)
		if _, ok := respBody["page"]; !ok {
			t.Error("expected page-based pagination response")
		}
	})

	t.Run("TimeRangeFilter_ExpiresBefore", func(t *testing.T) {
		future := now.AddDate(0, 0, 365).Format(time.RFC3339)
		resp, err := http.Get(server.URL + "/api/v1/certificates?expires_before=" + future)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Errorf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
	})

	t.Run("TimeRangeFilter_ExpiresAfter", func(t *testing.T) {
		past := now.AddDate(0, 0, -90).Format(time.RFC3339)
		resp, err := http.Get(server.URL + "/api/v1/certificates?expires_after=" + past)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
	})

	t.Run("TimeRangeFilter_CreatedAfter", func(t *testing.T) {
		past := now.AddDate(-1, 0, 0).Format(time.RFC3339)
		resp, err := http.Get(server.URL + "/api/v1/certificates?created_after=" + past)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
	})

	t.Run("SparseFields", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/certificates?fields=id,common_name,status")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Errorf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var respBody map[string]interface{}
		json.NewDecoder(resp.Body).Decode(&respBody)
		if data, ok := respBody["data"].([]interface{}); ok && len(data) > 0 {
			firstCert, ok := data[0].(map[string]interface{})
			if !ok {
				t.Fatal("expected cert object in data array")
			}
			// Should have requested fields
			if _, ok := firstCert["id"]; !ok {
				t.Error("expected 'id' field in sparse response")
			}
			// Should NOT have unrequested fields like 'environment'
			if _, ok := firstCert["environment"]; ok {
				t.Error("did not expect 'environment' field in sparse response")
			}
		}
	})

	t.Run("ProfileFilter", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/certificates?profile_id=prof-standard")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
	})

	t.Run("AgentIDFilter", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/certificates?agent_id=agent-prod-001")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
	})

	t.Run("CursorPagination", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/certificates?cursor=abc123&page_size=10")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
		var respBody map[string]interface{}
		json.NewDecoder(resp.Body).Decode(&respBody)
		if _, ok := respBody["next_cursor"]; !ok {
			t.Error("expected next_cursor field with cursor pagination")
		}
	})

	t.Run("CombinedFilters", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/certificates?status=Active&environment=production&profile_id=prof-standard&sort=-createdAt&per_page=10")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200, got %d", resp.StatusCode)
		}
	})

	t.Run("GetCertificateDeployments_Success", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/certificates/mc-m20-test-1/deployments")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Errorf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		var respBody map[string]interface{}
		json.NewDecoder(resp.Body).Decode(&respBody)
		if _, ok := respBody["data"]; !ok {
			t.Error("expected data field in response")
		}
		if _, ok := respBody["total"]; !ok {
			t.Error("expected total field in response")
		}
	})

	t.Run("GetCertificateDeployments_NotFound", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/certificates/mc-nonexistent-m20/deployments")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusNotFound {
			t.Errorf("expected 404, got %d", resp.StatusCode)
		}
	})

	t.Run("InvalidTimeRange", func(t *testing.T) {
		// Invalid RFC3339 should be silently ignored (no filter applied)
		resp, err := http.Get(server.URL + "/api/v1/certificates?expires_before=not-a-date&page=1&per_page=10")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			t.Errorf("expected 200 (invalid time ignored), got %d", resp.StatusCode)
		}
	})
}

// generateE2ECSRPEM creates a valid ECDSA P-256 CSR PEM for integration testing.
func generateE2ECSRPEM(t *testing.T, cn string, sans []string) string {
	t.Helper()
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("generate key: %v", err)
	}
	template := &x509.CertificateRequest{
		Subject:  pkix.Name{CommonName: cn},
		DNSNames: sans,
	}
	csrDER, err := x509.CreateCertificateRequest(rand.Reader, template, key)
	if err != nil {
		t.Fatalf("create CSR: %v", err)
	}
	return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER}))
}

// generateE2ECSRBase64DER creates a valid base64-encoded DER CSR for EST wire format testing.
func generateE2ECSRBase64DER(t *testing.T, cn string, sans []string) string {
	t.Helper()
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("generate key: %v", err)
	}
	template := &x509.CertificateRequest{
		Subject:  pkix.Name{CommonName: cn},
		DNSNames: sans,
	}
	csrDER, err := x509.CreateCertificateRequest(rand.Reader, template, key)
	if err != nil {
		t.Fatalf("create CSR: %v", err)
	}
	return base64.StdEncoding.EncodeToString(csrDER)
}

// TestPrometheusMetrics exercises the Prometheus metrics endpoint (M22).
func TestPrometheusMetrics(t *testing.T) {
	server, _, _, _ := setupTestServer(t)

	t.Run("GetPrometheusMetrics_Success", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/api/v1/metrics/prometheus")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}

		// Verify Content-Type contains text/plain
		contentType := resp.Header.Get("Content-Type")
		if !strings.Contains(contentType, "text/plain") {
			t.Errorf("expected Content-Type containing 'text/plain', got %s", contentType)
		}

		// Read and verify Prometheus format
		body, _ := io.ReadAll(resp.Body)
		bodyStr := string(body)

		// Should contain HELP and TYPE lines for metrics
		if !strings.Contains(bodyStr, "# HELP") {
			t.Error("expected HELP line in Prometheus response")
		}
		if !strings.Contains(bodyStr, "# TYPE") {
			t.Error("expected TYPE line in Prometheus response")
		}

		// Should contain metric lines (gauge, counter, uptime)
		if !strings.Contains(bodyStr, "certctl_") {
			t.Error("expected certctl_ prefixed metrics in response")
		}

		t.Logf("Prometheus metrics endpoint working, body size: %d bytes", len(bodyStr))
	})

	t.Run("GetPrometheusMetrics_MethodNotAllowed", func(t *testing.T) {
		resp, err := http.Post(server.URL+"/api/v1/metrics/prometheus", "application/json", nil)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusMethodNotAllowed {
			t.Errorf("expected 405, got %d", resp.StatusCode)
		}
	})
}

// TestESTEndpoints exercises the EST (RFC 7030) enrollment endpoints end-to-end (M23).
func TestESTEndpoints(t *testing.T) {
	server, _, _, _ := setupTestServer(t)

	// ===========================
	// GET /cacerts — CA certificate chain
	// ===========================
	t.Run("GetCACerts_Success", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/.well-known/est/cacerts")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		ct := resp.Header.Get("Content-Type")
		if !strings.Contains(ct, "application/pkcs7-mime") {
			t.Errorf("expected application/pkcs7-mime content type, got %s", ct)
		}
		cte := resp.Header.Get("Content-Transfer-Encoding")
		if cte != "base64" {
			t.Errorf("expected base64 content-transfer-encoding, got %s", cte)
		}
		bodyBytes, _ := io.ReadAll(resp.Body)
		if len(bodyBytes) == 0 {
			t.Error("expected non-empty PKCS#7 response body")
		}
	})

	t.Run("GetCACerts_MethodNotAllowed", func(t *testing.T) {
		resp, err := http.Post(server.URL+"/.well-known/est/cacerts", "application/json", nil)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusMethodNotAllowed {
			t.Errorf("expected 405, got %d", resp.StatusCode)
		}
	})

	// ===========================
	// POST /simpleenroll — certificate enrollment
	// ===========================
	t.Run("SimpleEnroll_PEM_Success", func(t *testing.T) {
		csrPEM := generateE2ECSRPEM(t, "est-test.example.com", []string{"est-test.example.com"})
		resp, err := http.Post(server.URL+"/.well-known/est/simpleenroll", "application/pkcs10", strings.NewReader(csrPEM))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
		ct := resp.Header.Get("Content-Type")
		if !strings.Contains(ct, "application/pkcs7-mime") {
			t.Errorf("expected application/pkcs7-mime, got %s", ct)
		}
	})

	t.Run("SimpleEnroll_Base64DER_Success", func(t *testing.T) {
		csrB64 := generateE2ECSRBase64DER(t, "est-der.example.com", []string{"est-der.example.com"})
		resp, err := http.Post(server.URL+"/.well-known/est/simpleenroll", "application/pkcs10", strings.NewReader(csrB64))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
	})

	t.Run("SimpleEnroll_EmptyBody", func(t *testing.T) {
		resp, err := http.Post(server.URL+"/.well-known/est/simpleenroll", "application/pkcs10", strings.NewReader(""))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusBadRequest {
			t.Errorf("expected 400 for empty body, got %d", resp.StatusCode)
		}
	})

	t.Run("SimpleEnroll_InvalidCSR", func(t *testing.T) {
		resp, err := http.Post(server.URL+"/.well-known/est/simpleenroll", "application/pkcs10", strings.NewReader("not-a-valid-csr"))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusBadRequest {
			t.Errorf("expected 400 for invalid CSR, got %d", resp.StatusCode)
		}
	})

	t.Run("SimpleEnroll_MissingCN", func(t *testing.T) {
		csrPEM := generateE2ECSRPEM(t, "", []string{"no-cn.example.com"})
		resp, err := http.Post(server.URL+"/.well-known/est/simpleenroll", "application/pkcs10", strings.NewReader(csrPEM))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		// Should fail because EST requires a Common Name
		if resp.StatusCode == http.StatusOK {
			t.Error("expected error for CSR without Common Name")
		}
	})

	t.Run("SimpleEnroll_MethodNotAllowed", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/.well-known/est/simpleenroll")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusMethodNotAllowed {
			t.Errorf("expected 405, got %d", resp.StatusCode)
		}
	})

	// ===========================
	// POST /simplereenroll — certificate re-enrollment
	// ===========================
	t.Run("SimpleReEnroll_Success", func(t *testing.T) {
		csrPEM := generateE2ECSRPEM(t, "renew-est.example.com", []string{"renew-est.example.com"})
		resp, err := http.Post(server.URL+"/.well-known/est/simplereenroll", "application/pkcs10", strings.NewReader(csrPEM))
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			bodyBytes, _ := io.ReadAll(resp.Body)
			t.Fatalf("expected 200, got %d: %s", resp.StatusCode, string(bodyBytes))
		}
	})

	t.Run("SimpleReEnroll_MethodNotAllowed", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/.well-known/est/simplereenroll")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusMethodNotAllowed {
			t.Errorf("expected 405, got %d", resp.StatusCode)
		}
	})

	// ===========================
	// GET /csrattrs — CSR attributes
	// ===========================
	t.Run("GetCSRAttrs_NoContent", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/.well-known/est/csrattrs")
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		// Default implementation returns nil attrs → 204 No Content
		if resp.StatusCode != http.StatusNoContent {
			t.Errorf("expected 204, got %d", resp.StatusCode)
		}
	})

	t.Run("GetCSRAttrs_MethodNotAllowed", func(t *testing.T) {
		resp, err := http.Post(server.URL+"/.well-known/est/csrattrs", "application/json", nil)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusMethodNotAllowed {
			t.Errorf("expected 405, got %d", resp.StatusCode)
		}
	})
}
