package handler

import (
	"context"
	"encoding/pem"
	"errors"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

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

// mockSCEPService implements SCEPService for testing.
type mockSCEPService struct {
	CACaps       string
	CACertPEM    string
	CACertErr    error
	EnrollResult *domain.SCEPEnrollResult
	EnrollErr    error
}

func (m *mockSCEPService) GetCACaps(ctx context.Context) string {
	if m.CACaps != "" {
		return m.CACaps
	}
	return "POSTPKIOperation\nSHA-256\nAES\nSCEPStandard\n"
}

func (m *mockSCEPService) GetCACert(ctx context.Context) (string, error) {
	return m.CACertPEM, m.CACertErr
}

func (m *mockSCEPService) PKCSReq(ctx context.Context, csrPEM string, challengePassword string, transactionID string) (*domain.SCEPEnrollResult, error) {
	return m.EnrollResult, m.EnrollErr
}

func TestSCEP_GetCACaps_Success(t *testing.T) {
	svc := &mockSCEPService{}
	h := NewSCEPHandler(svc)

	req := httptest.NewRequest(http.MethodGet, "/scep?operation=GetCACaps", nil)
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
	}
	ct := w.Header().Get("Content-Type")
	if ct != "text/plain" {
		t.Errorf("expected text/plain, got %s", ct)
	}
	body := w.Body.String()
	if !strings.Contains(body, "POSTPKIOperation") {
		t.Errorf("expected POSTPKIOperation in response, got: %s", body)
	}
	if !strings.Contains(body, "SHA-256") {
		t.Errorf("expected SHA-256 in response, got: %s", body)
	}
}

func TestSCEP_GetCACaps_MethodNotAllowed(t *testing.T) {
	svc := &mockSCEPService{}
	h := NewSCEPHandler(svc)

	req := httptest.NewRequest(http.MethodPost, "/scep?operation=GetCACaps", nil)
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

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

func TestSCEP_GetCACert_Success_SingleCert(t *testing.T) {
	certPEM := generateTestCertPEM(t)
	svc := &mockSCEPService{
		CACertPEM: certPEM,
	}
	h := NewSCEPHandler(svc)

	req := httptest.NewRequest(http.MethodGet, "/scep?operation=GetCACert", nil)
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
	}
	ct := w.Header().Get("Content-Type")
	if ct != "application/x-x509-ca-cert" {
		t.Errorf("expected application/x-x509-ca-cert, got %s", ct)
	}
	if w.Body.Len() == 0 {
		t.Error("expected non-empty body")
	}
}

func TestSCEP_GetCACert_MethodNotAllowed(t *testing.T) {
	svc := &mockSCEPService{}
	h := NewSCEPHandler(svc)

	req := httptest.NewRequest(http.MethodPost, "/scep?operation=GetCACert", nil)
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

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

func TestSCEP_GetCACert_ServiceError(t *testing.T) {
	svc := &mockSCEPService{
		CACertErr: errors.New("CA unavailable"),
	}
	h := NewSCEPHandler(svc)

	req := httptest.NewRequest(http.MethodGet, "/scep?operation=GetCACert", nil)
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

	if w.Code != http.StatusInternalServerError {
		t.Errorf("expected 500, got %d", w.Code)
	}
}

func TestSCEP_PKIOperation_MethodNotAllowed(t *testing.T) {
	svc := &mockSCEPService{}
	h := NewSCEPHandler(svc)

	req := httptest.NewRequest(http.MethodGet, "/scep?operation=PKIOperation", nil)
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

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

func TestSCEP_PKIOperation_EmptyBody(t *testing.T) {
	svc := &mockSCEPService{}
	h := NewSCEPHandler(svc)

	req := httptest.NewRequest(http.MethodPost, "/scep?operation=PKIOperation", strings.NewReader(""))
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestSCEP_PKIOperation_InvalidBody(t *testing.T) {
	svc := &mockSCEPService{}
	h := NewSCEPHandler(svc)

	req := httptest.NewRequest(http.MethodPost, "/scep?operation=PKIOperation", strings.NewReader("not-valid-asn1-or-csr"))
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
	}
}

func TestSCEP_PKIOperation_ServiceError(t *testing.T) {
	svc := &mockSCEPService{
		EnrollErr: errors.New("enrollment failed"),
	}
	h := NewSCEPHandler(svc)

	// Generate a valid raw CSR DER to send as body (fallback path)
	csrPEM := generateTestCSRPEM(t)
	block, _ := pem.Decode([]byte(csrPEM))
	if block == nil {
		t.Fatal("failed to decode CSR PEM")
	}

	req := httptest.NewRequest(http.MethodPost, "/scep?operation=PKIOperation", strings.NewReader(string(block.Bytes)))
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

	if w.Code != http.StatusInternalServerError {
		t.Errorf("expected 500, got %d: %s", w.Code, w.Body.String())
	}
}

func TestSCEP_PKIOperation_Success_RawCSR(t *testing.T) {
	certPEM := generateTestCertPEM(t)
	svc := &mockSCEPService{
		EnrollResult: &domain.SCEPEnrollResult{
			CertPEM:  certPEM,
			ChainPEM: "",
		},
	}
	h := NewSCEPHandler(svc)

	csrPEM := generateTestCSRPEM(t)
	block, _ := pem.Decode([]byte(csrPEM))
	if block == nil {
		t.Fatal("failed to decode CSR PEM")
	}

	req := httptest.NewRequest(http.MethodPost, "/scep?operation=PKIOperation", strings.NewReader(string(block.Bytes)))
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
	}
	ct := w.Header().Get("Content-Type")
	if ct != "application/x-pki-message" {
		t.Errorf("expected application/x-pki-message, got %s", ct)
	}
}

func TestSCEP_PKIOperation_ChallengePasswordRejected(t *testing.T) {
	svc := &mockSCEPService{
		EnrollErr: errors.New("invalid challenge password"),
	}
	h := NewSCEPHandler(svc)

	csrPEM := generateTestCSRPEM(t)
	block, _ := pem.Decode([]byte(csrPEM))
	if block == nil {
		t.Fatal("failed to decode CSR PEM")
	}

	req := httptest.NewRequest(http.MethodPost, "/scep?operation=PKIOperation", strings.NewReader(string(block.Bytes)))
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

	if w.Code != http.StatusForbidden {
		t.Errorf("expected 403, got %d: %s", w.Code, w.Body.String())
	}
}

func TestSCEP_UnknownOperation(t *testing.T) {
	svc := &mockSCEPService{}
	h := NewSCEPHandler(svc)

	req := httptest.NewRequest(http.MethodGet, "/scep?operation=UnknownOp", nil)
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestSCEP_MissingOperation(t *testing.T) {
	svc := &mockSCEPService{}
	h := NewSCEPHandler(svc)

	req := httptest.NewRequest(http.MethodGet, "/scep", nil)
	w := httptest.NewRecorder()
	h.HandleSCEP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}
