package iace

import (
	"testing"

	"github.com/google/uuid"
)

func TestGetBuiltinHazardLibrary_EntryCount(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	if len(entries) < 150 {
		t.Fatalf("GetBuiltinHazardLibrary returned %d entries, want at least 150", len(entries))
	}
}

func TestGetBuiltinHazardLibrary_AllBuiltinAndNoTenant(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	for i, e := range entries {
		if !e.IsBuiltin {
			t.Errorf("entries[%d] (%s): IsBuiltin = false, want true", i, e.Name)
		}
		if e.TenantID != nil {
			t.Errorf("entries[%d] (%s): TenantID = %v, want nil", i, e.Name, e.TenantID)
		}
	}
}

func TestGetBuiltinHazardLibrary_UniqueNonZeroUUIDs(t *testing.T) {
	entries := GetBuiltinHazardLibrary()
	seen := make(map[uuid.UUID]string)

	for i, e := range entries {
		if e.ID == uuid.Nil {
			t.Errorf("entries[%d] (%s): ID is zero UUID", i, e.Name)
		}
		if prev, exists := seen[e.ID]; exists {
			t.Errorf("entries[%d] (%s): duplicate UUID %s, same as %q", i, e.Name, e.ID, prev)
		}
		seen[e.ID] = e.Name
	}
}

func TestGetBuiltinHazardLibrary_AllCategoriesPresent(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	// All expected categories (original AI/SW + extended ISO 12100 physical)
	expectedCategories := map[string]bool{
		// Original AI/cyber categories
		"false_classification":       false,
		"timing_error":               false,
		"data_poisoning":             false,
		"model_drift":                false,
		"sensor_spoofing":            false,
		"communication_failure":      false,
		"unauthorized_access":        false,
		"firmware_corruption":        false,
		"safety_boundary_violation":  false,
		"mode_confusion":             false,
		"unintended_bias":            false,
		"update_failure":             false,
		// Extended SW/HW categories
		"software_fault":             false,
		"hmi_error":                  false,
		"mechanical_hazard":          false,
		"electrical_hazard":          false,
		"thermal_hazard":             false,
		"emc_hazard":                 false,
		"configuration_error":        false,
		"safety_function_failure":    false,
		"logging_audit_failure":      false,
		"integration_error":          false,
		"environmental_hazard":       false,
		"maintenance_hazard":         false,
		// ISO 12100 physical categories
		"pneumatic_hydraulic":        false,
		"noise_vibration":            false,
		"ergonomic":                  false,
		"material_environmental":     false,
	}

	for _, e := range entries {
		if _, ok := expectedCategories[e.Category]; !ok {
			t.Errorf("unexpected category %q in entry %q", e.Category, e.Name)
		}
		expectedCategories[e.Category] = true
	}

	for cat, found := range expectedCategories {
		if !found {
			t.Errorf("expected category %q not found in any entry", cat)
		}
	}
}

func TestGetBuiltinHazardLibrary_CategoryCounts(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	actualCounts := make(map[string]int)
	for _, e := range entries {
		actualCounts[e.Category]++
	}

	// Each category must have at least 1 entry
	for cat, count := range actualCounts {
		if count < 1 {
			t.Errorf("category %q: count = %d, want >= 1", cat, count)
		}
	}

	// ISO 12100 physical categories must have substantial coverage
	minCounts := map[string]int{
		"mechanical_hazard":   10,
		"electrical_hazard":   6,
		"thermal_hazard":      4,
		"pneumatic_hydraulic": 6,
		"noise_vibration":     4,
	}
	for cat, minCount := range minCounts {
		if actualCounts[cat] < minCount {
			t.Errorf("category %q: count = %d, want >= %d", cat, actualCounts[cat], minCount)
		}
	}
}

func TestGetBuiltinHazardLibrary_SeverityRange(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	for i, e := range entries {
		if e.DefaultSeverity < 1 || e.DefaultSeverity > 5 {
			t.Errorf("entries[%d] (%s): DefaultSeverity = %d, want 1-5", i, e.Name, e.DefaultSeverity)
		}
	}
}

func TestGetBuiltinHazardLibrary_ProbabilityRange(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	for i, e := range entries {
		if e.DefaultProbability < 1 || e.DefaultProbability > 5 {
			t.Errorf("entries[%d] (%s): DefaultProbability = %d, want 1-5", i, e.Name, e.DefaultProbability)
		}
	}
}

func TestGetBuiltinHazardLibrary_ExposureAndAvoidanceRange(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	for i, e := range entries {
		if e.DefaultExposure < 0 || e.DefaultExposure > 5 {
			t.Errorf("entries[%d] (%s): DefaultExposure = %d, want 0-5", i, e.Name, e.DefaultExposure)
		}
		if e.DefaultAvoidance < 0 || e.DefaultAvoidance > 5 {
			t.Errorf("entries[%d] (%s): DefaultAvoidance = %d, want 0-5", i, e.Name, e.DefaultAvoidance)
		}
	}
}

func TestGetBuiltinHazardLibrary_NonEmptyFields(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	for i, e := range entries {
		if e.Name == "" {
			t.Errorf("entries[%d]: Name is empty", i)
		}
		if e.Category == "" {
			t.Errorf("entries[%d] (%s): Category is empty", i, e.Name)
		}
		if len(e.ApplicableComponentTypes) == 0 {
			t.Errorf("entries[%d] (%s): ApplicableComponentTypes is empty", i, e.Name)
		}
	}
}

func TestHazardUUID_Deterministic(t *testing.T) {
	tests := []struct {
		category string
		index    int
	}{
		{"false_classification", 1},
		{"timing_error", 2},
		{"data_poisoning", 1},
		{"mechanical_hazard", 7},
		{"pneumatic_hydraulic", 1},
	}

	for _, tt := range tests {
		t.Run(tt.category, func(t *testing.T) {
			id1 := hazardUUID(tt.category, tt.index)
			id2 := hazardUUID(tt.category, tt.index)

			if id1 != id2 {
				t.Errorf("hazardUUID(%q, %d) not deterministic: %s != %s", tt.category, tt.index, id1, id2)
			}
			if id1 == uuid.Nil {
				t.Errorf("hazardUUID(%q, %d) returned zero UUID", tt.category, tt.index)
			}
		})
	}
}

func TestHazardUUID_DifferentInputsProduceDifferentUUIDs(t *testing.T) {
	tests := []struct {
		cat1 string
		idx1 int
		cat2 string
		idx2 int
	}{
		{"false_classification", 1, "false_classification", 2},
		{"false_classification", 1, "timing_error", 1},
		{"mechanical_hazard", 7, "mechanical_hazard", 8},
		{"pneumatic_hydraulic", 1, "noise_vibration", 1},
	}

	for _, tt := range tests {
		name := tt.cat1 + "_" + tt.cat2
		t.Run(name, func(t *testing.T) {
			id1 := hazardUUID(tt.cat1, tt.idx1)
			id2 := hazardUUID(tt.cat2, tt.idx2)

			if id1 == id2 {
				t.Errorf("hazardUUID(%q,%d) == hazardUUID(%q,%d): %s", tt.cat1, tt.idx1, tt.cat2, tt.idx2, id1)
			}
		})
	}
}

func TestGetBuiltinHazardLibrary_CreatedAtSet(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	for i, e := range entries {
		if e.CreatedAt.IsZero() {
			t.Errorf("entries[%d] (%s): CreatedAt is zero", i, e.Name)
		}
	}
}

func TestGetBuiltinHazardLibrary_DescriptionPresent(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	for i, e := range entries {
		if e.Description == "" {
			t.Errorf("entries[%d] (%s): Description is empty", i, e.Name)
		}
	}
}

func TestGetBuiltinHazardLibrary_SuggestedMitigationsPresent(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	for i, e := range entries {
		if e.SuggestedMitigations == nil || len(e.SuggestedMitigations) == 0 {
			t.Errorf("entries[%d] (%s): SuggestedMitigations is nil/empty", i, e.Name)
		}
	}
}

func TestGetBuiltinHazardLibrary_ApplicableComponentTypesAreValid(t *testing.T) {
	entries := GetBuiltinHazardLibrary()

	validTypes := map[string]bool{
		string(ComponentTypeSoftware):   true,
		string(ComponentTypeFirmware):   true,
		string(ComponentTypeAIModel):    true,
		string(ComponentTypeHMI):        true,
		string(ComponentTypeSensor):     true,
		string(ComponentTypeActuator):   true,
		string(ComponentTypeController): true,
		string(ComponentTypeNetwork):    true,
		string(ComponentTypeMechanical): true,
		string(ComponentTypeElectrical): true,
		string(ComponentTypeOther):      true,
	}

	for i, e := range entries {
		for _, ct := range e.ApplicableComponentTypes {
			if !validTypes[ct] {
				t.Errorf("entries[%d] (%s): invalid component type %q in ApplicableComponentTypes", i, e.Name, ct)
			}
		}
	}
}

func TestGetBuiltinHazardLibrary_ConsistentAcrossCalls(t *testing.T) {
	entries1 := GetBuiltinHazardLibrary()
	entries2 := GetBuiltinHazardLibrary()

	if len(entries1) != len(entries2) {
		t.Fatalf("inconsistent lengths: %d vs %d", len(entries1), len(entries2))
	}

	for i := range entries1 {
		if entries1[i].ID != entries2[i].ID {
			t.Errorf("entries[%d]: ID mismatch across calls: %s vs %s", i, entries1[i].ID, entries2[i].ID)
		}
		if entries1[i].Name != entries2[i].Name {
			t.Errorf("entries[%d]: Name mismatch across calls: %q vs %q", i, entries1[i].Name, entries2[i].Name)
		}
	}
}

// TestGetProtectiveMeasureLibrary_EntryCount verifies the protective measures library size
func TestGetProtectiveMeasureLibrary_EntryCount(t *testing.T) {
	entries := GetProtectiveMeasureLibrary()

	if len(entries) < 150 {
		t.Fatalf("GetProtectiveMeasureLibrary returned %d entries, want at least 150", len(entries))
	}
}

// TestGetProtectiveMeasureLibrary_AllTypesPresent verifies all 3 reduction types exist
func TestGetProtectiveMeasureLibrary_AllTypesPresent(t *testing.T) {
	entries := GetProtectiveMeasureLibrary()

	typeCounts := map[string]int{}
	for _, e := range entries {
		typeCounts[e.ReductionType]++
	}

	for _, rt := range []string{"design", "information"} {
		if typeCounts[rt] < 10 {
			t.Errorf("reduction type %q: count = %d, want >= 10", rt, typeCounts[rt])
		}
	}
	// Accept both "protection" and "protective" naming
	protCount := typeCounts["protection"] + typeCounts["protective"]
	if protCount < 10 {
		t.Errorf("reduction type protection/protective: count = %d, want >= 10", protCount)
	}
}

// TestGetProtectiveMeasureLibrary_UniqueIDs verifies all measure IDs are unique
func TestGetProtectiveMeasureLibrary_UniqueIDs(t *testing.T) {
	entries := GetProtectiveMeasureLibrary()
	seen := make(map[string]string)

	for i, e := range entries {
		if e.ID == "" {
			t.Errorf("entries[%d] (%s): ID is empty", i, e.Name)
			continue
		}
		if prev, exists := seen[e.ID]; exists {
			t.Errorf("entries[%d] (%s): duplicate ID %q, same as %q", i, e.Name, e.ID, prev)
		}
		seen[e.ID] = e.Name
	}
}

// TestGetProtectiveMeasureLibrary_NonEmptyFields verifies required fields are filled
func TestGetProtectiveMeasureLibrary_NonEmptyFields(t *testing.T) {
	entries := GetProtectiveMeasureLibrary()

	for i, e := range entries {
		if e.Name == "" {
			t.Errorf("entries[%d]: Name is empty", i)
		}
		if e.Description == "" {
			t.Errorf("entries[%d] (%s): Description is empty", i, e.Name)
		}
		if e.ReductionType == "" {
			t.Errorf("entries[%d] (%s): ReductionType is empty", i, e.Name)
		}
	}
}

// TestGetProtectiveMeasureLibrary_ValidReductionTypes verifies reduction types are valid
func TestGetProtectiveMeasureLibrary_ValidReductionTypes(t *testing.T) {
	entries := GetProtectiveMeasureLibrary()
	validTypes := map[string]bool{"design": true, "protection": true, "protective": true, "information": true}

	for i, e := range entries {
		if !validTypes[e.ReductionType] {
			t.Errorf("entries[%d] (%s): invalid ReductionType %q", i, e.Name, e.ReductionType)
		}
	}
}

// TestGetControlsLibrary_EntryCount verifies the controls library size
func TestGetControlsLibrary_EntryCount(t *testing.T) {
	entries := GetControlsLibrary()

	if len(entries) < 30 {
		t.Fatalf("GetControlsLibrary returned %d entries, want at least 30", len(entries))
	}
}
