package orm

import (
	"fmt"
	"testing"
)

func TestDomainCompileEmpty(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	where, params, err := dc.Compile(nil)
	if err != nil {
		t.Fatal(err)
	}
	if where != "TRUE" {
		t.Errorf("expected TRUE, got %s", where)
	}
	if len(params) != 0 {
		t.Errorf("expected 0 params, got %d", len(params))
	}
}

func TestDomainCompileSimpleLeaf(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("name", "=", "test")}
	where, params, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"name" = $1` {
		t.Errorf("got %s", where)
	}
	if len(params) != 1 || params[0] != "test" {
		t.Errorf("params: %v", params)
	}
}

func TestDomainCompileNullCheck(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("name", "=", nil)}
	where, _, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"name" IS NULL` {
		t.Errorf("got %s", where)
	}
}

func TestDomainCompileFalseCheck(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("active", "=", false)}
	where, _, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"active" IS NULL` {
		t.Errorf("got %s", where)
	}
}

func TestDomainCompileNotEqualNull(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("name", "!=", nil)}
	where, _, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"name" IS NOT NULL` {
		t.Errorf("got %s", where)
	}
}

func TestDomainCompileIn(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("id", "in", []int64{1, 2, 3})}
	where, params, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"id" IN ($1, $2, $3)` {
		t.Errorf("got %s", where)
	}
	if len(params) != 3 {
		t.Errorf("params: %v", params)
	}
}

func TestDomainCompileEmptyIn(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("id", "in", []int64{})}
	where, _, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != "FALSE" {
		t.Errorf("got %s", where)
	}
}

func TestDomainCompileNotIn(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("id", "not in", []int64{1, 2})}
	where, params, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"id" NOT IN ($1, $2)` {
		t.Errorf("got %s", where)
	}
	if len(params) != 2 {
		t.Errorf("params: %v", params)
	}
}

func TestDomainCompileEmptyNotIn(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("id", "not in", []int64{})}
	where, _, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != "TRUE" {
		t.Errorf("got %s", where)
	}
}

func TestDomainCompileLike(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("name", "ilike", "test")}
	where, params, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"name" ILIKE $1` {
		t.Errorf("got %s", where)
	}
	if params[0] != "%test%" {
		t.Errorf("expected %%test%%, got %v", params[0])
	}
}

func TestDomainCompileLikeWithWildcard(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("name", "ilike", "test%")}
	where, params, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"name" ILIKE $1` {
		t.Errorf("got %s", where)
	}
	// Value already contains wildcard, should not be wrapped again
	if params[0] != "test%" {
		t.Errorf("expected test%%, got %v", params[0])
	}
}

func TestDomainCompileNotLike(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("name", "not like", "foo")}
	where, params, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"name" NOT LIKE $1` {
		t.Errorf("got %s", where)
	}
	if params[0] != "%foo%" {
		t.Errorf("expected %%foo%%, got %v", params[0])
	}
}

func TestDomainCompileExactLike(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("name", "=like", "test")}
	where, params, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"name" LIKE $1` {
		t.Errorf("got %s", where)
	}
	// =like does NOT auto-wrap
	if params[0] != "test" {
		t.Errorf("expected test, got %v", params[0])
	}
}

func TestDomainCompileExactIlike(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("name", "=ilike", "Test")}
	where, params, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"name" ILIKE $1` {
		t.Errorf("got %s", where)
	}
	if params[0] != "Test" {
		t.Errorf("expected Test, got %v", params[0])
	}
}

func TestDomainCompileAnd(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := And(Leaf("a", "=", 1), Leaf("b", "=", 2))
	where, params, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	expected := `("a" = $1 AND "b" = $2)`
	if where != expected {
		t.Errorf("expected %s, got %s", expected, where)
	}
	if len(params) != 2 {
		t.Errorf("params: %v", params)
	}
}

func TestDomainCompileOr(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Or(Leaf("a", "=", 1), Leaf("b", "=", 2))
	where, _, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	expected := `("a" = $1 OR "b" = $2)`
	if where != expected {
		t.Errorf("expected %s, got %s", expected, where)
	}
}

func TestDomainCompileNot(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Not(Leaf("active", "=", true))
	where, _, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	expected := `(NOT "active" = $1)`
	if where != expected {
		t.Errorf("expected %s, got %s", expected, where)
	}
}

func TestDomainCompileInvalidOperator(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("a", "INVALID", 1)}
	_, _, err := dc.Compile(domain)
	if err == nil {
		t.Error("expected error for invalid operator")
	}
}

func TestDomainCompileComparison(t *testing.T) {
	ops := []string{"<", ">", "<=", ">="}
	for _, op := range ops {
		t.Run(op, func(t *testing.T) {
			dc := &DomainCompiler{model: &Model{table: "test"}}
			domain := Domain{Leaf("age", op, 18)}
			where, params, err := dc.Compile(domain)
			if err != nil {
				t.Fatal(err)
			}
			expected := fmt.Sprintf(`"age" %s $1`, op)
			if where != expected {
				t.Errorf("expected %s, got %s", expected, where)
			}
			if len(params) != 1 || params[0] != 18 {
				t.Errorf("params: %v", params)
			}
		})
	}
}

func TestDomainCompileInStrings(t *testing.T) {
	dc := &DomainCompiler{model: &Model{table: "test"}}
	domain := Domain{Leaf("state", "in", []string{"draft", "sent"})}
	where, params, err := dc.Compile(domain)
	if err != nil {
		t.Fatal(err)
	}
	if where != `"state" IN ($1, $2)` {
		t.Errorf("got %s", where)
	}
	if len(params) != 2 {
		t.Errorf("params: %v", params)
	}
}

func TestAndEmpty(t *testing.T) {
	d := And()
	if d != nil {
		t.Errorf("expected nil, got %v", d)
	}
}

func TestAndSingle(t *testing.T) {
	d := And(Leaf("a", "=", 1))
	if len(d) != 1 {
		t.Errorf("expected 1 node, got %d", len(d))
	}
}

func TestOrEmpty(t *testing.T) {
	d := Or()
	if d != nil {
		t.Errorf("expected nil, got %v", d)
	}
}

func TestOrSingle(t *testing.T) {
	d := Or(Leaf("a", "=", 1))
	if len(d) != 1 {
		t.Errorf("expected 1 node, got %d", len(d))
	}
}

func TestOrMultiple(t *testing.T) {
	d := Or(Leaf("a", "=", 1), Leaf("b", "=", 2), Leaf("c", "=", 3))
	// Should have 2 OR operators + 3 leaves = 5 nodes
	if len(d) != 5 {
		t.Errorf("expected 5 nodes, got %d", len(d))
	}
}

func TestAndMultiple(t *testing.T) {
	d := And(Leaf("a", "=", 1), Leaf("b", "=", 2), Leaf("c", "=", 3))
	// Should have 2 AND operators + 3 leaves = 5 nodes
	if len(d) != 5 {
		t.Errorf("expected 5 nodes, got %d", len(d))
	}
}

func TestNotDomain(t *testing.T) {
	d := Not(Leaf("active", "=", true))
	if len(d) != 2 {
		t.Errorf("expected 2 nodes, got %d", len(d))
	}
	if d[0] != OpNot {
		t.Errorf("expected OpNot, got %v", d[0])
	}
}

func TestLeafCreation(t *testing.T) {
	c := Leaf("name", "=", "test")
	if c.Field != "name" {
		t.Errorf("field: %s", c.Field)
	}
	if c.Operator != "=" {
		t.Errorf("operator: %s", c.Operator)
	}
	if c.Value != "test" {
		t.Errorf("value: %v", c.Value)
	}
}

func TestWrapLikeValue(t *testing.T) {
	tests := []struct {
		name  string
		input Value
		want  Value
	}{
		{"plain string", "test", "%test%"},
		{"already has %", "test%", "test%"},
		{"already has _", "test_val", "test_val"},
		{"non-string", 42, 42},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := wrapLikeValue(tt.input)
			if got != tt.want {
				t.Errorf("wrapLikeValue(%v) = %v, want %v", tt.input, got, tt.want)
			}
		})
	}
}

func TestNormalizeSlice(t *testing.T) {
	t.Run("[]int64", func(t *testing.T) {
		result := normalizeSlice([]int64{1, 2, 3})
		if len(result) != 3 {
			t.Errorf("expected 3, got %d", len(result))
		}
	})
	t.Run("[]string", func(t *testing.T) {
		result := normalizeSlice([]string{"a", "b"})
		if len(result) != 2 {
			t.Errorf("expected 2, got %d", len(result))
		}
	})
	t.Run("[]int", func(t *testing.T) {
		result := normalizeSlice([]int{1, 2})
		if len(result) != 2 {
			t.Errorf("expected 2, got %d", len(result))
		}
	})
	t.Run("[]float64", func(t *testing.T) {
		result := normalizeSlice([]float64{1.5, 2.5})
		if len(result) != 2 {
			t.Errorf("expected 2, got %d", len(result))
		}
	})
	t.Run("non-slice", func(t *testing.T) {
		result := normalizeSlice("not a slice")
		if result != nil {
			t.Errorf("expected nil, got %v", result)
		}
	})
}

func TestToInt64Slice(t *testing.T) {
	tests := []struct {
		name string
		val  Value
		want []int64
	}{
		{"int64", int64(5), []int64{5}},
		{"int", int(3), []int64{3}},
		{"int32", int32(7), []int64{7}},
		{"float64", float64(9), []int64{9}},
		{"[]int64", []int64{1, 2}, []int64{1, 2}},
		{"[]int", []int{3, 4}, []int64{3, 4}},
		{"string", "bad", nil},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := toInt64Slice(tt.val)
			if tt.want == nil {
				if got != nil {
					t.Errorf("expected nil, got %v", got)
				}
				return
			}
			if len(got) != len(tt.want) {
				t.Fatalf("len: got %d, want %d", len(got), len(tt.want))
			}
			for i := range got {
				if got[i] != tt.want[i] {
					t.Errorf("index %d: got %d, want %d", i, got[i], tt.want[i])
				}
			}
		})
	}
}
