package iace

import (
	"strings"
	"testing"

	"github.com/google/uuid"
)

// ============================================================================
// Test Helpers
// ============================================================================

// newTestSectionContext builds a SectionGenerationContext with realistic sample
// data for a "Robot Arm XY-200" industrial robot project.
func newTestSectionContext() *SectionGenerationContext {
	projectID := uuid.New()
	compSoftwareID := uuid.New()
	compSensorID := uuid.New()
	hazardHighID := uuid.New()
	hazardLowID := uuid.New()

	return &SectionGenerationContext{
		Project: &Project{
			ID:              projectID,
			TenantID:        uuid.New(),
			MachineName:     "Robot Arm XY-200",
			MachineType:     "industrial_robot",
			Manufacturer:    "TestCorp",
			Description:     "6-axis industrial robot for automotive welding",
			CEMarkingTarget: "2023/1230",
			Status:          ProjectStatusHazardAnalysis,
		},
		Components: []Component{
			{
				ID:               compSoftwareID,
				ProjectID:        projectID,
				Name:             "SafetyPLC-500",
				ComponentType:    ComponentTypeSoftware,
				Version:          "3.2.1",
				Description:      "Safety-rated programmable logic controller firmware",
				IsSafetyRelevant: true,
				IsNetworked:      true,
			},
			{
				ID:               compSensorID,
				ProjectID:        projectID,
				Name:             "ProxSensor-LiDAR",
				ComponentType:    ComponentTypeSensor,
				Version:          "1.0.0",
				Description:      "LiDAR proximity sensor for collision avoidance",
				IsSafetyRelevant: false,
				IsNetworked:      false,
			},
		},
		Hazards: []Hazard{
			{
				ID:             hazardHighID,
				ProjectID:      projectID,
				ComponentID:    compSoftwareID,
				Name:           "Software malfunction causing uncontrolled movement",
				Description:    "Firmware fault leads to unpredictable arm trajectory",
				Category:       "mechanical",
				SubCategory:    "crushing",
				Status:         HazardStatusAssessed,
				LifecyclePhase: "normal_operation",
				AffectedPerson: "operator",
				PossibleHarm:   "Severe crushing injury to operator",
			},
			{
				ID:             hazardLowID,
				ProjectID:      projectID,
				ComponentID:    compSensorID,
				Name:           "Sensor drift causing delayed stop",
				Description:    "Gradual sensor calibration loss reduces reaction time",
				Category:       "electrical",
				SubCategory:    "sensor_failure",
				Status:         HazardStatusIdentified,
				LifecyclePhase: "normal_operation",
				AffectedPerson: "bystander",
				PossibleHarm:   "Minor bruising",
			},
		},
		Assessments: map[uuid.UUID][]RiskAssessment{
			hazardHighID: {
				{
					ID:             uuid.New(),
					HazardID:       hazardHighID,
					Version:        1,
					AssessmentType: AssessmentTypeInitial,
					Severity:       5,
					Exposure:       4,
					Probability:    3,
					Avoidance:      2,
					InherentRisk:   120.0,
					ResidualRisk:   85.0,
					RiskLevel:      RiskLevelHigh,
					IsAcceptable:   false,
				},
			},
			hazardLowID: {
				{
					ID:             uuid.New(),
					HazardID:       hazardLowID,
					Version:        1,
					AssessmentType: AssessmentTypeInitial,
					Severity:       2,
					Exposure:       3,
					Probability:    2,
					Avoidance:      4,
					InherentRisk:   12.0,
					ResidualRisk:   6.0,
					RiskLevel:      RiskLevelLow,
					IsAcceptable:   true,
				},
			},
		},
		Mitigations: map[uuid.UUID][]Mitigation{
			hazardHighID: {
				{
					ID:            uuid.New(),
					HazardID:      hazardHighID,
					ReductionType: ReductionTypeDesign,
					Name:          "Redundant safety controller",
					Description:   "Dual-channel safety PLC with cross-monitoring",
					Status:        MitigationStatusImplemented,
				},
				{
					ID:            uuid.New(),
					HazardID:      hazardHighID,
					ReductionType: ReductionTypeProtective,
					Name:          "Light curtain barrier",
					Description:   "Type 4 safety light curtain around work envelope",
					Status:        MitigationStatusVerified,
				},
			},
			hazardLowID: {
				{
					ID:            uuid.New(),
					HazardID:      hazardLowID,
					ReductionType: ReductionTypeInformation,
					Name:          "Calibration schedule warning",
					Description:   "Automated alert when sensor calibration is overdue",
					Status:        MitigationStatusPlanned,
				},
			},
		},
		Classifications: []RegulatoryClassification{
			{
				ID:                   uuid.New(),
				ProjectID:            projectID,
				Regulation:           RegulationMachineryRegulation,
				ClassificationResult: "Annex I - High-Risk Machinery",
				RiskLevel:            RiskLevelHigh,
				Confidence:           0.92,
			},
		},
		Evidence: []Evidence{
			{
				ID:          uuid.New(),
				ProjectID:   projectID,
				FileName:    "safety_plc_test_report.pdf",
				Description: "Functional safety test report for SafetyPLC-500",
			},
		},
		RAGContext: "",
	}
}

// ============================================================================
// Tests: buildUserPrompt
// ============================================================================

func TestBuildUserPrompt_RiskAssessmentReport(t *testing.T) {
	sctx := newTestSectionContext()
	prompt := buildUserPrompt(sctx, "risk_assessment_report")

	// Must contain project identification
	if !strings.Contains(prompt, "Robot Arm XY-200") {
		t.Error("prompt should contain machine name 'Robot Arm XY-200'")
	}
	if !strings.Contains(prompt, "TestCorp") {
		t.Error("prompt should contain manufacturer 'TestCorp'")
	}

	// Must reference hazard information
	if !strings.Contains(prompt, "uncontrolled movement") || !strings.Contains(prompt, "Software malfunction") {
		t.Error("prompt should contain hazard name or description for high-risk hazard")
	}

	// Must reference risk levels
	if !strings.Contains(prompt, "high") && !strings.Contains(prompt, "High") && !strings.Contains(prompt, "HIGH") {
		t.Error("prompt should reference the high risk level")
	}
}

func TestBuildUserPrompt_ComponentList(t *testing.T) {
	sctx := newTestSectionContext()
	prompt := buildUserPrompt(sctx, "component_list")

	// Must list component names
	if !strings.Contains(prompt, "SafetyPLC-500") {
		t.Error("prompt should contain component name 'SafetyPLC-500'")
	}
	if !strings.Contains(prompt, "ProxSensor-LiDAR") {
		t.Error("prompt should contain component name 'ProxSensor-LiDAR'")
	}

	// Must reference component types
	if !strings.Contains(prompt, "software") && !strings.Contains(prompt, "Software") {
		t.Error("prompt should contain component type 'software'")
	}
	if !strings.Contains(prompt, "sensor") && !strings.Contains(prompt, "Sensor") {
		t.Error("prompt should contain component type 'sensor'")
	}
}

func TestBuildUserPrompt_EmptyProject(t *testing.T) {
	sctx := &SectionGenerationContext{
		Project:         nil,
		Components:      nil,
		Hazards:         nil,
		Assessments:     nil,
		Mitigations:     nil,
		Classifications: nil,
		Evidence:        nil,
		RAGContext:       "",
	}

	// Should not panic on nil/empty data
	prompt := buildUserPrompt(sctx, "general_description")
	if prompt == "" {
		t.Error("buildUserPrompt should return non-empty string even for empty context")
	}
}

func TestBuildUserPrompt_MitigationReport(t *testing.T) {
	sctx := newTestSectionContext()
	prompt := buildUserPrompt(sctx, "mitigation_report")

	// Must reference mitigation names
	if !strings.Contains(prompt, "Redundant safety controller") {
		t.Error("prompt should contain design mitigation 'Redundant safety controller'")
	}
	if !strings.Contains(prompt, "Light curtain barrier") {
		t.Error("prompt should contain protective mitigation 'Light curtain barrier'")
	}
	if !strings.Contains(prompt, "Calibration schedule warning") {
		t.Error("prompt should contain information mitigation 'Calibration schedule warning'")
	}

	// Must reference reduction types
	hasDesign := strings.Contains(prompt, "design") || strings.Contains(prompt, "Design")
	hasProtective := strings.Contains(prompt, "protective") || strings.Contains(prompt, "Protective")
	hasInformation := strings.Contains(prompt, "information") || strings.Contains(prompt, "Information")
	if !hasDesign {
		t.Error("prompt should reference 'design' reduction type")
	}
	if !hasProtective {
		t.Error("prompt should reference 'protective' reduction type")
	}
	if !hasInformation {
		t.Error("prompt should reference 'information' reduction type")
	}
}

func TestBuildUserPrompt_WithRAGContext(t *testing.T) {
	sctx := newTestSectionContext()
	sctx.RAGContext = "According to EN ISO 13849-1:2023, safety-related parts of control systems for machinery shall be designed and constructed using the principles of EN ISO 12100."

	prompt := buildUserPrompt(sctx, "standards_applied")

	if !strings.Contains(prompt, "EN ISO 13849-1") {
		t.Error("prompt should include the RAG context referencing EN ISO 13849-1")
	}
	if !strings.Contains(prompt, "EN ISO 12100") {
		t.Error("prompt should include the RAG context referencing EN ISO 12100")
	}
}

func TestBuildUserPrompt_WithoutRAGContext(t *testing.T) {
	sctx := newTestSectionContext()
	sctx.RAGContext = ""

	prompt := buildUserPrompt(sctx, "standards_applied")

	// Should still produce a valid prompt without RAG context
	if prompt == "" {
		t.Error("prompt should be non-empty even without RAG context")
	}
	// Should still contain the project info
	if !strings.Contains(prompt, "Robot Arm XY-200") {
		t.Error("prompt should still contain machine name when no RAG context")
	}
}

// ============================================================================
// Tests: buildRAGQuery
// ============================================================================

func TestBuildRAGQuery_AllSectionTypes(t *testing.T) {
	sectionTypes := []string{
		"risk_assessment_report",
		"hazard_log_combined",
		"general_description",
		"essential_requirements",
		"design_specifications",
		"test_reports",
		"standards_applied",
		"declaration_of_conformity",
		"ai_intended_purpose",
		"ai_model_description",
		"ai_risk_management",
		"ai_human_oversight",
		"component_list",
		"classification_report",
		"mitigation_report",
		"verification_report",
		"evidence_index",
		"instructions_for_use",
		"monitoring_plan",
	}

	for _, st := range sectionTypes {
		t.Run(st, func(t *testing.T) {
			q := buildRAGQuery(st)
			if q == "" {
				t.Errorf("buildRAGQuery(%q) returned empty string", st)
			}
			// Each query should be at least a few words long to be useful
			if len(q) < 10 {
				t.Errorf("buildRAGQuery(%q) returned suspiciously short query: %q", st, q)
			}
		})
	}
}

func TestBuildRAGQuery_UnknownSectionType(t *testing.T) {
	q := buildRAGQuery("nonexistent_section_type")
	// Should return a generic fallback query rather than an empty string
	// (the function needs some query to send to RAG even for unknown types)
	if q == "" {
		t.Log("buildRAGQuery returned empty for unknown section type (may be acceptable if caller handles this)")
	}
}

func TestBuildRAGQuery_QueriesAreDifferent(t *testing.T) {
	// Different section types should produce different queries for targeted retrieval
	q1 := buildRAGQuery("risk_assessment_report")
	q2 := buildRAGQuery("declaration_of_conformity")
	q3 := buildRAGQuery("monitoring_plan")

	if q1 == q2 {
		t.Error("risk_assessment_report and declaration_of_conformity should have different RAG queries")
	}
	if q2 == q3 {
		t.Error("declaration_of_conformity and monitoring_plan should have different RAG queries")
	}
	if q1 == q3 {
		t.Error("risk_assessment_report and monitoring_plan should have different RAG queries")
	}
}

// ============================================================================
// Tests: sectionSystemPrompts
// ============================================================================

func TestSectionSystemPrompts_Coverage(t *testing.T) {
	requiredTypes := []string{
		"risk_assessment_report",
		"hazard_log_combined",
		"general_description",
		"essential_requirements",
		"design_specifications",
		"test_reports",
		"standards_applied",
		"declaration_of_conformity",
		"component_list",
		"classification_report",
		"mitigation_report",
		"verification_report",
		"evidence_index",
		"instructions_for_use",
		"monitoring_plan",
	}

	for _, st := range requiredTypes {
		t.Run(st, func(t *testing.T) {
			prompt, ok := sectionSystemPrompts[st]
			if !ok {
				t.Errorf("sectionSystemPrompts missing entry for %q", st)
				return
			}
			if prompt == "" {
				t.Errorf("sectionSystemPrompts[%q] is empty", st)
			}
			// System prompts should contain meaningful instruction text
			if len(prompt) < 50 {
				t.Errorf("sectionSystemPrompts[%q] is suspiciously short (%d chars)", st, len(prompt))
			}
		})
	}
}

func TestSectionSystemPrompts_ContainRoleInstruction(t *testing.T) {
	// Each system prompt should instruct the LLM about its role as a compliance expert.
	// Prompts are in German, so check for both German and English keywords.
	keywords := []string{
		"expert", "engineer", "compliance", "technical", "documentation", "safety",
		"generate", "write", "create", "produce",
		// German equivalents
		"experte", "ingenieur", "erstelle", "beschreibe", "dokumentation", "sicherheit",
		"risikobeurteilung", "konformit", "norm", "verordnung", "richtlinie",
		"gefaehrdung", "massnahm", "verifikation", "uebersicht", "protokoll",
		"abschnitt", "bericht", "maschin",
	}
	for st, prompt := range sectionSystemPrompts {
		t.Run(st, func(t *testing.T) {
			lower := strings.ToLower(prompt)
			found := false
			for _, kw := range keywords {
				if strings.Contains(lower, kw) {
					found = true
					break
				}
			}
			if !found {
				t.Errorf("sectionSystemPrompts[%q] does not appear to contain a role or task instruction", st)
			}
		})
	}
}

// ============================================================================
// Tests: buildUserPrompt — additional section types
// ============================================================================

func TestBuildUserPrompt_ClassificationReport(t *testing.T) {
	sctx := newTestSectionContext()
	prompt := buildUserPrompt(sctx, "classification_report")

	// Must reference classification data
	if !strings.Contains(prompt, "machinery_regulation") && !strings.Contains(prompt, "Machinery") && !strings.Contains(prompt, "machinery") {
		t.Error("prompt should reference the machinery regulation classification")
	}
}

func TestBuildUserPrompt_EvidenceIndex(t *testing.T) {
	sctx := newTestSectionContext()
	prompt := buildUserPrompt(sctx, "evidence_index")

	// Must reference evidence files
	if !strings.Contains(prompt, "safety_plc_test_report.pdf") {
		t.Error("prompt should reference evidence file name 'safety_plc_test_report.pdf'")
	}
}

func TestBuildUserPrompt_GeneralDescription(t *testing.T) {
	sctx := newTestSectionContext()
	prompt := buildUserPrompt(sctx, "general_description")

	// Must contain machine description
	if !strings.Contains(prompt, "Robot Arm XY-200") {
		t.Error("prompt should contain machine name")
	}
	if !strings.Contains(prompt, "industrial_robot") && !strings.Contains(prompt, "industrial robot") {
		t.Error("prompt should contain machine type")
	}
	if !strings.Contains(prompt, "automotive welding") && !strings.Contains(prompt, "6-axis") {
		t.Error("prompt should reference machine description content")
	}
}

func TestBuildUserPrompt_DeclarationOfConformity(t *testing.T) {
	sctx := newTestSectionContext()
	prompt := buildUserPrompt(sctx, "declaration_of_conformity")

	// Declaration needs manufacturer and CE target
	if !strings.Contains(prompt, "TestCorp") {
		t.Error("prompt should contain manufacturer for declaration of conformity")
	}
	if !strings.Contains(prompt, "2023/1230") && !strings.Contains(prompt, "CE") && !strings.Contains(prompt, "ce_marking") {
		t.Error("prompt should reference CE marking target or regulation for declaration")
	}
}

// ============================================================================
// Tests: Error handling & edge cases
// ============================================================================

func TestBuildUserPrompt_NilContext(t *testing.T) {
	// Should not panic on completely nil context
	prompt := buildUserPrompt(nil, "risk_assessment_report")
	if prompt == "" {
		t.Error("buildUserPrompt should return non-empty string for nil context")
	}
	if !strings.Contains(prompt, "Keine Projektdaten") {
		t.Error("nil context prompt should contain fallback text 'Keine Projektdaten'")
	}
}

func TestBuildUserPrompt_NilProject(t *testing.T) {
	sctx := &SectionGenerationContext{
		Project: nil,
	}
	prompt := buildUserPrompt(sctx, "general_description")
	if prompt == "" {
		t.Error("buildUserPrompt should return non-empty string for nil project")
	}
	if !strings.Contains(prompt, "Keine Projektdaten") {
		t.Error("nil project prompt should contain fallback text 'Keine Projektdaten'")
	}
}

func TestBuildUserPrompt_EmptyComponents(t *testing.T) {
	sctx := &SectionGenerationContext{
		Project:    &Project{MachineName: "Test", Manufacturer: "Corp"},
		Components: []Component{},
		Hazards:    nil,
	}
	prompt := buildUserPrompt(sctx, "component_list")
	if !strings.Contains(prompt, "Test") {
		t.Error("prompt should contain machine name even with empty components")
	}
}

func TestBuildUserPrompt_EmptyHazards(t *testing.T) {
	sctx := &SectionGenerationContext{
		Project: &Project{MachineName: "Test", Manufacturer: "Corp"},
		Hazards: []Hazard{},
	}
	prompt := buildUserPrompt(sctx, "hazard_log_combined")
	if prompt == "" {
		t.Error("prompt should be non-empty even with no hazards")
	}
}

func TestBuildUserPrompt_EmptyClassifications(t *testing.T) {
	sctx := &SectionGenerationContext{
		Project:         &Project{MachineName: "Test", Manufacturer: "Corp"},
		Classifications: []RegulatoryClassification{},
	}
	prompt := buildUserPrompt(sctx, "classification_report")
	if prompt == "" {
		t.Error("prompt should be non-empty even with no classifications")
	}
}

func TestBuildUserPrompt_EmptyEvidence(t *testing.T) {
	sctx := &SectionGenerationContext{
		Project:  &Project{MachineName: "Test", Manufacturer: "Corp"},
		Evidence: []Evidence{},
	}
	prompt := buildUserPrompt(sctx, "evidence_index")
	if prompt == "" {
		t.Error("prompt should be non-empty even with no evidence")
	}
}

func TestGetSystemPrompt_UnknownType(t *testing.T) {
	prompt := getSystemPrompt("totally_unknown_section")
	if prompt == "" {
		t.Error("getSystemPrompt should return a fallback for unknown types")
	}
	// Fallback should still be a useful CE expert prompt
	lower := strings.ToLower(prompt)
	if !strings.Contains(lower, "ce") && !strings.Contains(lower, "experte") && !strings.Contains(lower, "dokumentation") {
		t.Error("fallback system prompt should reference CE or documentation expertise")
	}
}

func TestGetSystemPrompt_AllKnownTypes(t *testing.T) {
	knownTypes := []string{
		"risk_assessment_report", "hazard_log_combined", "general_description",
		"essential_requirements", "design_specifications", "test_reports",
		"standards_applied", "declaration_of_conformity", "component_list",
		"classification_report", "mitigation_report", "verification_report",
		"evidence_index", "instructions_for_use", "monitoring_plan",
		"ai_intended_purpose", "ai_model_description", "ai_risk_management",
		"ai_human_oversight",
	}
	for _, st := range knownTypes {
		t.Run(st, func(t *testing.T) {
			prompt := getSystemPrompt(st)
			if prompt == "" {
				t.Errorf("getSystemPrompt(%q) returned empty string", st)
			}
		})
	}
}

func TestBuildUserPrompt_AIIntendedPurpose(t *testing.T) {
	sctx := newTestSectionContext()
	prompt := buildUserPrompt(sctx, "ai_intended_purpose")
	if prompt == "" {
		t.Error("prompt should be non-empty for ai_intended_purpose")
	}
	if !strings.Contains(prompt, "Robot Arm XY-200") {
		t.Error("AI intended purpose prompt should contain machine name")
	}
}

func TestBuildUserPrompt_AIHumanOversight(t *testing.T) {
	sctx := newTestSectionContext()
	prompt := buildUserPrompt(sctx, "ai_human_oversight")
	if prompt == "" {
		t.Error("prompt should be non-empty for ai_human_oversight")
	}
	if !strings.Contains(prompt, "Robot Arm XY-200") {
		t.Error("AI human oversight prompt should contain machine name")
	}
}

func TestBuildUserPrompt_MultipleHazardAssessments(t *testing.T) {
	sctx := newTestSectionContext()

	// Find the high-risk hazard ID from the assessments map
	var highHazardID uuid.UUID
	for hid, assessments := range sctx.Assessments {
		if len(assessments) > 0 && assessments[0].RiskLevel == RiskLevelHigh {
			highHazardID = hid
			break
		}
	}

	if highHazardID != uuid.Nil {
		// Add a second assessment version (post-mitigation) for the high-risk hazard
		sctx.Assessments[highHazardID] = append(sctx.Assessments[highHazardID], RiskAssessment{
			ID:             uuid.New(),
			HazardID:       highHazardID,
			Version:        2,
			AssessmentType: AssessmentTypePostMitigation,
			Severity:       5,
			Exposure:       4,
			Probability:    1,
			Avoidance:      4,
			InherentRisk:   120.0,
			ResidualRisk:   20.0,
			RiskLevel:      RiskLevelMedium,
			IsAcceptable:   true,
		})
	}

	prompt := buildUserPrompt(sctx, "risk_assessment_report")
	if prompt == "" {
		t.Error("prompt should not be empty with multiple assessments")
	}
}
