package handler

import (
	"crypto/tls"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/certctl-io/certctl/internal/domain"
)

// EST RFC 7030 hardening master bundle Phase 10.3 — Cisco IOS quirk
// fixtures. Each fixture is a captured-shape CSR that exercises one
// of the documented IOS wire-format deviations from the EST §4.2.1
// happy-path; the test pins that ESTHandler.readCSRFromRequest +
// the broader handler pipeline accept each shape without operator
// intervention.
//
// Fixtures live under testdata/cisco_ios_*.txt — kept as plain-text
// copies so a future reader can `cat` them + understand the shape
// without re-deriving from a binary blob.

// loadCiscoFixture reads the named testdata file. Path-traversal-safe
// because the fixture name is a compile-time constant per call site;
// we keep filepath.Clean for hygiene.
func loadCiscoFixture(t *testing.T, name string) string {
	t.Helper()
	body, err := os.ReadFile(filepath.Clean(filepath.Join("testdata", name)))
	if err != nil {
		t.Fatalf("read fixture %q: %v", name, err)
	}
	return string(body)
}

// TestESTCiscoIOSQuirk_15xPEMUploadAccepted exercises the documented
// IOS 15.x quirk: the device sends Content-Type `application/x-pem-file`
// (PEM-encoded) instead of the EST §4.2.1 canonical
// `application/pkcs10` (base64-DER). The handler's readCSRFromRequest
// dispatches on body-prefix (`-----BEGIN CERTIFICATE REQUEST-----`)
// rather than Content-Type, so the upload should parse cleanly + the
// service should see a properly-formed CSR.
func TestESTCiscoIOSQuirk_15xPEMUploadAccepted(t *testing.T) {
	body := loadCiscoFixture(t, "cisco_ios_15x_pem_csr.txt")
	if !strings.HasPrefix(body, "-----BEGIN CERTIFICATE REQUEST-----") {
		t.Fatalf("fixture corrupted: expected PEM prefix, got %q", body[:60])
	}

	svc := &mockESTService{EnrollResult: ciscoQuirkOKResult(t)}
	h := NewESTHandler(svc)

	req := httptest.NewRequest(http.MethodPost,
		"/.well-known/est/corp/simpleenroll", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/x-pem-file") // the IOS 15.x quirk
	req.TLS = &tls.ConnectionState{HandshakeComplete: true, Version: tls.VersionTLS13}
	w := httptest.NewRecorder()
	h.SimpleEnroll(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("IOS 15.x PEM upload status = %d, want 200; body=%q", w.Code, w.Body.String())
	}
}

// TestESTCiscoIOSQuirk_16xTrailingNewlinesAccepted exercises the
// documented IOS 16.x quirk: an extra trailing newline after the
// base64 body. The handler's strings.TrimSpace pass MUST tolerate
// any number of trailing whitespace bytes without surfacing as a
// malformed-CSR rejection.
func TestESTCiscoIOSQuirk_16xTrailingNewlinesAccepted(t *testing.T) {
	body := loadCiscoFixture(t, "cisco_ios_16x_trailing_newline_csr.txt")
	if !strings.HasSuffix(body, "\n\n\n") && !strings.HasSuffix(body, "\n\n") {
		tail := body
		if len(tail) > 10 {
			tail = body[len(body)-10:]
		}
		t.Fatalf("fixture corrupted: expected ≥2 trailing newlines; got tail=%q", tail)
	}

	svc := &mockESTService{EnrollResult: ciscoQuirkOKResult(t)}
	h := NewESTHandler(svc)

	req := httptest.NewRequest(http.MethodPost,
		"/.well-known/est/corp/simpleenroll", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/pkcs10")
	req.TLS = &tls.ConnectionState{HandshakeComplete: true, Version: tls.VersionTLS13}
	w := httptest.NewRecorder()
	h.SimpleEnroll(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("IOS 16.x trailing-newlines status = %d, want 200; body=%q", w.Code, w.Body.String())
	}
}

// TestESTCiscoIOSQuirk_CRLFBase64Accepted exercises the documented
// CRLF-line-ending quirk. Some IOS versions emit base64-DER with
// CRLF wrapping (the RFC 2045 §6.8 wire shape) rather than bare LF
// (the JSON-via-curl shape). The handler must strip both CRLF + LF
// before passing to base64.StdEncoding.DecodeString.
func TestESTCiscoIOSQuirk_CRLFBase64Accepted(t *testing.T) {
	body := loadCiscoFixture(t, "cisco_ios_crlf_b64_csr.txt")
	if !strings.Contains(body, "\r\n") {
		t.Fatalf("fixture corrupted: expected CRLF-wrapped body; first 80 = %q", body[:80])
	}

	svc := &mockESTService{EnrollResult: ciscoQuirkOKResult(t)}
	h := NewESTHandler(svc)

	req := httptest.NewRequest(http.MethodPost,
		"/.well-known/est/corp/simpleenroll", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/pkcs10")
	req.TLS = &tls.ConnectionState{HandshakeComplete: true, Version: tls.VersionTLS13}
	w := httptest.NewRecorder()
	h.SimpleEnroll(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("CRLF-wrapped base64 status = %d, want 200; body=%q", w.Code, w.Body.String())
	}
}

// ciscoQuirkOKResult is the service-side response the mock returns for
// every Cisco-quirk happy-path test. The cert content doesn't matter —
// what matters is that the handler reaches the service call (i.e. it
// successfully parsed the CSR), so we hand back a hard-coded EC cert
// PEM that pkcs7.PEMToDERChain accepts cleanly.
func ciscoQuirkOKResult(t *testing.T) *domain.ESTEnrollResult {
	t.Helper()
	return &domain.ESTEnrollResult{
		CertPEM: "-----BEGIN CERTIFICATE-----\nMIIBnDCCAUOgAwIBAgIBATAKBggqhkjOPQQDAjAUMRIwEAYDVQQDDAljaXNjby10\nZXN0MB4XDTI1MDEwMTAwMDAwMFoXDTM1MTIzMTAwMDAwMFowFDESMBAGA1UEAwwJ\nY2lzY28tdGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAfNh1+nAo15qVMF\nh0w4EQfHBn5zQgEDLkJhpZ+9PqJkgqdSwJgC+4Ah+UWrJOO6+P9YOPXqkSQU0E2X\n3/Ms2DyjUzBRMB0GA1UdDgQWBBSm1U4Fmh4j9eJDVa8qBOrkxqLhajAfBgNVHSME\nGDAWgBSm1U4Fmh4j9eJDVa8qBOrkxqLhajAPBgNVHRMBAf8EBTADAQH/MAoGCCqG\nSM49BAMCA0gAMEUCIQCY7d0XHVz7AmAFZrYTIVFmRn/PV+0qRu9HSqwvU1HYNgIg\nXKJM6e/0ckLhqLGB1lN9Bz/cvyZuYIcHLgMrlvNUwYE=\n-----END CERTIFICATE-----\n",
	}
}
