package ucca

import (
	"strings"
	"testing"
)

func TestLoadControlPatterns_ValidFiles(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if idx == nil {
		t.Fatal("Expected non-nil index")
	}
	if len(idx.All) != 50 {
		t.Errorf("Expected 50 patterns, got %d", len(idx.All))
	}
}

func TestLoadControlPatterns_NoDuplicateIDs(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	seen := make(map[string]bool)
	for _, p := range idx.All {
		if seen[p.ID] {
			t.Errorf("Duplicate pattern ID: %s", p.ID)
		}
		seen[p.ID] = true
	}
}

func TestControlPatternIndex_GetPattern(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	tests := []struct {
		name     string
		id       string
		expected bool
	}{
		{"existing pattern CP-AUTH-001", "CP-AUTH-001", true},
		{"existing pattern CP-CRYP-001", "CP-CRYP-001", true},
		{"lowercase lookup", "cp-auth-001", true},
		{"non-existing pattern", "CP-FAKE-999", false},
		{"empty id", "", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p, ok := idx.GetPattern(tt.id)
			if ok != tt.expected {
				t.Errorf("GetPattern(%q): expected found=%v, got found=%v", tt.id, tt.expected, ok)
			}
			if ok && p.ID == "" {
				t.Error("Pattern found but has empty ID")
			}
		})
	}
}

func TestControlPatternIndex_GetPatternsByDomain(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	tests := []struct {
		domain   string
		minCount int
	}{
		{"AUTH", 3},
		{"CRYP", 3},
		{"DATA", 5},
		{"SEC", 3},
		{"COMP", 5},
		{"LOG", 2},
		{"INC", 3},
		{"AI", 2},
	}

	for _, tt := range tests {
		t.Run(tt.domain, func(t *testing.T) {
			patterns := idx.GetPatternsByDomain(tt.domain)
			if len(patterns) < tt.minCount {
				t.Errorf("Domain %s: expected at least %d patterns, got %d",
					tt.domain, tt.minCount, len(patterns))
			}
		})
	}

	emptyPatterns := idx.GetPatternsByDomain("NOPE")
	if len(emptyPatterns) != 0 {
		t.Errorf("Expected 0 patterns for unknown domain, got %d", len(emptyPatterns))
	}
}

func TestControlPatternIndex_GetPatternsByCategory(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	authPatterns := idx.GetPatternsByCategory("authentication")
	if len(authPatterns) < 3 {
		t.Errorf("Expected at least 3 authentication patterns, got %d", len(authPatterns))
	}

	encPatterns := idx.GetPatternsByCategory("encryption")
	if len(encPatterns) < 3 {
		t.Errorf("Expected at least 3 encryption patterns, got %d", len(encPatterns))
	}
}

func TestControlPatternIndex_GetPatternsByTag(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	dpPatterns := idx.GetPatternsByTag("data_protection")
	if len(dpPatterns) < 3 {
		t.Errorf("Expected at least 3 data_protection tagged patterns, got %d", len(dpPatterns))
	}

	secPatterns := idx.GetPatternsByTag("security")
	if len(secPatterns) >= 1 {
		// At least 1 pattern tagged with "security" — good
	}
}

func TestControlPatternIndex_MatchByKeywords(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	tests := []struct {
		name           string
		text           string
		expectPatternID string
	}{
		{
			"password related text",
			"Die Passwortrichtlinie muss sicherstellen, dass Anmeldedaten geschuetzt sind",
			"CP-AUTH-001",
		},
		{
			"encryption text",
			"Verschluesselung ruhender Daten muss mit AES-256 erfolgen",
			"CP-CRYP-001",
		},
		{
			"incident response text",
			"Ein Vorfall-Reaktionsplan muss fuer Sicherheitsvorfaelle bereitstehen",
			"CP-INC-001",
		},
		{
			"DSGVO consent text",
			"Die Einwilligung der betroffenen Person muss freiwillig erfolgen",
			"CP-DATA-004",
		},
		{
			"AI risk text",
			"KI-Systeme mit hohem Risiko muessen einer Konformitaetsbewertung unterzogen werden",
			"CP-AI-001",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			matches := idx.MatchByKeywords(tt.text)
			if len(matches) == 0 {
				t.Fatalf("Expected at least 1 match for text: %s", tt.text[:50])
			}

			// Check if the expected pattern is in top 3 matches
			found := false
			for i, m := range matches {
				if i >= 3 {
					break
				}
				if m.Pattern.ID == tt.expectPatternID {
					found = true
					break
				}
			}
			if !found {
				topIDs := make([]string, 0, 3)
				for i, m := range matches {
					if i >= 3 {
						break
					}
					topIDs = append(topIDs, m.Pattern.ID)
				}
				t.Errorf("Expected %s in top 3, got %v", tt.expectPatternID, topIDs)
			}
		})
	}
}

func TestControlPatternIndex_MatchByKeywords_NoMatch(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	matches := idx.MatchByKeywords("xyzzy foobar baz completely unrelated text")
	if len(matches) != 0 {
		t.Errorf("Expected 0 matches for unrelated text, got %d", len(matches))
	}
}

func TestPatternMatch_Score(t *testing.T) {
	match := PatternMatch{
		KeywordHits:   3,
		TotalKeywords: 7,
	}

	score := match.Score()
	expected := 3.0 / 7.0
	if score < expected-0.01 || score > expected+0.01 {
		t.Errorf("Expected score ~%.3f, got %.3f", expected, score)
	}

	zeroMatch := PatternMatch{
		KeywordHits:   0,
		TotalKeywords: 0,
	}
	if zeroMatch.Score() != 0 {
		t.Errorf("Expected 0 score for zero keywords, got %f", zeroMatch.Score())
	}
}

func TestControlPatternIndex_ValidatePatternID(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	if !idx.ValidatePatternID("CP-AUTH-001") {
		t.Error("Expected CP-AUTH-001 to be valid")
	}
	if idx.ValidatePatternID("CP-FAKE-999") {
		t.Error("Expected CP-FAKE-999 to be invalid")
	}
}

func TestControlPatternIndex_Domains(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	domains := idx.Domains()
	if len(domains) < 5 {
		t.Errorf("Expected at least 5 domains, got %d: %v", len(domains), domains)
	}

	// Check critical domains are present
	domainSet := make(map[string]bool)
	for _, d := range domains {
		domainSet[d] = true
	}

	for _, required := range []string{"AUTH", "CRYP", "DATA", "SEC", "COMP"} {
		if !domainSet[required] {
			t.Errorf("Expected domain %s to be present", required)
		}
	}
}

func TestControlPattern_FieldsNotEmpty(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	for _, p := range idx.All {
		t.Run(p.ID, func(t *testing.T) {
			if p.ID == "" {
				t.Error("Empty ID")
			}
			if p.Name == "" {
				t.Error("Empty Name")
			}
			if p.NameDE == "" {
				t.Error("Empty NameDE")
			}
			if p.Domain == "" {
				t.Error("Empty Domain")
			}
			if p.Category == "" {
				t.Error("Empty Category")
			}
			if len(p.Description) < 20 {
				t.Errorf("Description too short: %d chars", len(p.Description))
			}
			if len(p.ObjectiveTemplate) < 20 {
				t.Errorf("ObjectiveTemplate too short: %d chars", len(p.ObjectiveTemplate))
			}
			if len(p.RationaleTemplate) < 20 {
				t.Errorf("RationaleTemplate too short: %d chars", len(p.RationaleTemplate))
			}
			if len(p.RequirementsTemplate) < 2 {
				t.Errorf("Not enough requirements: %d", len(p.RequirementsTemplate))
			}
			if len(p.TestProcedureTemplate) < 1 {
				t.Errorf("Not enough test procedures: %d", len(p.TestProcedureTemplate))
			}
			if len(p.EvidenceTemplate) < 1 {
				t.Errorf("Not enough evidence items: %d", len(p.EvidenceTemplate))
			}
			if len(p.ObligationMatchKeywords) < 3 {
				t.Errorf("Not enough keywords: %d", len(p.ObligationMatchKeywords))
			}
			if len(p.Tags) < 1 {
				t.Errorf("Not enough tags: %d", len(p.Tags))
			}

			validSeverities := map[string]bool{"low": true, "medium": true, "high": true, "critical": true}
			if !validSeverities[p.SeverityDefault] {
				t.Errorf("Invalid severity: %s", p.SeverityDefault)
			}
		})
	}
}

func TestControlPattern_IDDomainConsistency(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	for _, p := range idx.All {
		parts := strings.Split(p.ID, "-")
		if len(parts) != 3 {
			t.Errorf("Pattern %s: expected 3 parts in ID, got %d", p.ID, len(parts))
			continue
		}
		idDomain := parts[1]
		if idDomain != p.Domain {
			t.Errorf("Pattern %s: ID domain '%s' != field domain '%s'", p.ID, idDomain, p.Domain)
		}
	}
}

func TestControlPattern_ComposableWithValid(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	for _, p := range idx.All {
		for _, ref := range p.ComposableWith {
			if _, ok := idx.ByID[ref]; !ok {
				t.Errorf("Pattern %s: composable_with ref '%s' does not exist", p.ID, ref)
			}
			if ref == p.ID {
				t.Errorf("Pattern %s: composable_with contains self-reference", p.ID)
			}
		}
	}
}

func TestControlPattern_KeywordsLowercase(t *testing.T) {
	idx, err := LoadControlPatterns()
	if err != nil {
		t.Fatalf("Failed to load patterns: %v", err)
	}

	for _, p := range idx.All {
		for _, kw := range p.ObligationMatchKeywords {
			if kw != strings.ToLower(kw) {
				t.Errorf("Pattern %s: keyword should be lowercase: '%s'", p.ID, kw)
			}
		}
	}
}
