package handlers

import (
	"testing"

	"bookhoard/internal/database"

	"github.com/jackc/pgx/v5/pgtype"
	"github.com/stretchr/testify/assert"
)

// Test collection rule matching with various operators

func TestEvaluateRule_Equals_StringMatch(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre: pgtype.Text{String: "Science Fiction", Valid: true},
	}

	result := handler.evaluateRule(item, "genre", "equals", "Science Fiction")
	assert.True(t, result)
}

func TestEvaluateRule_Equals_NoMatch(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre: pgtype.Text{String: "Fantasy", Valid: true},
	}

	result := handler.evaluateRule(item, "genre", "equals", "Science Fiction")
	assert.False(t, result)
}

func TestEvaluateRule_NotEquals_Matches(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre: pgtype.Text{String: "Fantasy", Valid: true},
	}

	result := handler.evaluateRule(item, "genre", "not_equals", "Science Fiction")
	assert.True(t, result)
}

func TestEvaluateRule_NotEquals_NoMatch(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre: pgtype.Text{String: "Science Fiction", Valid: true},
	}

	result := handler.evaluateRule(item, "genre", "not_equals", "Science Fiction")
	assert.False(t, result)
}

func TestEvaluateRule_Contains_Matches(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Author: pgtype.Text{String: "Isaac Asimov", Valid: true},
	}

	result := handler.evaluateRule(item, "author", "contains", "asimov")
	assert.True(t, result)
}

func TestEvaluateRule_Contains_NoMatch(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Author: pgtype.Text{String: "Isaac Asimov", Valid: true},
	}

	result := handler.evaluateRule(item, "author", "contains", "clarke")
	assert.False(t, result)
}

func TestEvaluateRule_NotContains_Matches(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Author: pgtype.Text{String: "Isaac Asimov", Valid: true},
	}

	result := handler.evaluateRule(item, "author", "not_contains", "clarke")
	assert.True(t, result)
}

func TestEvaluateRule_StartsWith_Matches(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Series: pgtype.Text{String: "Foundation", Valid: true},
	}

	result := handler.evaluateRule(item, "series", "starts_with", "Foun")
	assert.True(t, result)
}

func TestEvaluateRule_StartsWith_NoMatch(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Series: pgtype.Text{String: "Foundation", Valid: true},
	}

	result := handler.evaluateRule(item, "series", "starts_with", "Robots")
	assert.False(t, result)
}

func TestEvaluateRule_EndsWith_Matches(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Series: pgtype.Text{String: "Foundation", Valid: true},
	}

	result := handler.evaluateRule(item, "series", "ends_with", "tion")
	assert.True(t, result)
}

func TestEvaluateRule_GreaterThan_Matches(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		CopyrightYear: pgtype.Int4{Int32: 2021, Valid: true},
	}

	result := handler.evaluateRule(item, "copyright_year", "greater_than", "2000")
	assert.True(t, result)
}

func TestEvaluateRule_GreaterThan_NoMatch(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		CopyrightYear: pgtype.Int4{Int32: 1999, Valid: true},
	}

	result := handler.evaluateRule(item, "copyright_year", "greater_than", "2000")
	assert.False(t, result)
}

func TestEvaluateRule_LessThan_Matches(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		CopyrightYear: pgtype.Int4{Int32: 1999, Valid: true},
	}

	result := handler.evaluateRule(item, "copyright_year", "less_than", "2000")
	assert.True(t, result)
}

func TestEvaluateRule_LessThan_NoMatch(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		CopyrightYear: pgtype.Int4{Int32: 2021, Valid: true},
	}

	result := handler.evaluateRule(item, "copyright_year", "less_than", "2000")
	assert.False(t, result)
}

func TestEvaluateRule_NullField(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre: pgtype.Text{Valid: false}, // NULL
	}

	result := handler.evaluateRule(item, "genre", "equals", "Science Fiction")
	assert.False(t, result, "Should return false for NULL fields")
}

func TestCompareValues_CaseInsensitive(t *testing.T) {
	handler := &CollectionHandler{}

	result := handler.compareValues("Science Fiction", "contains", "science fiction")
	assert.True(t, result, "Contains should be case-insensitive")

	result = handler.compareValues("Science Fiction", "contains", "SCIENCE FICTION")
	assert.True(t, result)
}

func TestCompareValues_EmptyString(t *testing.T) {
	handler := &CollectionHandler{}

	result := handler.compareValues("", "contains", "test")
	assert.False(t, result, "Empty string should not contain anything")

	result = handler.compareValues("test", "contains", "")
	assert.True(t, result, "Non-empty string contains empty string")
}

func TestCompareValues_NumericEdgeCases(t *testing.T) {
	handler := &CollectionHandler{}

	result := handler.compareValues("0", "greater_than", "-1")
	assert.True(t, result)

	result = handler.compareValues("9999", "less_than", "10000")
	assert.True(t, result)
}

// Test rule checking with multiple rules

func TestCheckRulesAgainstBook_MatchesFirstRule(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre: pgtype.Text{String: "Science Fiction", Valid: true},
	}

	rules := []map[string]interface{}{
		{
			"field":    "genre",
			"operator": "equals",
			"value":    "Science Fiction",
		},
		{
			"field":    "author",
			"operator": "contains",
			"value":    "Asimov",
		},
	}

	reason := handler.checkRulesAgainstBook(item, rules)
	assert.NotEmpty(t, reason, "Should match at least one rule")
	assert.Contains(t, reason, "genre")
}

func TestCheckRulesAgainstBook_MatchesSecondRule(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre:         pgtype.Text{String: "Fantasy", Valid: true},
		Author:        pgtype.Text{String: "Isaac Asimov", Valid: true},
		CopyrightYear: pgtype.Int4{Int32: 2021, Valid: true},
	}

	rules := []map[string]interface{}{
		{
			"field":    "genre",
			"operator": "equals",
			"value":    "Science Fiction",
		},
		{
			"field":    "author",
			"operator": "contains",
			"value":    "Asimov",
		},
	}

	reason := handler.checkRulesAgainstBook(item, rules)
	assert.NotEmpty(t, reason, "Should match second rule")
	assert.Contains(t, reason, "author")
}

func TestCheckRulesAgainstBook_NoMatches(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre:         pgtype.Text{String: "Mystery", Valid: true},
		Author:        pgtype.Text{String: "Agatha Christie", Valid: true},
		CopyrightYear: pgtype.Int4{Int32: 1950, Valid: true},
	}

	rules := []map[string]interface{}{
		{
			"field":    "genre",
			"operator": "equals",
			"value":    "Science Fiction",
		},
		{
			"field":    "author",
			"operator": "contains",
			"value":    "Asimov",
		},
	}

	reason := handler.checkRulesAgainstBook(item, rules)
	assert.Empty(t, reason, "Should not match any rules")
}

func TestCheckRulesAgainstBook_EmptyRules(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre: pgtype.Text{String: "Science Fiction", Valid: true},
	}

	rules := []map[string]interface{}{}

	reason := handler.checkRulesAgainstBook(item, rules)
	assert.Empty(t, reason, "Empty rules should return empty reason")
}

// Test complex rule scenarios

func TestComplexRule_MultipleConditions(t *testing.T) {
	handler := &CollectionHandler{}

	// Book: "Foundation" by Isaac Asimov, Sci-Fi, 1951
	item := database.ListMediaItemsRow{
		Title:         "Foundation",
		Author:        pgtype.Text{String: "Isaac Asimov", Valid: true},
		Genre:         pgtype.Text{String: "Science Fiction", Valid: true},
		CopyrightYear: pgtype.Int4{Int32: 1951, Valid: true},
		Series:        pgtype.Text{String: "Foundation", Valid: true},
	}

	tests := []struct {
		name     string
		field    string
		operator string
		value    string
		expected bool
	}{
		{
			name:     "Match genre exactly",
			field:    "genre",
			operator: "equals",
			value:    "Science Fiction",
			expected: true,
		},
		{
			name:     "Match author substring",
			field:    "author",
			operator: "contains",
			value:    "asimov",
			expected: true,
		},
		{
			name:     "Match year greater than",
			field:    "copyright_year",
			operator: "greater_than",
			value:    "1950",
			expected: true,
		},
		{
			name:     "Match year less than",
			field:    "copyright_year",
			operator: "less_than",
			value:    "2000",
			expected: true,
		},
		{
			name:     "Match series starts with",
			field:    "series",
			operator: "starts_with",
			value:    "Found",
			expected: true,
		},
		{
			name:     "Match series ends with",
			field:    "series",
			operator: "ends_with",
			value:    "tion",
			expected: true,
		},
		{
			name:     "No match for genre",
			field:    "genre",
			operator: "equals",
			value:    "Fantasy",
			expected: false,
		},
		{
			name:     "No match for author",
			field:    "author",
			operator: "contains",
			value:    "Tolkien",
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := handler.evaluateRule(item, tt.field, tt.operator, tt.value)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test edge cases and error handling

func TestEvaluateRule_InvalidOperator(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre: pgtype.Text{String: "Science Fiction", Valid: true},
	}

	result := handler.evaluateRule(item, "genre", "invalid_operator", "Science Fiction")
	assert.False(t, result, "Invalid operator should return false")
}

func TestEvaluateRule_NonExistentField(t *testing.T) {
	handler := &CollectionHandler{}
	item := database.ListMediaItemsRow{
		Genre: pgtype.Text{String: "Science Fiction", Valid: true},
	}

	result := handler.evaluateRule(item, "nonexistent_field", "equals", "value")
	assert.False(t, result, "Non-existent field should return false")
}

func TestCompareValues_InvalidOperator(t *testing.T) {
	handler := &CollectionHandler{}

	result := handler.compareValues("test", "invalid_op", "value")
	assert.False(t, result, "Invalid comparison operator should return false")
}

func TestCompareValues_NumericConversion(t *testing.T) {
	handler := &CollectionHandler{}

	// Valid numeric strings
	result := handler.compareValues("100", "greater_than", "99")
	assert.True(t, result)

	result = handler.compareValues("99", "less_than", "100")
	assert.True(t, result)

	// Invalid numeric strings (should return false)
	result = handler.compareValues("abc", "greater_than", "100")
	assert.False(t, result, "Non-numeric string should not compare as greater than")

	result = handler.compareValues("100", "greater_than", "xyz")
	assert.False(t, result, "Non-numeric value should not allow comparison")
}
