package uiapi

import (
	"encoding/json"
	"testing"
)

// =============================================================================
// RecordUI — top-level response model
// =============================================================================

func TestRecordUI_EmptyInitialization(t *testing.T) {
	ui := RecordUI{
		Records:     make(map[string]Record),
		Layouts:     make(map[string]map[string]Layout),
		ObjectInfos: make(map[string]ObjectInfo),
	}
	if len(ui.Records) != 0 {
		t.Error("expected empty Records")
	}
	if len(ui.Layouts) != 0 {
		t.Error("expected empty Layouts")
	}
	if len(ui.ObjectInfos) != 0 {
		t.Error("expected empty ObjectInfos")
	}
}

func TestRecordUI_JSONSerialization(t *testing.T) {
	ui := RecordUI{
		Records: map[string]Record{
			"rec-1": {
				ApiName: "Account",
				Id:      "rec-1",
				Fields: map[string]FieldValue{
					"Name": {Value: "Acme Corp", DisplayValue: nil},
				},
			},
		},
		Layouts:     make(map[string]map[string]Layout),
		ObjectInfos: make(map[string]ObjectInfo),
	}

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

	var decoded map[string]interface{}
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("failed to unmarshal: %v", err)
	}

	if _, ok := decoded["records"]; !ok {
		t.Error("expected 'records' key in JSON")
	}
	if _, ok := decoded["layouts"]; !ok {
		t.Error("expected 'layouts' key in JSON")
	}
	if _, ok := decoded["objectInfos"]; !ok {
		t.Error("expected 'objectInfos' key in JSON")
	}
}

// =============================================================================
// FieldValue — display value semantics
// =============================================================================

func TestFieldValue_NilDisplayValue(t *testing.T) {
	fv := FieldValue{Value: "test", DisplayValue: nil}
	data, err := json.Marshal(fv)
	if err != nil {
		t.Fatalf("marshal error: %v", err)
	}
	// displayValue should be present as null in JSON
	var decoded map[string]interface{}
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal error: %v", err)
	}
	// displayValue key should exist (not omitempty)
	if _, ok := decoded["displayValue"]; !ok {
		t.Error("expected 'displayValue' key in JSON even when nil")
	}
}

func TestFieldValue_WithDisplayValue(t *testing.T) {
	dv := "$100.00"
	fv := FieldValue{Value: float64(100), DisplayValue: &dv}
	data, err := json.Marshal(fv)
	if err != nil {
		t.Fatalf("marshal error: %v", err)
	}

	var decoded map[string]interface{}
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal error: %v", err)
	}
	if decoded["displayValue"] != "$100.00" {
		t.Errorf("displayValue = %v, want '$100.00'", decoded["displayValue"])
	}
}

// =============================================================================
// Record — record model
// =============================================================================

func TestRecord_JSONFields(t *testing.T) {
	rec := Record{
		ApiName:          "Contact",
		Id:               "contact-1",
		Fields:           map[string]FieldValue{},
		LastModifiedDate: "2024-01-15T12:00:00Z",
		SystemModstamp:   "2024-01-15T12:00:00Z",
		RecordTypeId:     "rt-1",
	}

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

	var decoded map[string]interface{}
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal error: %v", err)
	}

	if decoded["apiName"] != "Contact" {
		t.Errorf("apiName = %v", decoded["apiName"])
	}
	if decoded["id"] != "contact-1" {
		t.Errorf("id = %v", decoded["id"])
	}
}

func TestRecord_OmitsEmptyOptionalFields(t *testing.T) {
	rec := Record{
		ApiName: "Account",
		Id:      "acc-1",
		Fields:  map[string]FieldValue{},
		// No LastModifiedDate, SystemModstamp, RecordTypeId
	}

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

	var decoded map[string]interface{}
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal error: %v", err)
	}

	// These should be omitted (omitempty)
	if _, ok := decoded["lastModifiedDate"]; ok {
		t.Error("lastModifiedDate should be omitted when empty")
	}
	if _, ok := decoded["systemModstamp"]; ok {
		t.Error("systemModstamp should be omitted when empty")
	}
	if _, ok := decoded["recordTypeId"]; ok {
		t.Error("recordTypeId should be omitted when empty")
	}
}

// =============================================================================
// ObjectInfo — object metadata model
// =============================================================================

func TestObjectInfo_WritableFlags(t *testing.T) {
	// Writable object
	writable := ObjectInfo{
		ApiName:    "Account",
		Createable: true,
		Updateable: true,
		Deletable:  true,
		Queryable:  true,
	}
	if !writable.Createable {
		t.Error("writable should be createable")
	}

	// Read-only (system) object
	readOnly := ObjectInfo{
		ApiName:    "_System_Metadata",
		Createable: false,
		Updateable: false,
		Deletable:  false,
		Queryable:  true,
	}
	if readOnly.Createable {
		t.Error("system object should not be createable")
	}
	if readOnly.Deletable {
		t.Error("system object should not be deletable")
	}
}

// =============================================================================
// FieldInfo — field metadata model
// =============================================================================

func TestFieldInfo_ReferenceField(t *testing.T) {
	fi := FieldInfo{
		ApiName:    "AccountId",
		Label:      "Account",
		DataType:   "reference",
		Reference:  true,
		Createable: true,
		Updateable: true,
		ReferenceToInfos: []ReferenceToInfo{
			{ApiName: "Account", NameFields: []string{"Name"}},
		},
		RelationshipName: "Account",
	}

	if !fi.Reference {
		t.Error("expected Reference to be true")
	}
	if len(fi.ReferenceToInfos) != 1 {
		t.Errorf("expected 1 ReferenceToInfo, got %d", len(fi.ReferenceToInfos))
	}
	if fi.ReferenceToInfos[0].ApiName != "Account" {
		t.Errorf("ReferenceToInfo.ApiName = %q", fi.ReferenceToInfos[0].ApiName)
	}
}

// =============================================================================
// Layout — layout model
// =============================================================================

func TestLayout_WithSections(t *testing.T) {
	layout := Layout{
		Id:   "layout-1",
		Mode: "View",
		Sections: []LayoutSection{
			{
				Id:      "sec-1",
				Heading: "Detail",
				Columns: 2,
				LayoutRows: []LayoutRow{
					{
						LayoutItems: []LayoutItem{
							{
								Label: "Name",
								LayoutComponents: []LayoutComponent{
									{ComponentType: "Field", ApiName: "Name"},
								},
								Required:          true,
								EditableForNew:    true,
								EditableForUpdate: true,
							},
						},
					},
				},
				UseHeading:  true,
				Collapsible: true,
			},
		},
	}

	if len(layout.Sections) != 1 {
		t.Fatalf("expected 1 section, got %d", len(layout.Sections))
	}
	sec := layout.Sections[0]
	if sec.Columns != 2 {
		t.Errorf("Columns = %d, want 2", sec.Columns)
	}
	if len(sec.LayoutRows) != 1 {
		t.Fatalf("expected 1 row, got %d", len(sec.LayoutRows))
	}
	item := sec.LayoutRows[0].LayoutItems[0]
	if item.Label != "Name" {
		t.Errorf("item Label = %q", item.Label)
	}
	if !item.Required {
		t.Error("Name field should be required")
	}
}

func TestLayout_WithRelatedLists(t *testing.T) {
	layout := Layout{
		Id:   "layout-1",
		Mode: "View",
		RelatedLists: []RelatedListInfo{
			{
				Id:             "Contact.AccountId",
				Label:          "Contacts",
				SecondaryLabel: "via Account",
				ObjectApiName:  "Contact",
				LookupField:    "AccountId",
				Fields:         []string{"Name", "Email"},
			},
		},
	}

	if len(layout.RelatedLists) != 1 {
		t.Fatalf("expected 1 related list, got %d", len(layout.RelatedLists))
	}
	rl := layout.RelatedLists[0]
	if rl.ObjectApiName != "Contact" {
		t.Errorf("ObjectApiName = %q", rl.ObjectApiName)
	}
	if rl.LookupField != "AccountId" {
		t.Errorf("LookupField = %q", rl.LookupField)
	}
	if rl.SecondaryLabel != "via Account" {
		t.Errorf("SecondaryLabel = %q", rl.SecondaryLabel)
	}
}

func TestLayout_JSONIncludesEmptyRelatedLists(t *testing.T) {
	layout := Layout{
		Id:           "layout-1",
		Mode:         "View",
		Sections:     []LayoutSection{},
		RelatedLists: []RelatedListInfo{},
	}

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

	var decoded map[string]interface{}
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal error: %v", err)
	}

	if _, ok := decoded["relatedLists"]; !ok {
		t.Fatal("expected relatedLists key in JSON even when empty")
	}
}

// =============================================================================
// RecordDefaults — create/clone defaults model
// =============================================================================

func TestRecordDefaults_Structure(t *testing.T) {
	defaults := RecordDefaults{
		ObjectApiName: "Account",
		RecordTypeId:  "rt-1",
		Record: RecordWithDefaults{
			ApiName: "Account",
			Fields: map[string]FieldValue{
				"Name":    {Value: "", DisplayValue: nil},
				"OwnerId": {Value: "user-1", DisplayValue: strPtr("Admin User")},
			},
		},
	}

	if defaults.ObjectApiName != "Account" {
		t.Errorf("ObjectApiName = %q", defaults.ObjectApiName)
	}
	if len(defaults.Record.Fields) != 2 {
		t.Errorf("expected 2 default fields, got %d", len(defaults.Record.Fields))
	}
	if ownerFv := defaults.Record.Fields["OwnerId"]; ownerFv.DisplayValue == nil {
		t.Error("OwnerId should have a display value")
	}
}

// =============================================================================
// ListInfo — list view metadata model
// =============================================================================

func TestListInfo_Structure(t *testing.T) {
	info := ListInfo{
		ApiName:       "all-accounts",
		Id:            "all-accounts",
		Label:         "All Accounts",
		DeveloperName: "all-accounts",
		ObjectApiName: "Account",
		Visibility:    "Public",
		DisplayColumns: []DisplayColumn{
			{FieldApiName: "Name", Label: "Account Name", Sortable: true},
			{FieldApiName: "Industry", Label: "Industry", Sortable: true},
		},
	}

	if info.Visibility != "Public" {
		t.Errorf("Visibility = %q", info.Visibility)
	}
	if len(info.DisplayColumns) != 2 {
		t.Fatalf("expected 2 columns, got %d", len(info.DisplayColumns))
	}
	if !info.DisplayColumns[0].Sortable {
		t.Error("Name column should be sortable")
	}
}

func TestListInfo_JSONSerialization(t *testing.T) {
	info := ListInfo{
		ApiName:       "recent",
		Id:            "recent",
		Label:         "Recently Viewed",
		ObjectApiName: "Contact",
		Visibility:    "Public",
	}

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

	var decoded map[string]interface{}
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal error: %v", err)
	}

	if decoded["objectApiName"] != "Contact" {
		t.Errorf("objectApiName = %v", decoded["objectApiName"])
	}
	if decoded["visibility"] != "Public" {
		t.Errorf("visibility = %v", decoded["visibility"])
	}
}

// =============================================================================
// ThemeInfo — theme model
// =============================================================================

func TestThemeInfo_Structure(t *testing.T) {
	theme := ThemeInfo{
		Color:   "#1B96FF",
		IconUrl: "/assets/icons/standard/account.svg",
	}
	if theme.Color != "#1B96FF" {
		t.Errorf("Color = %q", theme.Color)
	}
	if theme.IconUrl != "/assets/icons/standard/account.svg" {
		t.Errorf("IconUrl = %q", theme.IconUrl)
	}
}
