package handlers

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
)

func init() {
	gin.SetMode(gin.TestMode)
}

// ============================================================================
// Helper: create a gin test context with optional JSON body, headers, params
// ============================================================================

func newTestContext(method, path string, body interface{}, headers map[string]string, params gin.Params) (*httptest.ResponseRecorder, *gin.Context) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	var reqBody *bytes.Reader
	if body != nil {
		b, _ := json.Marshal(body)
		reqBody = bytes.NewReader(b)
	} else {
		reqBody = bytes.NewReader(nil)
	}

	c.Request, _ = http.NewRequest(method, path, reqBody)
	c.Request.Header.Set("Content-Type", "application/json")
	for k, v := range headers {
		c.Request.Header.Set(k, v)
	}
	if params != nil {
		c.Params = params
	}

	return w, c
}

func parseResponse(w *httptest.ResponseRecorder) map[string]interface{} {
	var resp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &resp)
	return resp
}

// ============================================================================
// InitFromProfile Tests
// ============================================================================

func TestInitFromProfile_InvalidProjectID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("POST", "/projects/not-a-uuid/init-from-profile", nil, nil, gin.Params{
		{Key: "id", Value: "not-a-uuid"},
	})

	handler.InitFromProfile(c)

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

	resp := parseResponse(w)
	if resp["error"] == nil {
		t.Error("Expected error message in response")
	}
}

func TestInitFromProfile_EmptyProjectID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("POST", "/projects//init-from-profile", nil, nil, gin.Params{
		{Key: "id", Value: ""},
	})

	handler.InitFromProfile(c)

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

func TestInitFromProfile_RequestBinding(t *testing.T) {
	// Verify the InitFromProfileRequest properly binds company_profile and compliance_scope
	body := `{
		"company_profile": {"company_name": "TestCorp GmbH", "contact_email": "info@testcorp.de"},
		"compliance_scope": {"machine_name": "Robot XY", "has_ai": true, "applicable_regulations": ["machinery_regulation"]}
	}`

	var parsed struct {
		CompanyProfile  json.RawMessage `json:"company_profile"`
		ComplianceScope json.RawMessage `json:"compliance_scope"`
	}
	if err := json.Unmarshal([]byte(body), &parsed); err != nil {
		t.Fatalf("Failed to parse request body: %v", err)
	}

	if parsed.CompanyProfile == nil {
		t.Error("company_profile should not be nil")
	}
	if parsed.ComplianceScope == nil {
		t.Error("compliance_scope should not be nil")
	}

	// Verify scope parsing
	var scope struct {
		MachineName           string   `json:"machine_name"`
		HasAI                 bool     `json:"has_ai"`
		ApplicableRegulations []string `json:"applicable_regulations"`
	}
	if err := json.Unmarshal(parsed.ComplianceScope, &scope); err != nil {
		t.Fatalf("Failed to parse compliance_scope: %v", err)
	}
	if scope.MachineName != "Robot XY" {
		t.Errorf("Expected machine_name 'Robot XY', got %q", scope.MachineName)
	}
	if !scope.HasAI {
		t.Error("Expected has_ai to be true")
	}
	if len(scope.ApplicableRegulations) != 1 || scope.ApplicableRegulations[0] != "machinery_regulation" {
		t.Errorf("Unexpected applicable_regulations: %v", scope.ApplicableRegulations)
	}

	// Verify profile parsing
	var profile struct {
		CompanyName  string `json:"company_name"`
		ContactEmail string `json:"contact_email"`
	}
	if err := json.Unmarshal(parsed.CompanyProfile, &profile); err != nil {
		t.Fatalf("Failed to parse company_profile: %v", err)
	}
	if profile.CompanyName != "TestCorp GmbH" {
		t.Errorf("Expected company_name 'TestCorp GmbH', got %q", profile.CompanyName)
	}
	if profile.ContactEmail != "info@testcorp.de" {
		t.Errorf("Expected contact_email 'info@testcorp.de', got %q", profile.ContactEmail)
	}
}

// ============================================================================
// GenerateSingleSection Tests
// ============================================================================

func TestGenerateSingleSection_InvalidProjectID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("POST", "/projects/invalid/tech-file/risk_assessment_report/generate", nil, nil, gin.Params{
		{Key: "id", Value: "invalid"},
		{Key: "section", Value: "risk_assessment_report"},
	})

	handler.GenerateSingleSection(c)

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

	resp := parseResponse(w)
	errMsg, _ := resp["error"].(string)
	if errMsg != "invalid project ID" {
		t.Errorf("Expected 'invalid project ID' error, got %q", errMsg)
	}
}

func TestGenerateSingleSection_EmptySectionType_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("POST", "/projects/00000000-0000-0000-0000-000000000001/tech-file//generate", nil, nil, gin.Params{
		{Key: "id", Value: "00000000-0000-0000-0000-000000000001"},
		{Key: "section", Value: ""},
	})

	handler.GenerateSingleSection(c)

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

	resp := parseResponse(w)
	errMsg, _ := resp["error"].(string)
	if errMsg != "section type required" {
		t.Errorf("Expected 'section type required' error, got %q", errMsg)
	}
}

func TestGenerateSingleSection_SectionTitleMapping(t *testing.T) {
	// Verify the section title map covers all 19 section types
	sectionTitles := map[string]string{
		"general_description":       "General Description of the Machinery",
		"risk_assessment_report":    "Risk Assessment Report",
		"hazard_log_combined":       "Combined Hazard Log",
		"essential_requirements":    "Essential Health and Safety Requirements",
		"design_specifications":     "Design Specifications and Drawings",
		"test_reports":              "Test Reports and Verification Results",
		"standards_applied":         "Applied Harmonised Standards",
		"declaration_of_conformity": "EU Declaration of Conformity",
		"component_list":            "Component List",
		"classification_report":     "Regulatory Classification Report",
		"mitigation_report":         "Mitigation Measures Report",
		"verification_report":       "Verification Report",
		"evidence_index":            "Evidence Index",
		"instructions_for_use":      "Instructions for Use",
		"monitoring_plan":           "Post-Market Monitoring Plan",
		"ai_intended_purpose":       "AI System Intended Purpose",
		"ai_model_description":      "AI Model Description and Training Data",
		"ai_risk_management":        "AI Risk Management System",
		"ai_human_oversight":        "AI Human Oversight Measures",
	}

	if len(sectionTitles) != 19 {
		t.Errorf("Expected 19 section types, got %d", len(sectionTitles))
	}

	for key, title := range sectionTitles {
		if key == "" {
			t.Error("Section key must not be empty")
		}
		if title == "" {
			t.Errorf("Section title for %q must not be empty", key)
		}
	}
}

// ============================================================================
// ExportTechFile Tests
// ============================================================================

func TestExportTechFile_InvalidProjectID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("GET", "/projects/invalid/tech-file/export", nil, nil, gin.Params{
		{Key: "id", Value: "invalid"},
	})

	handler.ExportTechFile(c)

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

func TestExportTechFile_FormatQueryParam(t *testing.T) {
	// Verify that DefaultQuery correctly parses format parameter
	tests := []struct {
		name          string
		queryString   string
		expectedQuery string
	}{
		{"default json", "", "json"},
		{"explicit pdf", "format=pdf", "pdf"},
		{"explicit xlsx", "format=xlsx", "xlsx"},
		{"explicit docx", "format=docx", "docx"},
		{"explicit md", "format=md", "md"},
		{"explicit json", "format=json", "json"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)
			url := "/projects/test/tech-file/export"
			if tt.queryString != "" {
				url += "?" + tt.queryString
			}
			c.Request, _ = http.NewRequest("GET", url, nil)

			result := c.DefaultQuery("format", "json")
			if result != tt.expectedQuery {
				t.Errorf("Expected format %q, got %q", tt.expectedQuery, result)
			}
		})
	}
}

func TestExportTechFile_ContentDispositionHeaders(t *testing.T) {
	// Verify the header format for different export types
	tests := []struct {
		format      string
		contentType string
	}{
		{"pdf", "application/pdf"},
		{"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
		{"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
		{"md", "text/markdown"},
	}

	for _, tt := range tests {
		t.Run(tt.format, func(t *testing.T) {
			if tt.contentType == "" {
				t.Errorf("Content-Type for format %q must not be empty", tt.format)
			}
		})
	}
}

// ============================================================================
// CheckCompleteness Tests
// ============================================================================

func TestCheckCompleteness_InvalidProjectID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("POST", "/projects/invalid/completeness-check", nil, nil, gin.Params{
		{Key: "id", Value: "not-valid-uuid"},
	})

	handler.CheckCompleteness(c)

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

// ============================================================================
// getTenantID Tests
// ============================================================================

func TestGetTenantID_MissingHeader_ReturnsError(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request, _ = http.NewRequest("GET", "/test", nil)

	_, err := getTenantID(c)
	if err == nil {
		t.Error("Expected error for missing X-Tenant-Id header")
	}
}

func TestGetTenantID_InvalidUUID_ReturnsError(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request, _ = http.NewRequest("GET", "/test", nil)
	c.Request.Header.Set("X-Tenant-Id", "not-a-uuid")

	_, err := getTenantID(c)
	if err == nil {
		t.Error("Expected error for invalid UUID in X-Tenant-Id header")
	}
}

func TestGetTenantID_ValidUUID_ReturnsUUID(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request, _ = http.NewRequest("GET", "/test", nil)
	c.Request.Header.Set("X-Tenant-Id", "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e")

	tid, err := getTenantID(c)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if tid.String() != "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e" {
		t.Errorf("Expected tenant ID '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e', got %q", tid.String())
	}
}

// ============================================================================
// CreateProject / ListProjects — Input Validation
// ============================================================================

func TestCreateProject_MissingTenantID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("POST", "/projects", map[string]string{"machine_name": "Test"}, nil, nil)

	handler.CreateProject(c)

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

func TestListProjects_MissingTenantID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("GET", "/projects", nil, nil, nil)

	handler.ListProjects(c)

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

// ============================================================================
// Component Handlers — Input Validation
// ============================================================================

func TestCreateComponent_InvalidProjectID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("POST", "/projects/invalid/components", map[string]string{"name": "Test"}, nil, gin.Params{
		{Key: "id", Value: "invalid"},
	})

	handler.CreateComponent(c)

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

func TestListComponents_InvalidProjectID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("GET", "/projects/invalid/components", nil, nil, gin.Params{
		{Key: "id", Value: "invalid"},
	})

	handler.ListComponents(c)

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

func TestUpdateComponent_InvalidProjectID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("PUT", "/projects/invalid/components/abc", map[string]string{"name": "New"}, nil, gin.Params{
		{Key: "id", Value: "invalid"},
		{Key: "cid", Value: "00000000-0000-0000-0000-000000000001"},
	})

	handler.UpdateComponent(c)

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

func TestUpdateComponent_InvalidComponentID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("PUT", "/projects/00000000-0000-0000-0000-000000000001/components/invalid", nil, nil, gin.Params{
		{Key: "id", Value: "00000000-0000-0000-0000-000000000001"},
		{Key: "cid", Value: "invalid"},
	})

	handler.UpdateComponent(c)

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

func TestDeleteComponent_InvalidProjectID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("DELETE", "/projects/invalid/components/abc", nil, nil, gin.Params{
		{Key: "id", Value: "invalid"},
		{Key: "cid", Value: "00000000-0000-0000-0000-000000000001"},
	})

	handler.DeleteComponent(c)

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

func TestDeleteComponent_InvalidComponentID_Returns400(t *testing.T) {
	handler := &IACEHandler{}

	w, c := newTestContext("DELETE", "/projects/00000000-0000-0000-0000-000000000001/components/invalid", nil, nil, gin.Params{
		{Key: "id", Value: "00000000-0000-0000-0000-000000000001"},
		{Key: "cid", Value: "invalid"},
	})

	handler.DeleteComponent(c)

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