package models

import (
	"database/sql"
	"encoding/json"
	"testing"
	"time"
)

func TestNewPattern(t *testing.T) {
	pattern := NewPattern(
		"Test Pattern",
		PatternTypeBug,
		"A test pattern description",
		[]string{"nil", "error", "handling"},
		"test-project",
		123,
	)

	if pattern.Name != "Test Pattern" {
		t.Errorf("Expected name 'Test Pattern', got '%s'", pattern.Name)
	}
	if pattern.Type != PatternTypeBug {
		t.Errorf("Expected type PatternTypeBug, got '%s'", pattern.Type)
	}
	if !pattern.Description.Valid || pattern.Description.String != "A test pattern description" {
		t.Errorf("Description not set correctly")
	}
	if len(pattern.Signature) != 3 {
		t.Errorf("Expected 3 signature elements, got %d", len(pattern.Signature))
	}
	if pattern.Frequency != 1 {
		t.Errorf("Expected frequency 1, got %d", pattern.Frequency)
	}
	if len(pattern.Projects) != 1 || pattern.Projects[0] != "test-project" {
		t.Errorf("Projects not set correctly")
	}
	if len(pattern.ObservationIDs) != 1 || pattern.ObservationIDs[0] != 123 {
		t.Errorf("ObservationIDs not set correctly")
	}
	if pattern.Status != PatternStatusActive {
		t.Errorf("Expected status Active, got '%s'", pattern.Status)
	}
	if pattern.Confidence != 0.5 {
		t.Errorf("Expected initial confidence 0.5, got %f", pattern.Confidence)
	}
}

func TestPattern_AddOccurrence(t *testing.T) {
	pattern := NewPattern("Test", PatternTypeBug, "desc", []string{"test"}, "project1", 1)

	// Add same project occurrence
	pattern.AddOccurrence("project1", 2)
	if pattern.Frequency != 2 {
		t.Errorf("Expected frequency 2, got %d", pattern.Frequency)
	}
	if len(pattern.Projects) != 1 {
		t.Errorf("Expected 1 project (no duplicates), got %d", len(pattern.Projects))
	}

	// Add different project occurrence
	pattern.AddOccurrence("project2", 3)
	if pattern.Frequency != 3 {
		t.Errorf("Expected frequency 3, got %d", pattern.Frequency)
	}
	if len(pattern.Projects) != 2 {
		t.Errorf("Expected 2 projects, got %d", len(pattern.Projects))
	}

	// Add duplicate observation ID - should not duplicate
	pattern.AddOccurrence("project2", 3)
	if len(pattern.ObservationIDs) != 3 {
		t.Errorf("Expected 3 observation IDs (no duplicate), got %d", len(pattern.ObservationIDs))
	}

	// Check confidence increased
	if pattern.Confidence <= 0.5 {
		t.Errorf("Expected confidence to increase above 0.5, got %f", pattern.Confidence)
	}
}

func TestPattern_ConfidenceCalculation(t *testing.T) {
	tests := []struct {
		name          string
		frequency     int
		projectCount  int
		minConfidence float64
		maxConfidence float64
	}{
		{"low_frequency", 2, 1, 0.3, 0.5},
		{"high_frequency", 10, 1, 0.6, 0.8},
		{"multi_project", 3, 3, 0.4, 0.7},
		{"high_freq_multi_proj", 10, 5, 0.7, 1.0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pattern := NewPattern("Test", PatternTypeBug, "", []string{}, "proj1", 1)

			// Simulate occurrences
			for i := 1; i < tt.frequency; i++ {
				projIdx := i % tt.projectCount
				if projIdx == 0 {
					projIdx = 1
				}
				pattern.AddOccurrence("proj"+string(rune('0'+projIdx)), int64(i+1))
			}

			if pattern.Confidence < tt.minConfidence || pattern.Confidence > tt.maxConfidence {
				t.Errorf("Expected confidence between %f and %f, got %f",
					tt.minConfidence, tt.maxConfidence, pattern.Confidence)
			}
		})
	}
}

func TestPatternType_Detection(t *testing.T) {
	tests := []struct {
		title     string
		narrative string
		expected  PatternType
		concepts  []string
	}{
		{title: "", narrative: "", expected: PatternTypeAntiPattern, concepts: []string{"anti-pattern"}},
		{title: "", narrative: "", expected: PatternTypeBestPractice, concepts: []string{"best-practice"}},
		{title: "", narrative: "", expected: PatternTypeArchitecture, concepts: []string{"architecture"}},
		{title: "", narrative: "", expected: PatternTypeRefactor, concepts: []string{"refactor"}},
		{title: "nil pointer bug", narrative: "", expected: PatternTypeBug, concepts: []string{}},
		{title: "Deadlock in concurrent code", narrative: "", expected: PatternTypeBug, concepts: []string{}},
		{title: "Extract interface", narrative: "", expected: PatternTypeRefactor, concepts: []string{}},
	}

	for _, tt := range tests {
		t.Run(tt.title+"_"+tt.expected.String(), func(t *testing.T) {
			result := DetectPatternType(tt.concepts, tt.title, tt.narrative)
			if result != tt.expected {
				t.Errorf("Expected %s, got %s", tt.expected, result)
			}
		})
	}
}

func (pt PatternType) String() string {
	return string(pt)
}

func TestExtractSignature(t *testing.T) {
	concepts := []string{"error-handling", "security"}
	title := "Nil Pointer Validation Pattern"
	narrative := "Always validate before dereferencing"

	signature := ExtractSignature(concepts, title, narrative)

	// Should contain concepts
	found := false
	for _, s := range signature {
		if s == "error-handling" {
			found = true
			break
		}
	}
	if !found {
		t.Errorf("Expected signature to contain concepts, got %v", signature)
	}

	// Should contain significant words from title
	found = false
	for _, s := range signature {
		if s == "validation" || s == "pattern" || s == "pointer" {
			found = true
			break
		}
	}
	if !found {
		t.Errorf("Expected signature to contain title keywords, got %v", signature)
	}
}

func TestCalculateMatchScore(t *testing.T) {
	tests := []struct {
		name     string
		sig1     []string
		sig2     []string
		minScore float64
		maxScore float64
	}{
		{"identical", []string{"a", "b", "c"}, []string{"a", "b", "c"}, 1.0, 1.0},
		{"partial", []string{"a", "b", "c"}, []string{"a", "b", "d"}, 0.4, 0.6},
		{"no_match", []string{"a", "b", "c"}, []string{"x", "y", "z"}, 0.0, 0.0},
		{"empty", []string{}, []string{"a", "b"}, 0.0, 0.0},
		{"subset", []string{"a", "b"}, []string{"a", "b", "c", "d"}, 0.4, 0.6},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			score := CalculateMatchScore(tt.sig1, tt.sig2)
			if score < tt.minScore || score > tt.maxScore {
				t.Errorf("Expected score between %f and %f, got %f",
					tt.minScore, tt.maxScore, score)
			}
		})
	}
}

func TestPattern_MarshalJSON(t *testing.T) {
	pattern := &Pattern{
		ID:             1,
		Name:           "Test Pattern",
		Type:           PatternTypeBug,
		Description:    sql.NullString{String: "A description", Valid: true},
		Signature:      []string{"a", "b"},
		Recommendation: sql.NullString{String: "Do this", Valid: true},
		Frequency:      5,
		Projects:       []string{"proj1", "proj2"},
		ObservationIDs: []int64{1, 2, 3},
		Status:         PatternStatusActive,
		MergedIntoID:   sql.NullInt64{Int64: 0, Valid: false},
		Confidence:     0.8,
		LastSeenAt:     time.Now().Format(time.RFC3339),
		LastSeenEpoch:  time.Now().UnixMilli(),
		CreatedAt:      time.Now().Format(time.RFC3339),
		CreatedAtEpoch: time.Now().UnixMilli(),
	}

	data, err := json.Marshal(pattern)
	if err != nil {
		t.Fatalf("Failed to marshal pattern: %v", err)
	}

	var result PatternJSON
	if err := json.Unmarshal(data, &result); err != nil {
		t.Fatalf("Failed to unmarshal pattern: %v", err)
	}

	if result.Name != pattern.Name {
		t.Errorf("Expected name %s, got %s", pattern.Name, result.Name)
	}
	if result.Description != pattern.Description.String {
		t.Errorf("Expected description %s, got %s", pattern.Description.String, result.Description)
	}
	if result.Frequency != pattern.Frequency {
		t.Errorf("Expected frequency %d, got %d", pattern.Frequency, result.Frequency)
	}
	if result.MergedIntoID != 0 {
		t.Errorf("Expected merged_into_id 0 for invalid NullInt64, got %d", result.MergedIntoID)
	}
}

func TestJSONInt64Array_Scan(t *testing.T) {
	tests := []struct {
		name     string
		input    interface{}
		expected JSONInt64Array
		wantErr  bool
	}{
		{"string_array", "[1, 2, 3]", JSONInt64Array{1, 2, 3}, false},
		{"bytes_array", []byte("[4, 5, 6]"), JSONInt64Array{4, 5, 6}, false},
		{"nil", nil, nil, false},
		{"empty_string", "", nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var arr JSONInt64Array
			err := arr.Scan(tt.input)
			if (err != nil) != tt.wantErr {
				t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if len(arr) != len(tt.expected) {
				t.Errorf("Expected length %d, got %d", len(tt.expected), len(arr))
			}
		})
	}
}

func TestJSONInt64Array_Value(t *testing.T) {
	arr := JSONInt64Array{1, 2, 3}
	val, err := arr.Value()
	if err != nil {
		t.Fatalf("Value() error = %v", err)
	}

	bytes, ok := val.([]byte)
	if !ok {
		t.Fatalf("Expected []byte, got %T", val)
	}

	var result []int64
	if err := json.Unmarshal(bytes, &result); err != nil {
		t.Fatalf("Failed to unmarshal: %v", err)
	}

	if len(result) != 3 || result[0] != 1 || result[1] != 2 || result[2] != 3 {
		t.Errorf("Expected [1, 2, 3], got %v", result)
	}
}

func TestPatternSignatureKeywords(t *testing.T) {
	// Verify keywords exist for each type
	types := []PatternType{
		PatternTypeBug,
		PatternTypeRefactor,
		PatternTypeArchitecture,
		PatternTypeAntiPattern,
		PatternTypeBestPractice,
	}

	for _, pt := range types {
		keywords := PatternSignatureKeywords[pt]
		if len(keywords) == 0 {
			t.Errorf("No keywords defined for pattern type %s", pt)
		}
	}
}

func TestUniqueStrings(t *testing.T) {
	tests := []struct {
		input    []string
		expected int
	}{
		{[]string{"a", "b", "c"}, 3},
		{[]string{"a", "a", "b"}, 2},
		{[]string{"a", "a", "a"}, 1},
		{[]string{}, 0},
	}

	for _, tt := range tests {
		result := uniqueStrings(tt.input)
		if len(result) != tt.expected {
			t.Errorf("uniqueStrings(%v) = %v (len=%d), expected len=%d",
				tt.input, result, len(result), tt.expected)
		}
	}
}

func TestContainsIgnoreCase(t *testing.T) {
	tests := []struct {
		text     string
		substr   string
		expected bool
	}{
		{"Hello World", "hello", true},
		{"Hello World", "WORLD", true},
		{"Hello World", "xyz", false},
		{"", "a", false},
		{"a", "", true},
	}

	for _, tt := range tests {
		result := containsIgnoreCase(tt.text, tt.substr)
		if result != tt.expected {
			t.Errorf("containsIgnoreCase(%q, %q) = %v, expected %v",
				tt.text, tt.substr, result, tt.expected)
		}
	}
}
