package orm

import "testing"

func TestFieldIsCopyable(t *testing.T) {
	tests := []struct {
		name  string
		field Field
		want  bool
	}{
		{"regular char", Field{Name: "name", Type: TypeChar}, true},
		{"id field", Field{Name: "id", Type: TypeInteger}, false},
		{"create_uid", Field{Name: "create_uid", Type: TypeMany2one}, false},
		{"write_uid", Field{Name: "write_uid", Type: TypeMany2one}, false},
		{"create_date", Field{Name: "create_date", Type: TypeDatetime}, false},
		{"write_date", Field{Name: "write_date", Type: TypeDatetime}, false},
		{"password", Field{Name: "password", Type: TypeChar}, false},
		{"computed non-stored", Field{Name: "total", Type: TypeFloat, Compute: "x"}, false},
		{"computed stored", Field{Name: "total", Type: TypeFloat, Compute: "x", Store: true}, true},
		{"o2m", Field{Name: "lines", Type: TypeOne2many}, false},
		{"m2o", Field{Name: "partner_id", Type: TypeMany2one}, true},
		{"boolean", Field{Name: "active", Type: TypeBoolean}, true},
		{"explicit copy true", Field{Name: "ref", Type: TypeChar, Copy: true}, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := tt.field.IsCopyable()
			if got != tt.want {
				t.Errorf("IsCopyable() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestFieldSQLType(t *testing.T) {
	tests := []struct {
		typ  FieldType
		want string
	}{
		{TypeChar, "varchar"},
		{TypeText, "text"},
		{TypeHTML, "text"},
		{TypeInteger, "int4"},
		{TypeFloat, "numeric"},
		{TypeMonetary, "numeric"},
		{TypeBoolean, "bool"},
		{TypeDate, "date"},
		{TypeDatetime, "timestamp without time zone"},
		{TypeMany2one, "int4"},
		{TypeOne2many, ""},
		{TypeMany2many, ""},
		{TypeJson, "jsonb"},
		{TypeProperties, "jsonb"},
		{TypeBinary, "bytea"},
		{TypeSelection, "varchar"},
		{TypeReference, "varchar"},
	}
	for _, tt := range tests {
		t.Run(tt.typ.String(), func(t *testing.T) {
			got := tt.typ.SQLType()
			if got != tt.want {
				t.Errorf("SQLType() = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestFieldSQLTypeWithSize(t *testing.T) {
	f := &Field{Type: TypeChar, Size: 64}
	got := f.SQLType()
	if got != "varchar(64)" {
		t.Errorf("expected varchar(64), got %s", got)
	}
}

func TestFieldTypeString(t *testing.T) {
	if TypeChar.String() != "char" {
		t.Error("expected char")
	}
	if TypeMany2one.String() != "many2one" {
		t.Error("expected many2one")
	}
	if TypeBoolean.String() != "boolean" {
		t.Error("expected boolean")
	}
	if TypeText.String() != "text" {
		t.Error("expected text")
	}
	if TypeInteger.String() != "integer" {
		t.Error("expected integer")
	}
	if TypeFloat.String() != "float" {
		t.Error("expected float")
	}
}

func TestFieldTypeIsRelational(t *testing.T) {
	if !TypeMany2one.IsRelational() {
		t.Error("m2o should be relational")
	}
	if !TypeOne2many.IsRelational() {
		t.Error("o2m should be relational")
	}
	if !TypeMany2many.IsRelational() {
		t.Error("m2m should be relational")
	}
	if TypeChar.IsRelational() {
		t.Error("char should not be relational")
	}
	if TypeInteger.IsRelational() {
		t.Error("integer should not be relational")
	}
	if TypeBoolean.IsRelational() {
		t.Error("boolean should not be relational")
	}
}

func TestFieldTypeIsStored(t *testing.T) {
	if !TypeChar.IsStored() {
		t.Error("char should be stored")
	}
	if !TypeMany2one.IsStored() {
		t.Error("m2o should be stored")
	}
	if TypeOne2many.IsStored() {
		t.Error("o2m should not be stored")
	}
	if TypeMany2many.IsStored() {
		t.Error("m2m should not be stored")
	}
	if !TypeBoolean.IsStored() {
		t.Error("boolean should be stored")
	}
	if !TypeInteger.IsStored() {
		t.Error("integer should be stored")
	}
}

func TestFieldIsStored(t *testing.T) {
	tests := []struct {
		name string
		f    Field
		want bool
	}{
		{"plain char", Field{Type: TypeChar}, true},
		{"computed not stored", Field{Type: TypeChar, Compute: "x"}, false},
		{"computed stored", Field{Type: TypeChar, Compute: "x", Store: true}, true},
		{"related not stored", Field{Type: TypeChar, Related: "partner_id.name"}, false},
		{"related stored", Field{Type: TypeChar, Related: "partner_id.name", Store: true}, true},
		{"o2m", Field{Type: TypeOne2many}, false},
		{"m2m", Field{Type: TypeMany2many}, false},
		{"m2o", Field{Type: TypeMany2one}, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := tt.f.IsStored()
			if got != tt.want {
				t.Errorf("IsStored() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestFieldColumn(t *testing.T) {
	f := &Field{Name: "partner_id", column: "partner_id"}
	if f.Column() != "partner_id" {
		t.Errorf("expected partner_id, got %s", f.Column())
	}

	f2 := &Field{Name: "custom", column: "custom_col"}
	if f2.Column() != "custom_col" {
		t.Errorf("expected custom_col, got %s", f2.Column())
	}

	// When column is empty, falls back to Name
	f3 := &Field{Name: "fallback"}
	if f3.Column() != "fallback" {
		t.Errorf("expected fallback, got %s", f3.Column())
	}
}

func TestFieldConstructors(t *testing.T) {
	t.Run("Char", func(t *testing.T) {
		f := Char("name", FieldOpts{String: "Name", Required: true})
		if f.Type != TypeChar {
			t.Errorf("type: %s", f.Type)
		}
		if f.Name != "name" {
			t.Errorf("name: %s", f.Name)
		}
		if !f.Required {
			t.Error("expected required")
		}
		if f.String != "Name" {
			t.Errorf("string: %s", f.String)
		}
	})

	t.Run("Integer", func(t *testing.T) {
		f := Integer("count", FieldOpts{})
		if f.Type != TypeInteger {
			t.Errorf("type: %s", f.Type)
		}
	})

	t.Run("Boolean", func(t *testing.T) {
		f := Boolean("active", FieldOpts{Default: true})
		if f.Type != TypeBoolean {
			t.Errorf("type: %s", f.Type)
		}
		if f.Default != true {
			t.Error("expected default true")
		}
	})

	t.Run("Many2one", func(t *testing.T) {
		f := Many2one("partner_id", "res.partner", FieldOpts{String: "Partner"})
		if f.Type != TypeMany2one {
			t.Errorf("type: %s", f.Type)
		}
		if f.Comodel != "res.partner" {
			t.Errorf("comodel: %s", f.Comodel)
		}
		if !f.Index {
			t.Error("M2O should be auto-indexed")
		}
		if f.OnDelete != OnDeleteSetNull {
			t.Errorf("expected set null, got %s", f.OnDelete)
		}
	})

	t.Run("One2many", func(t *testing.T) {
		f := One2many("line_ids", "sale.order.line", "order_id", FieldOpts{})
		if f.Type != TypeOne2many {
			t.Errorf("type: %s", f.Type)
		}
		if f.InverseField != "order_id" {
			t.Errorf("inverse: %s", f.InverseField)
		}
	})

	t.Run("Many2many", func(t *testing.T) {
		f := Many2many("tag_ids", "res.partner.tag", FieldOpts{})
		if f.Type != TypeMany2many {
			t.Errorf("type: %s", f.Type)
		}
		if f.Comodel != "res.partner.tag" {
			t.Errorf("comodel: %s", f.Comodel)
		}
	})

	t.Run("Text", func(t *testing.T) {
		f := Text("description", FieldOpts{})
		if f.Type != TypeText {
			t.Errorf("type: %s", f.Type)
		}
	})

	t.Run("Float", func(t *testing.T) {
		f := Float("amount", FieldOpts{})
		if f.Type != TypeFloat {
			t.Errorf("type: %s", f.Type)
		}
	})

	t.Run("Date", func(t *testing.T) {
		f := Date("birthday", FieldOpts{})
		if f.Type != TypeDate {
			t.Errorf("type: %s", f.Type)
		}
	})

	t.Run("Datetime", func(t *testing.T) {
		f := Datetime("created", FieldOpts{})
		if f.Type != TypeDatetime {
			t.Errorf("type: %s", f.Type)
		}
	})

	t.Run("Binary", func(t *testing.T) {
		f := Binary("image", FieldOpts{})
		if f.Type != TypeBinary {
			t.Errorf("type: %s", f.Type)
		}
	})

	t.Run("Json", func(t *testing.T) {
		f := Json("data", FieldOpts{})
		if f.Type != TypeJson {
			t.Errorf("type: %s", f.Type)
		}
	})

	t.Run("default label from name", func(t *testing.T) {
		f := Char("my_field", FieldOpts{})
		if f.String != "my_field" {
			t.Errorf("expected my_field as default label, got %s", f.String)
		}
	})
}

func TestFieldResolveDefault(t *testing.T) {
	t.Run("nil default", func(t *testing.T) {
		f := &Field{Default: nil}
		if f.ResolveDefault() != nil {
			t.Error("expected nil")
		}
	})

	t.Run("string default", func(t *testing.T) {
		f := &Field{Default: "hello"}
		if f.ResolveDefault() != "hello" {
			t.Error("expected hello")
		}
	})

	t.Run("bool default", func(t *testing.T) {
		f := &Field{Default: true}
		if f.ResolveDefault() != true {
			t.Error("expected true")
		}
	})

	t.Run("int default", func(t *testing.T) {
		f := &Field{Default: 42}
		if f.ResolveDefault() != 42 {
			t.Error("expected 42")
		}
	})
}
