package cli

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"encoding/pem"
	"math/big"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"
	"time"
)

func TestClient_ListCertificates(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" || r.URL.Path != "/api/v1/certificates" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"data": []map[string]interface{}{
				{
					"id":          "mc-1",
					"common_name": "example.com",
					"status":      "Active",
					"expires_at":  "2025-12-31T00:00:00Z",
					"issuer_id":   "iss-local",
				},
			},
			"total": 1,
		})
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "table", "", false)
	err := client.ListCertificates([]string{})
	if err != nil {
		t.Fatalf("ListCertificates failed: %v", err)
	}
}

func TestClient_GetCertificate(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" || r.URL.Path != "/api/v1/certificates/mc-1" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"id":          "mc-1",
			"common_name": "example.com",
			"status":      "Active",
			"expires_at":  "2025-12-31T00:00:00Z",
			"issuer_id":   "iss-local",
		})
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "json", "", false)
	err := client.GetCertificate("mc-1")
	if err != nil {
		t.Fatalf("GetCertificate failed: %v", err)
	}
}

func TestClient_RenewCertificate(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" || r.URL.Path != "/api/v1/certificates/mc-1/renew" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"job_id": "job-123",
			"status": "Pending",
		})
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "table", "", false)
	err := client.RenewCertificate("mc-1")
	if err != nil {
		t.Fatalf("RenewCertificate failed: %v", err)
	}
}

func TestClient_RevokeCertificate(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" || r.URL.Path != "/api/v1/certificates/mc-1/revoke" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"status": "revoked",
		})
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "table", "", false)
	err := client.RevokeCertificate("mc-1", "cessationOfOperation")
	if err != nil {
		t.Fatalf("RevokeCertificate failed: %v", err)
	}
}

func TestClient_BulkRevokeCertificates(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" || r.URL.Path != "/api/v1/certificates/bulk-revoke" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		// Verify request body contains expected fields
		var body map[string]interface{}
		json.NewDecoder(r.Body).Decode(&body)
		if body["reason"] != "keyCompromise" {
			t.Errorf("expected reason keyCompromise, got %v", body["reason"])
		}
		if body["profile_id"] != "prof-tls" {
			t.Errorf("expected profile_id prof-tls, got %v", body["profile_id"])
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"total_matched": 3,
			"total_revoked": 2,
			"total_skipped": 1,
			"total_failed":  0,
		})
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "table", "", false)
	err := client.BulkRevokeCertificates([]string{
		"--reason", "keyCompromise",
		"--profile-id", "prof-tls",
	})
	if err != nil {
		t.Fatalf("BulkRevokeCertificates failed: %v", err)
	}
}

func TestClient_ListAgents(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" || r.URL.Path != "/api/v1/agents" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"data": []map[string]interface{}{
				{
					"id":           "ag-1",
					"hostname":     "agent1.example.com",
					"status":       "Online",
					"os":           "linux",
					"architecture": "amd64",
					"ip_address":   "192.168.1.1",
				},
			},
			"total": 1,
		})
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "table", "", false)
	err := client.ListAgents([]string{})
	if err != nil {
		t.Fatalf("ListAgents failed: %v", err)
	}
}

func TestClient_GetAgent(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" || r.URL.Path != "/api/v1/agents/ag-1" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"id":           "ag-1",
			"hostname":     "agent1.example.com",
			"status":       "Online",
			"os":           "linux",
			"architecture": "amd64",
			"ip_address":   "192.168.1.1",
		})
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "json", "", false)
	err := client.GetAgent("ag-1")
	if err != nil {
		t.Fatalf("GetAgent failed: %v", err)
	}
}

func TestClient_ListJobs(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" || r.URL.Path != "/api/v1/jobs" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"data": []map[string]interface{}{
				{
					"id":             "job-1",
					"type":           "Renewal",
					"certificate_id": "mc-1",
					"status":         "Completed",
					"attempts":       1,
					"max_attempts":   3,
				},
			},
			"total": 1,
		})
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "table", "", false)
	err := client.ListJobs([]string{})
	if err != nil {
		t.Fatalf("ListJobs failed: %v", err)
	}
}

func TestClient_GetJob(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" || r.URL.Path != "/api/v1/jobs/job-1" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"id":             "job-1",
			"type":           "Renewal",
			"certificate_id": "mc-1",
			"status":         "Completed",
			"attempts":       1,
			"max_attempts":   3,
		})
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "json", "", false)
	err := client.GetJob("job-1")
	if err != nil {
		t.Fatalf("GetJob failed: %v", err)
	}
}

func TestClient_CancelJob(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" || r.URL.Path != "/api/v1/jobs/job-1/cancel" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "table", "", false)
	err := client.CancelJob("job-1")
	if err != nil {
		t.Fatalf("CancelJob failed: %v", err)
	}
}

func TestClient_GetStatus(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/json")

		if r.URL.Path == "/api/v1/health" {
			json.NewEncoder(w).Encode(map[string]interface{}{
				"status":    "healthy",
				"timestamp": time.Now().Format(time.RFC3339),
			})
		} else if r.URL.Path == "/api/v1/stats/summary" {
			json.NewEncoder(w).Encode(map[string]interface{}{
				"data": map[string]interface{}{
					"total_certificates": 10,
					"total_agents":       5,
				},
			})
		}
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "table", "", false)
	err := client.GetStatus()
	if err != nil {
		t.Fatalf("GetStatus failed: %v", err)
	}
}

func TestParsePEMCertificates(t *testing.T) {
	// Generate a self-signed test certificate
	cert := generateTestCert()

	// Encode it to PEM
	pemBlock := &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cert.Raw,
	}
	pemData := pem.EncodeToMemory(pemBlock)

	// Parse it back
	certs, err := parsePEMCertificates(pemData)
	if err != nil {
		t.Fatalf("parsePEMCertificates failed: %v", err)
	}

	if len(certs) != 1 {
		t.Fatalf("expected 1 certificate, got %d", len(certs))
	}

	if certs[0].Subject.CommonName != "test.example.com" {
		t.Fatalf("expected CommonName 'test.example.com', got %s", certs[0].Subject.CommonName)
	}
}

func TestParsePEMCertificates_Multiple(t *testing.T) {
	// Generate two test certificates
	cert1 := generateTestCert()
	cert2 := generateTestCert()

	// Encode both to PEM
	block1 := &pem.Block{Type: "CERTIFICATE", Bytes: cert1.Raw}
	block2 := &pem.Block{Type: "CERTIFICATE", Bytes: cert2.Raw}

	pemData := append(pem.EncodeToMemory(block1), pem.EncodeToMemory(block2)...)

	// Parse them back
	certs, err := parsePEMCertificates(pemData)
	if err != nil {
		t.Fatalf("parsePEMCertificates failed: %v", err)
	}

	if len(certs) != 2 {
		t.Fatalf("expected 2 certificates, got %d", len(certs))
	}
}

func TestParsePEMCertificates_NoCertificates(t *testing.T) {
	pemData := []byte("no certificates here")

	_, err := parsePEMCertificates(pemData)
	if err == nil {
		t.Fatal("expected error for empty PEM data")
	}
}

func TestClient_AuthHeader(t *testing.T) {
	var authHeader string
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		authHeader = r.Header.Get("Authorization")
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{"data": []interface{}{}})
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "testkey123", "json", "", false)
	client.do("GET", "/api/v1/certificates", nil, nil)

	if authHeader != "Bearer testkey123" {
		t.Fatalf("expected 'Bearer testkey123', got '%s'", authHeader)
	}
}

// TestClient_ImportCertificates_MissingRequiredFlags verifies the CLI
// import command rejects invocations missing any of the four required
// flags (--owner-id, --team-id, --renewal-policy-id, --issuer-id)
// before any network call is attempted. This is the C-001 scope-expansion
// closure for the CLI layer: the handler now requires all six cert
// fields, so the importer must collect ownership / team / policy /
// issuer up front rather than hard-coding iss-local and letting the
// server 400 on every POST.
func TestClient_ImportCertificates_MissingRequiredFlags(t *testing.T) {
	var requestCount int
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requestCount++
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	cases := []struct {
		name    string
		args    []string
		missing string
	}{
		{
			name:    "missing owner-id",
			args:    []string{"--team-id", "t-platform", "--renewal-policy-id", "rp-default", "--issuer-id", "iss-local", "certs.pem"},
			missing: "--owner-id",
		},
		{
			name:    "missing team-id",
			args:    []string{"--owner-id", "o-alice", "--renewal-policy-id", "rp-default", "--issuer-id", "iss-local", "certs.pem"},
			missing: "--team-id",
		},
		{
			name:    "missing renewal-policy-id",
			args:    []string{"--owner-id", "o-alice", "--team-id", "t-platform", "--issuer-id", "iss-local", "certs.pem"},
			missing: "--renewal-policy-id",
		},
		{
			name:    "missing issuer-id",
			args:    []string{"--owner-id", "o-alice", "--team-id", "t-platform", "--renewal-policy-id", "rp-default", "certs.pem"},
			missing: "--issuer-id",
		},
		{
			name:    "no flags at all",
			args:    []string{"certs.pem"},
			missing: "--owner-id",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			client, _ := NewClient(server.URL, "", "table", "", false)
			err := client.ImportCertificates(tc.args)
			if err == nil {
				t.Fatalf("expected error for %s, got nil", tc.name)
			}
			msg := err.Error()
			if !containsStr(msg, tc.missing) {
				t.Fatalf("expected error to name %q, got: %v", tc.missing, err)
			}
			if !containsStr(msg, "required") {
				t.Fatalf("expected error message to mention 'required', got: %v", err)
			}
		})
	}

	if requestCount != 0 {
		t.Fatalf("expected zero HTTP requests before flag validation, got %d", requestCount)
	}
}

// TestClient_ImportCertificates_MissingPositionalArgs verifies the
// import command errors out when flags are present but no PEM file
// paths follow them.
func TestClient_ImportCertificates_MissingPositionalArgs(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Errorf("unexpected HTTP request: %s %s", r.Method, r.URL.Path)
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "table", "", false)
	err := client.ImportCertificates([]string{
		"--owner-id", "o-alice",
		"--team-id", "t-platform",
		"--renewal-policy-id", "rp-default",
		"--issuer-id", "iss-local",
	})
	if err == nil {
		t.Fatal("expected error when no PEM file paths are supplied")
	}
	if !containsStr(err.Error(), "PEM file") {
		t.Fatalf("expected error to mention 'PEM file', got: %v", err)
	}
}

// TestClient_ImportCertificates_SixFieldPayload verifies the happy
// path: given all four required flags plus a PEM file, the importer
// POSTs a request containing all six required fields plus the
// name-template–resolved name. The httptest handler decodes the
// request body and asserts every required field is populated with
// the values supplied via flags.
func TestClient_ImportCertificates_SixFieldPayload(t *testing.T) {
	// Generate a test cert and write it to a temp PEM file.
	cert := generateTestCert()
	pemBlock := &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
	pemPath := filepath.Join(t.TempDir(), "test.pem")
	if err := os.WriteFile(pemPath, pem.EncodeToMemory(pemBlock), 0o600); err != nil {
		t.Fatalf("write temp PEM: %v", err)
	}

	var gotBody map[string]interface{}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" || r.URL.Path != "/api/v1/certificates" {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
			t.Errorf("decode request body: %v", err)
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"id":"mc-imported"}`))
	}))
	defer server.Close()

	client, _ := NewClient(server.URL, "", "table", "", false)
	err := client.ImportCertificates([]string{
		"--owner-id", "o-alice",
		"--team-id", "t-platform",
		"--renewal-policy-id", "rp-default",
		"--issuer-id", "iss-local",
		"--name-template", "imported-{cn}",
		pemPath,
	})
	if err != nil {
		t.Fatalf("ImportCertificates failed: %v", err)
	}

	// Verify every required field from the six-field contract is present.
	required := []struct {
		field string
		want  interface{}
	}{
		{"name", "imported-test.example.com"},
		{"common_name", "test.example.com"},
		{"issuer_id", "iss-local"},
		{"owner_id", "o-alice"},
		{"team_id", "t-platform"},
		{"renewal_policy_id", "rp-default"},
	}
	for _, r := range required {
		got, ok := gotBody[r.field]
		if !ok {
			t.Errorf("payload missing required field %q (body: %+v)", r.field, gotBody)
			continue
		}
		if got != r.want {
			t.Errorf("field %q = %v, want %v", r.field, got, r.want)
		}
	}
}

// containsStr is a tiny substring helper so the test file doesn't
// need a `strings` import dependency aside from what's already there.
func containsStr(haystack, needle string) bool {
	for i := 0; i+len(needle) <= len(haystack); i++ {
		if haystack[i:i+len(needle)] == needle {
			return true
		}
	}
	return false
}

// Helper function to generate a test certificate
func generateTestCert() *x509.Certificate {
	now := time.Now()
	template := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject: pkix.Name{
			CommonName: "test.example.com",
		},
		NotBefore:             now,
		NotAfter:              now.Add(365 * 24 * time.Hour),
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		DNSNames:              []string{"test.example.com", "*.test.example.com"},
	}

	privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
	certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
	cert, _ := x509.ParseCertificate(certBytes)

	return cert
}

// -----------------------------------------------------------------------------
// HTTPS-Everywhere milestone (v2.2, §3.2 + §7 Phase 5):
// The CLI binary now talks HTTPS-only to the control plane. These tests pin the
// three contracts the milestone requires every client binary (agent, CLI, MCP)
// to satisfy in lock-step:
//   (a) CA bundle load success — PEM loads, RootCAs + MinVersion=TLS1.3 wired
//       through the injected *http.Transport so the httpClient actually uses them.
//   (b) CA bundle load failure — missing file and malformed/empty PEM each fail
//       loud with a pinned substring so operators get a useful diagnostic instead
//       of a later TLS-handshake-error mystery.
//   (c) End-to-end TLS round-trip — an httptest.NewTLSServer whose own cert is
//       written out as the CA bundle validates that every TLS-config knob is
//       actually reaching the wire, not just surviving into the struct.
// Each of the three client binaries pins the same three contracts against its
// own NewClient signature; drifting any of them in isolation is exactly what
// this suite is here to catch. The error-string substrings below must stay in
// sync with the fmt.Errorf messages in internal/cli/client.go:NewClient.
// -----------------------------------------------------------------------------

// writeCABundle PEM-encodes a DER cert and writes it to a temp file under the
// test's own TempDir. Returns the absolute path of the written bundle so test
// callers can pass it straight into NewClient(..., caBundlePath, ...).
func writeCABundle(t *testing.T, dir string, certDER []byte, filename string) string {
	t.Helper()
	path := filepath.Join(dir, filename)
	pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
	if err := os.WriteFile(path, pemBytes, 0o600); err != nil {
		t.Fatalf("writing CA bundle to %q: %v", path, err)
	}
	return path
}

// TestNewClient_CABundle_Success pins the happy path: a valid PEM CA bundle
// loads, populates RootCAs on the client's TLS config, and leaves
// MinVersion=TLS1.3 intact. Regression guard: if a future edit accidentally
// swaps the transport after TLS config setup (or forgets to re-attach the
// *tls.Config to *http.Transport), this test catches it before ops does.
func TestNewClient_CABundle_Success(t *testing.T) {
	cert := generateTestCert()
	tmp := t.TempDir()
	bundlePath := writeCABundle(t, tmp, cert.Raw, "ca.pem")

	client, err := NewClient("https://certctl-server:8443", "test-key", "table", bundlePath, false)
	if err != nil {
		t.Fatalf("NewClient with valid CA bundle err=%v want nil", err)
	}
	if client == nil {
		t.Fatal("NewClient returned nil client on happy path")
	}

	transport, ok := client.httpClient.Transport.(*http.Transport)
	if !ok {
		t.Fatalf("httpClient.Transport type=%T want *http.Transport (TLS config injection broke)", client.httpClient.Transport)
	}
	if transport.TLSClientConfig == nil {
		t.Fatal("transport.TLSClientConfig is nil; TLS config must be set on every client")
	}
	if transport.TLSClientConfig.RootCAs == nil {
		t.Fatal("transport.TLSClientConfig.RootCAs is nil; CA bundle path was ignored")
	}
	if transport.TLSClientConfig.MinVersion != tls.VersionTLS13 {
		t.Errorf("MinVersion=%d want tls.VersionTLS13 (%d); HTTPS-Everywhere requires TLS1.3 floor",
			transport.TLSClientConfig.MinVersion, tls.VersionTLS13)
	}
	if transport.TLSClientConfig.InsecureSkipVerify {
		t.Error("InsecureSkipVerify=true with insecure=false arg; flag wiring crossed")
	}
}

// TestNewClient_CABundle_MissingFile pins the fail-loud path for a nonexistent
// bundle path. The error surface must include "reading CA bundle" so operators
// see the right diagnostic instead of a downstream TLS-handshake-error.
func TestNewClient_CABundle_MissingFile(t *testing.T) {
	_, err := NewClient("https://certctl-server:8443", "test-key", "table", "/nonexistent/path/ca.pem", false)
	if err == nil {
		t.Fatal("NewClient with missing CA bundle err=nil; must fail loud so operators see the right diagnostic")
	}
	if !containsStr(err.Error(), "reading CA bundle") {
		t.Errorf("err=%q must contain %q so operators can locate the misconfigured path", err.Error(), "reading CA bundle")
	}
}

// TestNewClient_CABundle_EmptyPEM pins the fail-loud path for a file whose
// contents are not valid PEM certificate data. AppendCertsFromPEM returning
// false is the signal we need to surface — otherwise the client would silently
// ship with an empty cert pool and every TLS handshake would fail downstream.
func TestNewClient_CABundle_EmptyPEM(t *testing.T) {
	tmp := t.TempDir()
	garbagePath := filepath.Join(tmp, "garbage.pem")
	if err := os.WriteFile(garbagePath, []byte("not a pem certificate, just bytes"), 0o600); err != nil {
		t.Fatalf("writing garbage file: %v", err)
	}

	_, err := NewClient("https://certctl-server:8443", "test-key", "table", garbagePath, false)
	if err == nil {
		t.Fatal("NewClient with malformed PEM err=nil; must fail loud, not silently skip")
	}
	if !containsStr(err.Error(), "no valid PEM-encoded certificates") {
		t.Errorf("err=%q must contain %q so operators know the file parsed but held no certs",
			err.Error(), "no valid PEM-encoded certificates")
	}
}

// TestNewClient_TLSRoundTrip validates that the TLS config knobs we set on
// NewClient actually reach the wire. An httptest.NewTLSServer signs its own
// self-signed leaf; we PEM-encode that server cert, write it as the CA bundle,
// and issue a real HTTPS call through ListCertificates. A successful round-trip
// proves RootCAs + MinVersion are flowing through *http.Transport into the
// dialer, not just surviving into the client struct.
func TestNewClient_TLSRoundTrip(t *testing.T) {
	var handlerHit int
	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "GET" && r.URL.Path == "/api/v1/certificates" {
			handlerHit++
			w.Header().Set("Content-Type", "application/json")
			_ = json.NewEncoder(w).Encode(map[string]interface{}{
				"data":  []map[string]interface{}{},
				"total": 0,
			})
			return
		}
		w.WriteHeader(http.StatusNotFound)
	}))
	defer server.Close()

	serverCert := server.Certificate()
	if serverCert == nil {
		t.Fatal("httptest.NewTLSServer.Certificate() returned nil; cannot build CA bundle")
	}

	tmp := t.TempDir()
	bundlePath := writeCABundle(t, tmp, serverCert.Raw, "server-ca.pem")

	client, err := NewClient(server.URL, "test-key", "table", bundlePath, false)
	if err != nil {
		t.Fatalf("NewClient(TLS server) err=%v want nil", err)
	}
	if err := client.ListCertificates([]string{}); err != nil {
		t.Fatalf("ListCertificates over HTTPS err=%v; TLS config must reach the wire", err)
	}
	if handlerHit != 1 {
		t.Errorf("handlerHit=%d want 1; request did not reach the TLS server", handlerHit)
	}
}

// TestNewClient_InsecureSkipVerify pins the dev-only escape hatch: an untrusted
// TLS server (cert NOT in the client's root pool) must be reachable when
// insecure=true. This is the only path in the control plane that disables
// certificate verification; it's documented in docs/tls.md and gated by the
// CERTCTL_SERVER_TLS_INSECURE_SKIP_VERIFY env var so it never slips into
// production silently.
func TestNewClient_InsecureSkipVerify(t *testing.T) {
	var handlerHit int
	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		handlerHit++
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]interface{}{
			"data":  []map[string]interface{}{},
			"total": 0,
		})
	}))
	defer server.Close()

	// No CA bundle → system roots, which will NOT trust the self-signed
	// httptest cert. insecure=true is the only thing keeping this call from
	// failing with an x509-unknown-authority error.
	client, err := NewClient(server.URL, "test-key", "table", "", true)
	if err != nil {
		t.Fatalf("NewClient(insecure=true) err=%v want nil", err)
	}

	transport, ok := client.httpClient.Transport.(*http.Transport)
	if !ok {
		t.Fatalf("httpClient.Transport type=%T want *http.Transport", client.httpClient.Transport)
	}
	if !transport.TLSClientConfig.InsecureSkipVerify {
		t.Fatal("insecure=true arg did not set TLSClientConfig.InsecureSkipVerify; flag wiring broken")
	}
	if transport.TLSClientConfig.MinVersion != tls.VersionTLS13 {
		t.Errorf("MinVersion=%d want tls.VersionTLS13 even with insecure=true (TLS1.3 floor is not optional)",
			transport.TLSClientConfig.MinVersion)
	}

	if err := client.ListCertificates([]string{}); err != nil {
		t.Fatalf("ListCertificates(insecure=true) err=%v; escape hatch must still complete the round-trip", err)
	}
	if handlerHit != 1 {
		t.Errorf("handlerHit=%d want 1; insecure round-trip did not reach the server", handlerHit)
	}
}
