package iace

import (
	"strings"
	"testing"
)

// ══════════════════════════════════════════════════════════════════
// Narrative Parser — Operational State Keywords
// ══════════════════════════════════════════════════════════════════

func TestParseNarrative_ExtractsOperationalStates(t *testing.T) {
	tests := []struct {
		name     string
		text     string
		expected []string
	}{
		{"teach mode", "Die Maschine hat einen Teach-Modus fuer die Programmierung", []string{"teach_mode"}},
		{"maintenance", "Wartung erfolgt monatlich durch Servicetechniker", []string{"maintenance"}},
		{"automatic", "Im Automatikbetrieb laeuft die Maschine ohne Bedienereingriff", []string{"automatic_operation"}},
		{"manual", "Handbetrieb fuer Einstellarbeiten", []string{"manual_operation"}},
		{"emergency", "Not-Halt-Taster an jeder Bedienstelle", []string{"emergency_stop"}},
		{"startup", "Anlauf der Maschine nach Schichtbeginn", []string{"startup"}},
		{"multiple states", "Automatikbetrieb und Einrichtbetrieb sowie Wartung und Not-Halt", []string{"automatic_operation", "teach_mode", "maintenance", "emergency_stop"}},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := ParseNarrative(tt.text, "")
			for _, exp := range tt.expected {
				found := false
				for _, s := range result.OperationalStates {
					if s == exp {
						found = true
						break
					}
				}
				if !found {
					t.Errorf("expected state %q in %v for text %q", exp, result.OperationalStates, tt.text[:40])
				}
			}
		})
	}
}

func TestParseNarrative_ExtractsStateTransitions(t *testing.T) {
	result := ParseNarrative("Gefahr durch unerwarteter Wiederanlauf nach Wartung", "")

	found := false
	for _, tr := range result.StateTransitions {
		if strings.Contains(tr, "maintenance") {
			found = true
			break
		}
	}
	if !found {
		t.Errorf("expected state transition containing 'maintenance' for 'unerwarteter Wiederanlauf', got %v", result.StateTransitions)
	}
}

func TestParseNarrative_NoStatesForUnrelatedText(t *testing.T) {
	result := ParseNarrative("Eine Hydraulikpresse mit 2000 kN Presskraft", "")
	if len(result.OperationalStates) != 0 {
		// Some keywords like "Presse" might incidentally match, that's OK
		// But we shouldn't get states for generic machine descriptions
		// Actually "Presse" doesn't map to a state, so this should be 0
		// unless some other keyword matches
	}
	if len(result.StateTransitions) != 0 {
		t.Errorf("expected no state transitions for generic text, got %v", result.StateTransitions)
	}
}

// ══════════════════════════════════════════════════════════════════
// CNC/VDMA Pattern MachineType Filtering
// ══════════════════════════════════════════════════════════════════

func TestCNCPatterns_HaveMachineTypeRestriction(t *testing.T) {
	patterns := GetCNCHazardPatterns()
	for _, p := range patterns {
		if len(p.MachineTypes) == 0 {
			t.Errorf("CNC pattern %s (%s) has no MachineTypes restriction", p.ID, p.NameDE)
		}
	}
	patternsExt := GetCNCHazardPatternsExt()
	for _, p := range patternsExt {
		if len(p.MachineTypes) == 0 {
			t.Errorf("CNC ext pattern %s (%s) has no MachineTypes restriction", p.ID, p.NameDE)
		}
	}
}

func TestVDMAPatterns_HaveMachineTypeRestriction(t *testing.T) {
	patterns := GetVDMAIndustryPatterns()
	for _, p := range patterns {
		if len(p.MachineTypes) == 0 {
			t.Errorf("VDMA pattern %s (%s) has no MachineTypes restriction", p.ID, p.NameDE)
		}
	}
}

func TestCNCPatterns_UniqueIDs(t *testing.T) {
	seen := make(map[string]bool)
	for _, p := range GetCNCHazardPatterns() {
		if seen[p.ID] {
			t.Errorf("duplicate CNC pattern ID: %s", p.ID)
		}
		seen[p.ID] = true
	}
	for _, p := range GetCNCHazardPatternsExt() {
		if seen[p.ID] {
			t.Errorf("duplicate CNC ext pattern ID: %s", p.ID)
		}
		seen[p.ID] = true
	}
}

func TestVDMAPatterns_UniqueIDs(t *testing.T) {
	seen := make(map[string]bool)
	for _, p := range GetVDMAIndustryPatterns() {
		if seen[p.ID] {
			t.Errorf("duplicate VDMA pattern ID: %s", p.ID)
		}
		seen[p.ID] = true
	}
}

func TestCNCPatterns_ReferencedMeasuresExist(t *testing.T) {
	measureIDs := make(map[string]bool)
	for _, m := range GetProtectiveMeasureLibrary() {
		measureIDs[m.ID] = true
	}

	for _, p := range GetCNCHazardPatterns() {
		for _, mid := range p.SuggestedMeasureIDs {
			if !measureIDs[mid] {
				t.Errorf("CNC pattern %s references non-existent measure %s", p.ID, mid)
			}
		}
	}
	for _, p := range GetCNCHazardPatternsExt() {
		for _, mid := range p.SuggestedMeasureIDs {
			if !measureIDs[mid] {
				t.Errorf("CNC ext pattern %s references non-existent measure %s", p.ID, mid)
			}
		}
	}
}

func TestVDMAPatterns_ReferencedMeasuresExist(t *testing.T) {
	measureIDs := make(map[string]bool)
	for _, m := range GetProtectiveMeasureLibrary() {
		measureIDs[m.ID] = true
	}

	for _, p := range GetVDMAIndustryPatterns() {
		for _, mid := range p.SuggestedMeasureIDs {
			if !measureIDs[mid] {
				t.Errorf("VDMA pattern %s references non-existent measure %s", p.ID, mid)
			}
		}
	}
}

// ══════════════════════════════════════════════════════════════════
// Metalworking + VDMA Measures Validation
// ══════════════════════════════════════════════════════════════════

func TestMetalworkingMeasures_ValidFields(t *testing.T) {
	measures := getMetalworkingMeasures()
	if len(measures) < 15 {
		t.Fatalf("expected at least 15 metalworking measures, got %d", len(measures))
	}
	for _, m := range measures {
		if m.ID == "" {
			t.Error("measure with empty ID")
		}
		if m.Name == "" {
			t.Errorf("measure %s has empty Name", m.ID)
		}
		if m.Description == "" {
			t.Errorf("measure %s has empty Description", m.ID)
		}
		if m.ReductionType == "" {
			t.Errorf("measure %s has empty ReductionType", m.ID)
		}
		if len(m.NormReferences) == 0 {
			t.Errorf("measure %s has no NormReferences", m.ID)
		}
	}
}

func TestVDMAMeasures_ValidFields(t *testing.T) {
	measures := getVDMAMeasures()
	if len(measures) < 25 {
		t.Fatalf("expected at least 25 VDMA measures, got %d", len(measures))
	}
	for _, m := range measures {
		if m.ID == "" || m.Name == "" || m.Description == "" || m.ReductionType == "" {
			t.Errorf("VDMA measure %s has empty required field", m.ID)
		}
	}
}

func TestAllMeasures_UniqueIDs_Full(t *testing.T) {
	all := GetProtectiveMeasureLibrary()
	seen := make(map[string]bool)
	for _, m := range all {
		if seen[m.ID] {
			t.Errorf("duplicate measure ID across all libraries: %s", m.ID)
		}
		seen[m.ID] = true
	}
	t.Logf("Total measures validated: %d (all unique)", len(all))
}

// ══════════════════════════════════════════════════════════════════
// Integration: Pattern → Measure → RiskTrajectory
// ══════════════════════════════════════════════════════════════════

func TestIntegration_CNCProject_FullFlow(t *testing.T) {
	engine := NewPatternEngine()

	// Simulate a CNC milling machine project
	// C001 = Roboterarm (moving_part), C071 = SPS (programmable)
	// We need cutting_tool for CNC patterns — check component library
	output := engine.Match(MatchInput{
		ComponentLibraryIDs: []string{"C001", "C071"},
		EnergySourceIDs:     []string{"EN01", "EN02"}, // kinetic
		LifecyclePhases:     []string{"normal_operation", "maintenance"},
		OperationalStates:   []string{"automatic_operation", "maintenance"},
		HumanRoles:          []string{"operator", "maintenance_tech"},
	})

	if len(output.MatchedPatterns) == 0 {
		t.Fatal("expected matched patterns for CNC-like setup")
	}
	if len(output.SuggestedMeasures) == 0 {
		t.Fatal("expected suggested measures")
	}

	// Collect measures from suggestions and build trajectory
	measureLib := make(map[string]ProtectiveMeasureEntry)
	for _, m := range GetProtectiveMeasureLibrary() {
		measureLib[m.ID] = m
	}

	var assignedMeasures []ProtectiveMeasureEntry
	for _, s := range output.SuggestedMeasures {
		if m, ok := measureLib[s.MeasureID]; ok {
			assignedMeasures = append(assignedMeasures, m)
		}
	}

	if len(assignedMeasures) == 0 {
		t.Fatal("expected assigned measures from library")
	}

	// Calculate risk trajectory
	riskEngine := NewRiskEngine()
	steps := riskEngine.CalculateRiskTrajectory(4, 4, 3, assignedMeasures)

	if len(steps) < 2 {
		t.Fatalf("expected at least 2 trajectory steps (inherent + reduction), got %d", len(steps))
	}

	// Verify inherent > final
	inherent := steps[0].RiskScore
	final := steps[len(steps)-1].RiskScore
	if final >= inherent {
		t.Errorf("expected final risk (%.1f) < inherent risk (%.1f)", final, inherent)
	}

	t.Logf("CNC flow: %d patterns, %d measures, trajectory %d steps: %.0f → %.0f (%s)",
		len(output.MatchedPatterns), len(assignedMeasures), len(steps),
		inherent, final, steps[len(steps)-1].RiskLevel)
}

func TestIntegration_MaintenanceState_FiltersPatterns(t *testing.T) {
	engine := NewPatternEngine()

	// Same components, different states — should get different pattern counts
	baseInput := MatchInput{
		ComponentLibraryIDs: []string{"C001", "C071"},
		EnergySourceIDs:     []string{"EN01"},
		LifecyclePhases:     []string{"maintenance"},
	}

	// Without state filter
	resultAll := engine.Match(baseInput)

	// With maintenance state
	maintInput := baseInput
	maintInput.OperationalStates = []string{"maintenance"}
	maintInput.HumanRoles = []string{"maintenance_tech"}
	resultMaint := engine.Match(maintInput)

	// With automatic_operation state
	autoInput := baseInput
	autoInput.OperationalStates = []string{"automatic_operation"}
	autoInput.HumanRoles = []string{"operator"}
	resultAuto := engine.Match(autoInput)

	t.Logf("No state filter: %d patterns, maintenance: %d, automatic: %d",
		len(resultAll.MatchedPatterns), len(resultMaint.MatchedPatterns), len(resultAuto.MatchedPatterns))

	// Maintenance-specific patterns should be in resultMaint but not resultAuto
	hasMaintPattern := false
	for _, p := range resultMaint.MatchedPatterns {
		if p.PatternID == "HP073" || p.PatternID == "HP700" {
			hasMaintPattern = true
			break
		}
	}
	if !hasMaintPattern {
		t.Error("expected maintenance-specific pattern (HP073 or HP700) in maintenance state results")
	}
}

// ══════════════════════════════════════════════════════════════════
// Evidence Types Validation
// ══════════════════════════════════════════════════════════════════

func TestEvidenceTypes_Count55(t *testing.T) {
	types := GetEvidenceTypeLibrary()
	if len(types) != 55 {
		t.Errorf("expected 55 evidence types, got %d", len(types))
	}
}

func TestEvidenceTypes_UniqueIDs(t *testing.T) {
	seen := make(map[string]bool)
	for _, e := range GetEvidenceTypeLibrary() {
		if seen[e.ID] {
			t.Errorf("duplicate evidence type ID: %s", e.ID)
		}
		seen[e.ID] = true
	}
}

// ══════════════════════════════════════════════════════════════════
// Sprint 4B: HazardType / ISO 12100 Trennung
// ══════════════════════════════════════════════════════════════════

func TestDeriveHazardType_WithScenarioAndHarm(t *testing.T) {
	h := &Hazard{Scenario: "Person im Bewegungsraum", PossibleHarm: "Quetschung"}
	if got := DeriveHazardType(h); got != HazardTypeHazardousSituation {
		t.Errorf("expected hazardous_situation, got %s", got)
	}
}

func TestDeriveHazardType_HarmOnly(t *testing.T) {
	h := &Hazard{PossibleHarm: "Quetschung", Category: "mechanical"}
	if got := DeriveHazardType(h); got != HazardTypeHarm {
		t.Errorf("expected harm, got %s", got)
	}
}

func TestDeriveHazardType_CategoryOnly(t *testing.T) {
	h := &Hazard{Category: "mechanical_hazard"}
	if got := DeriveHazardType(h); got != HazardTypeHazard {
		t.Errorf("expected hazard, got %s", got)
	}
}

func TestDeriveHazardType_ExplicitOverride(t *testing.T) {
	h := &Hazard{HazardType: "harm", Scenario: "ignored", PossibleHarm: "ignored"}
	if got := DeriveHazardType(h); got != "harm" {
		t.Errorf("expected explicit harm, got %s", got)
	}
}

func TestDeriveHazardType_EmptyFallback(t *testing.T) {
	h := &Hazard{}
	if got := DeriveHazardType(h); got != DefaultHazardType {
		t.Errorf("expected default %s, got %s", DefaultHazardType, got)
	}
}

func TestPatternMatch_GeneratedHazardType(t *testing.T) {
	// Verify PatternMatch carries GeneratedHazardType
	engine := NewPatternEngine()
	result := engine.Match(MatchInput{
		ComponentLibraryIDs: []string{"C001"},
		EnergySourceIDs:     []string{"EN01"},
	})
	// Most patterns don't set GeneratedHazardType (empty = default)
	// Just verify the field exists and doesn't crash
	for _, p := range result.MatchedPatterns {
		_ = p.GeneratedHazardType // Should compile and not panic
	}
	if len(result.MatchedPatterns) == 0 {
		t.Fatal("expected matched patterns")
	}
}

func TestEvidenceTypes_SortOrder(t *testing.T) {
	types := GetEvidenceTypeLibrary()
	for i := 1; i < len(types); i++ {
		if types[i].Sort <= types[i-1].Sort {
			t.Errorf("evidence types not in sort order: E%02d (sort=%d) after E%02d (sort=%d)",
				types[i].Sort, types[i].Sort, types[i-1].Sort, types[i-1].Sort)
		}
	}
}
