package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

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

// Add context import was already there — verify import is present above

// MockExportService is a mock implementation of ExportService interface.
type MockExportService struct {
	ExportPEMFn    func(ctx context.Context, certID string) (*service.ExportPEMResult, error)
	ExportPKCS12Fn func(ctx context.Context, certID string, password string) ([]byte, error)
}

func (m *MockExportService) ExportPEM(ctx context.Context, certID string) (*service.ExportPEMResult, error) {
	if m.ExportPEMFn != nil {
		return m.ExportPEMFn(ctx, certID)
	}
	return nil, nil
}

func (m *MockExportService) ExportPKCS12(ctx context.Context, certID string, password string) ([]byte, error) {
	if m.ExportPKCS12Fn != nil {
		return m.ExportPKCS12Fn(ctx, certID, password)
	}
	return nil, nil
}

func TestExportPEM_Success(t *testing.T) {
	mockSvc := &MockExportService{
		ExportPEMFn: func(_ context.Context, certID string) (*service.ExportPEMResult, error) {
			if certID != "mc-test-1" {
				t.Errorf("expected certID mc-test-1, got %s", certID)
			}
			return &service.ExportPEMResult{
				CertPEM:  "-----BEGIN CERTIFICATE-----\nAAA\n-----END CERTIFICATE-----\n",
				ChainPEM: "-----BEGIN CERTIFICATE-----\nBBB\n-----END CERTIFICATE-----\n",
				FullPEM:  "-----BEGIN CERTIFICATE-----\nAAA\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nBBB\n-----END CERTIFICATE-----\n",
			}, nil
		},
	}
	h := NewExportHandler(mockSvc)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates/mc-test-1/export/pem", nil)
	w := httptest.NewRecorder()

	h.ExportPEM(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}
	if ct := w.Header().Get("Content-Type"); ct != "application/json" {
		t.Errorf("expected application/json content type, got %s", ct)
	}

	var result service.ExportPEMResult
	if err := json.NewDecoder(w.Body).Decode(&result); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}
	if result.CertPEM == "" {
		t.Error("expected non-empty CertPEM")
	}
	if result.ChainPEM == "" {
		t.Error("expected non-empty ChainPEM")
	}
	if result.FullPEM == "" {
		t.Error("expected non-empty FullPEM")
	}
}

func TestExportPEM_Download(t *testing.T) {
	mockSvc := &MockExportService{
		ExportPEMFn: func(_ context.Context, _ string) (*service.ExportPEMResult, error) {
			return &service.ExportPEMResult{
				CertPEM:  "cert",
				ChainPEM: "chain",
				FullPEM:  "full-pem-content",
			}, nil
		},
	}
	h := NewExportHandler(mockSvc)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates/mc-test-1/export/pem?download=true", nil)
	w := httptest.NewRecorder()

	h.ExportPEM(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}
	if ct := w.Header().Get("Content-Type"); ct != "application/x-pem-file" {
		t.Errorf("expected application/x-pem-file, got %s", ct)
	}
	if cd := w.Header().Get("Content-Disposition"); cd != `attachment; filename="certificate.pem"` {
		t.Errorf("expected Content-Disposition attachment, got %s", cd)
	}
	if w.Body.String() != "full-pem-content" {
		t.Errorf("expected full-pem-content body, got %s", w.Body.String())
	}
}

func TestExportPEM_NotFound(t *testing.T) {
	mockSvc := &MockExportService{
		ExportPEMFn: func(_ context.Context, _ string) (*service.ExportPEMResult, error) {
			return nil, fmt.Errorf("certificate not found")
		},
	}
	h := NewExportHandler(mockSvc)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates/nonexistent/export/pem", nil)
	w := httptest.NewRecorder()

	h.ExportPEM(w, req)

	if w.Code != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", w.Code)
	}
}

func TestExportPEM_ServiceError(t *testing.T) {
	mockSvc := &MockExportService{
		ExportPEMFn: func(_ context.Context, _ string) (*service.ExportPEMResult, error) {
			return nil, fmt.Errorf("internal error")
		},
	}
	h := NewExportHandler(mockSvc)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates/mc-test-1/export/pem", nil)
	w := httptest.NewRecorder()

	h.ExportPEM(w, req)

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

func TestExportPEM_MethodNotAllowed(t *testing.T) {
	h := NewExportHandler(&MockExportService{})

	req := httptest.NewRequest(http.MethodPost, "/api/v1/certificates/mc-test-1/export/pem", nil)
	w := httptest.NewRecorder()

	h.ExportPEM(w, req)

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

func TestExportPKCS12_Success(t *testing.T) {
	pfxData := []byte{0x30, 0x82, 0x01, 0x00} // mock PKCS#12 data
	mockSvc := &MockExportService{
		ExportPKCS12Fn: func(_ context.Context, certID string, password string) ([]byte, error) {
			if certID != "mc-test-1" {
				t.Errorf("expected certID mc-test-1, got %s", certID)
			}
			if password != "mysecret" {
				t.Errorf("expected password mysecret, got %s", password)
			}
			return pfxData, nil
		},
	}
	h := NewExportHandler(mockSvc)

	body := strings.NewReader(`{"password":"mysecret"}`)
	req := httptest.NewRequest(http.MethodPost, "/api/v1/certificates/mc-test-1/export/pkcs12", body)
	w := httptest.NewRecorder()

	h.ExportPKCS12(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}
	if ct := w.Header().Get("Content-Type"); ct != "application/x-pkcs12" {
		t.Errorf("expected application/x-pkcs12, got %s", ct)
	}
	if cd := w.Header().Get("Content-Disposition"); cd != `attachment; filename="certificate.p12"` {
		t.Errorf("expected Content-Disposition attachment, got %s", cd)
	}
	if len(w.Body.Bytes()) != len(pfxData) {
		t.Errorf("expected %d bytes, got %d", len(pfxData), len(w.Body.Bytes()))
	}
}

func TestExportPKCS12_EmptyPassword(t *testing.T) {
	mockSvc := &MockExportService{
		ExportPKCS12Fn: func(_ context.Context, _ string, password string) ([]byte, error) {
			if password != "" {
				t.Errorf("expected empty password, got %s", password)
			}
			return []byte{0x30}, nil
		},
	}
	h := NewExportHandler(mockSvc)

	// Empty body — password defaults to ""
	req := httptest.NewRequest(http.MethodPost, "/api/v1/certificates/mc-test-1/export/pkcs12", nil)
	w := httptest.NewRecorder()

	h.ExportPKCS12(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}
}

func TestExportPKCS12_NotFound(t *testing.T) {
	mockSvc := &MockExportService{
		ExportPKCS12Fn: func(_ context.Context, _ string, _ string) ([]byte, error) {
			return nil, fmt.Errorf("certificate not found")
		},
	}
	h := NewExportHandler(mockSvc)

	req := httptest.NewRequest(http.MethodPost, "/api/v1/certificates/nonexistent/export/pkcs12", nil)
	w := httptest.NewRecorder()

	h.ExportPKCS12(w, req)

	if w.Code != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", w.Code)
	}
}

func TestExportPKCS12_ServiceError(t *testing.T) {
	mockSvc := &MockExportService{
		ExportPKCS12Fn: func(_ context.Context, _ string, _ string) ([]byte, error) {
			return nil, fmt.Errorf("encoding error")
		},
	}
	h := NewExportHandler(mockSvc)

	req := httptest.NewRequest(http.MethodPost, "/api/v1/certificates/mc-test-1/export/pkcs12", nil)
	w := httptest.NewRecorder()

	h.ExportPKCS12(w, req)

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

func TestExportPKCS12_MethodNotAllowed(t *testing.T) {
	h := NewExportHandler(&MockExportService{})

	req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates/mc-test-1/export/pkcs12", nil)
	w := httptest.NewRecorder()

	h.ExportPKCS12(w, req)

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

func TestExtractCertIDFromExportPath(t *testing.T) {
	tests := []struct {
		path     string
		expected string
	}{
		{"/api/v1/certificates/mc-test-1/export/pem", "mc-test-1"},
		{"/api/v1/certificates/mc-api-prod/export/pkcs12", "mc-api-prod"},
		{"/api/v1/certificates//export/pem", ""},
		{"/api/v1/other/mc-test-1/export/pem", ""},
		{"/api/v1/certificates/mc-test-1", ""},
		{"", ""},
	}

	for _, tt := range tests {
		got := extractCertIDFromExportPath(tt.path)
		if got != tt.expected {
			t.Errorf("extractCertIDFromExportPath(%q) = %q, want %q", tt.path, got, tt.expected)
		}
	}
}

func TestExportPKCS12_InvalidJSON(t *testing.T) {
	mockSvc := &MockExportService{
		ExportPKCS12Fn: func(_ context.Context, _ string, password string) ([]byte, error) {
			// Invalid JSON is silently ignored, defaults to empty password
			if password != "" {
				t.Errorf("expected empty password (invalid JSON ignored), got %s", password)
			}
			return []byte{0x30}, nil
		},
	}
	h := NewExportHandler(mockSvc)

	req := httptest.NewRequest(http.MethodPost, "/api/v1/certificates/mc-test-1/export/pkcs12", strings.NewReader(`{"invalid json`))
	w := httptest.NewRecorder()

	h.ExportPKCS12(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200 (invalid JSON ignored), got %d", w.Code)
	}
}

func TestExportPEM_MethodNotAllowedDelete(t *testing.T) {
	h := NewExportHandler(&MockExportService{})

	req := httptest.NewRequest(http.MethodDelete, "/api/v1/certificates/mc-test-1/export/pem", nil)
	w := httptest.NewRecorder()

	h.ExportPEM(w, req)

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