package persistence

import (
	"database/sql"
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// -----------------------------------------------------------------------
// prepareObjectDBValues  (schema_helpers.go)
// -----------------------------------------------------------------------

func TestPrepareObjectDBValues(t *testing.T) {
	r := &SchemaRepository{db: nil}

	t.Run("minimal object", func(t *testing.T) {
		obj := &models.ObjectMetadata{
			APIName:     "Account",
			Label:       "Account",
			PluralLabel: "Accounts",
		}

		vals, err := r.prepareObjectDBValues(obj)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if len(vals) != 17 {
			t.Fatalf("expected 17 values, got %d", len(vals))
		}

		// Check APIName (index 0)
		if vals[0] != "Account" {
			t.Errorf("vals[0] (APIName) = %v, want Account", vals[0])
		}

		// Check Label (index 1)
		if vals[1] != "Account" {
			t.Errorf("vals[1] (Label) = %v, want Account", vals[1])
		}

		// Check PluralLabel (index 2)
		if vals[2] != "Accounts" {
			t.Errorf("vals[2] (PluralLabel) = %v, want Accounts", vals[2])
		}

		// Icon should be null (index 3)
		icon, ok := vals[3].(sql.NullString)
		if !ok {
			t.Errorf("vals[3] (Icon) should be sql.NullString, got %T", vals[3])
		} else if icon.Valid {
			t.Error("Icon should be invalid for empty icon")
		}

		// SharingModel defaults to Private (index 7)
		if vals[7] != models.SharingModel(constants.SharingModelPrivate) {
			t.Errorf("SharingModel = %v, want %v", vals[7], constants.SharingModelPrivate)
		}

		// TableType for non-custom business objects = standard_object (index 16)
		if vals[16] != constants.TableTypeStandardObject {
			t.Errorf("TableType = %v, want %v", vals[16], constants.TableTypeStandardObject)
		}
	})

	t.Run("custom object with all fields", func(t *testing.T) {
		desc := "A custom object"
		appID := "app-1"
		pathField := "path"
		themeColor := "#FF0000"
		obj := &models.ObjectMetadata{
			APIName:                 "Custom__c",
			Label:                   "Custom",
			PluralLabel:             "Customs",
			Icon:                    "custom:custom1",
			Description:             &desc,
			IsCustom:                true,
			SharingModel:            constants.SharingModelPublicReadWrite,
			EnableHierarchySharing:  true,
			RequiredWritePermission: "ManageCustomObjects",
			AppID:                   &appID,
			ListFields:              []string{"Name", "Status"},
			PathField:               &pathField,
			ThemeColor:              &themeColor,
		}

		vals, err := r.prepareObjectDBValues(obj)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Icon should be valid (index 3)
		icon, ok := vals[3].(sql.NullString)
		if !ok {
			t.Errorf("vals[3] should be sql.NullString, got %T", vals[3])
		} else if !icon.Valid || icon.String != "custom:custom1" {
			t.Errorf("Icon = %v, want custom:custom1", icon)
		}

		// IsCustom (index 5)
		if vals[5] != true {
			t.Errorf("IsCustom = %v, want true", vals[5])
		}

		// SharingModel (index 7) should be ReadWrite
		if vals[7] != constants.SharingModelPublicReadWrite {
			t.Errorf("SharingModel = %v, want ReadWrite", vals[7])
		}
		if vals[8] != true {
			t.Errorf("EnableHierarchySharing = %v, want true", vals[8])
		}
		requiredWritePermission, ok := vals[9].(sql.NullString)
		if !ok || !requiredWritePermission.Valid || requiredWritePermission.String != "ManageCustomObjects" {
			t.Fatalf("vals[9] should be sql.NullString(ManageCustomObjects), got %#v", vals[9])
		}

		// ListFields JSON (index 13 — shifted by AccessPolicy at 11)
		listFields, ok := vals[13].(sql.NullString)
		if !ok {
			t.Fatalf("vals[13] should be sql.NullString, got %T", vals[13])
		}
		if !listFields.Valid {
			t.Fatal("ListFields should be valid")
		}
		if !strings.Contains(listFields.String, "Name") || !strings.Contains(listFields.String, "Status") {
			t.Errorf("ListFields JSON = %q, should contain Name and Status", listFields.String)
		}

		// TableType for custom = custom_object (index 16)
		if vals[16] != constants.TableTypeCustomObject {
			t.Errorf("TableType = %v, want %v", vals[16], constants.TableTypeCustomObject)
		}
	})
}

// -----------------------------------------------------------------------
// prepareFieldDBValues  (schema_helpers.go)
// -----------------------------------------------------------------------

func TestPrepareFieldDBValues(t *testing.T) {
	r := &SchemaRepository{db: nil}

	t.Run("simple string field", func(t *testing.T) {
		field := &models.FieldMetadata{
			APIName:      "Name",
			Label:        "Name",
			Type:         constants.FieldTypeString,
			Required:     true,
			IsNameField:  true,
			PhysicalName: "Name",
		}

		vals, err := r.prepareFieldDBValues(field)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if len(vals) != 21 {
			t.Fatalf("expected 21 values, got %d", len(vals))
		}

		// APIName (index 0)
		if vals[0] != "Name" {
			t.Errorf("vals[0] (APIName) = %v, want Name", vals[0])
		}

		// Required (index 3) should be 1
		if vals[3] != 1 {
			t.Errorf("vals[3] (required) = %v, want 1", vals[3])
		}

		// IsNameField (index 8) should be 1
		if vals[8] != 1 {
			t.Errorf("vals[8] (isNameField) = %v, want 1", vals[8])
		}
	})

	t.Run("reference field with options", func(t *testing.T) {
		dr := models.DeleteRule(constants.DeleteRuleCascade)
		relName := "Contacts"
		field := &models.FieldMetadata{
			APIName:          "AccountId",
			Label:            "Account",
			Type:             constants.FieldTypeReference,
			ReferenceTo:      []string{"Account"},
			DeleteRule:       &dr,
			RelationshipName: &relName,
			IsPolymorphic:    true,
			IsMasterDetail:   true,
			PhysicalName:     "AccountId",
		}

		vals, err := r.prepareFieldDBValues(field)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// IsMasterDetail (index 16) should be 1
		if vals[16] != 1 {
			t.Errorf("vals[16] (isMasterDetail) = %v, want 1", vals[16])
		}

		// IsPolymorphic (index 17) should be 1
		if vals[17] != 1 {
			t.Errorf("vals[17] (isPolymorphic) = %v, want 1", vals[17])
		}

		// DeleteRule (index 18)
		deleteRule, ok := vals[18].(sql.NullString)
		if !ok {
			t.Fatalf("vals[18] should be sql.NullString, got %T", vals[18])
		}
		if !deleteRule.Valid || deleteRule.String != "Cascade" {
			t.Errorf("DeleteRule = %v, want Cascade", deleteRule)
		}

		// RelationshipName (index 19)
		relNameVal, ok := vals[19].(sql.NullString)
		if !ok {
			t.Fatalf("vals[19] should be sql.NullString, got %T", vals[19])
		}
		if !relNameVal.Valid || relNameVal.String != "Contacts" {
			t.Errorf("RelationshipName = %v, want Contacts", relNameVal)
		}
	})

	t.Run("field with options and rollup config", func(t *testing.T) {
		field := &models.FieldMetadata{
			APIName:      "Status",
			Label:        "Status",
			Type:         constants.FieldTypePicklist,
			Options:      models.PicklistOptionsFromStrings([]string{"Open", "Closed"}),
			PhysicalName: "Status",
		}

		vals, err := r.prepareFieldDBValues(field)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Options (index 9)
		optJSON, ok := vals[9].(string)
		if !ok {
			t.Fatalf("vals[9] should be string, got %T", vals[9])
		}
		if !strings.Contains(optJSON, "Open") || !strings.Contains(optJSON, "Closed") {
			t.Errorf("Options JSON = %q, should contain Open and Closed", optJSON)
		}
	})

	t.Run("formula field with return type", func(t *testing.T) {
		formulaExpr := "price * quantity"
		rt := models.FieldType("double")
		field := &models.FieldMetadata{
			APIName:      "Total",
			Label:        "Total",
			Type:         constants.FieldTypeFormula,
			Formula:      &formulaExpr,
			ReturnType:   &rt,
			PhysicalName: "Total",
		}

		vals, err := r.prepareFieldDBValues(field)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Formula (index 13)
		formula, ok := vals[13].(sql.NullString)
		if !ok {
			t.Fatalf("vals[13] should be sql.NullString, got %T", vals[13])
		}
		if !formula.Valid || formula.String != "price * quantity" {
			t.Errorf("Formula = %v, want 'price * quantity'", formula)
		}

		// ReturnType (index 14)
		returnType, ok := vals[14].(sql.NullString)
		if !ok {
			t.Fatalf("vals[14] should be sql.NullString, got %T", vals[14])
		}
		if !returnType.Valid || returnType.String != "double" {
			t.Errorf("ReturnType = %v, want 'double'", returnType)
		}
	})

	t.Run("field with all nil optionals", func(t *testing.T) {
		field := &models.FieldMetadata{
			APIName:      "Simple",
			Label:        "Simple",
			Type:         constants.FieldTypeString,
			PhysicalName: "Simple",
		}

		vals, err := r.prepareFieldDBValues(field)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Required (index 3) should be 0
		if vals[3] != 0 {
			t.Errorf("required = %v, want 0", vals[3])
		}

		// IsUnique (index 4) should be 0
		if vals[4] != 0 {
			t.Errorf("unique = %v, want 0", vals[4])
		}

		// IsSystem (index 7) should be 0
		if vals[7] != 0 {
			t.Errorf("isSystem = %v, want 0", vals[7])
		}

		// Options (index 9) should be nil
		if vals[9] != nil {
			t.Errorf("options = %v, want nil", vals[9])
		}

		// RollupConfig (index 15) should be nil
		if vals[15] != nil {
			t.Errorf("rollupConfig = %v, want nil", vals[15])
		}
	})
}

// -----------------------------------------------------------------------
// prepareObjectDBMap  (schema_helpers.go)
// -----------------------------------------------------------------------

func TestPrepareObjectDBMap(t *testing.T) {
	r := &SchemaRepository{db: nil}

	t.Run("basic object to map", func(t *testing.T) {
		obj := &models.ObjectMetadata{
			ID:          "obj-1",
			APIName:     "Account",
			Label:       "Account",
			PluralLabel: "Accounts",
		}

		m, err := r.prepareObjectDBMap(obj)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if m[constants.FieldID] != "obj-1" {
			t.Errorf("ID = %v, want obj-1", m[constants.FieldID])
		}
		if m[constants.FieldSysObject_APIName] != "Account" {
			t.Errorf("APIName = %v, want Account", m[constants.FieldSysObject_APIName])
		}
		if m[constants.FieldSysObject_Label] != "Account" {
			t.Errorf("Label = %v, want Account", m[constants.FieldSysObject_Label])
		}

		// SharingModel defaults to Private
		if m[constants.FieldSysObject_SharingModel] != models.SharingModel(constants.SharingModelPrivate) {
			t.Errorf("SharingModel = %v, want Private", m[constants.FieldSysObject_SharingModel])
		}
		if m[constants.FieldSysObject_IsCustomized] != false {
			t.Errorf("IsCustomized = %v, want false", m[constants.FieldSysObject_IsCustomized])
		}

		// TableType for standard business objects
		if m[constants.FieldSysObject_TableType] != constants.TableTypeStandardObject {
			t.Errorf("TableType = %v, want standard_object", m[constants.FieldSysObject_TableType])
		}
	})

	t.Run("custom object with sharing model", func(t *testing.T) {
		obj := &models.ObjectMetadata{
			ID:           "obj-2",
			APIName:      "Custom__c",
			Label:        "Custom",
			PluralLabel:  "Customs",
			IsCustom:     true,
			SharingModel: constants.SharingModelPublicReadWrite,
		}

		m, err := r.prepareObjectDBMap(obj)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if m[constants.FieldSysObject_IsCustom] != true {
			t.Errorf("IsCustom = %v, want true", m[constants.FieldSysObject_IsCustom])
		}
		if m[constants.FieldSysObject_SharingModel] != constants.SharingModelPublicReadWrite {
			t.Errorf("SharingModel = %v, want ReadWrite", m[constants.FieldSysObject_SharingModel])
		}
		if m[constants.FieldSysObject_IsCustomized] != false {
			t.Errorf("IsCustomized = %v, want false", m[constants.FieldSysObject_IsCustomized])
		}
		if m[constants.FieldSysObject_TableType] != constants.TableTypeCustomObject {
			t.Errorf("TableType = %v, want custom_object", m[constants.FieldSysObject_TableType])
		}
	})
}

// -----------------------------------------------------------------------
// prepareFieldDBMap  (schema_helpers.go)
// -----------------------------------------------------------------------

func TestPrepareFieldDBMap(t *testing.T) {
	r := &SchemaRepository{db: nil}

	t.Run("simple field", func(t *testing.T) {
		field := &models.FieldMetadata{
			APIName:  "Email",
			Label:    "Email",
			Type:     constants.FieldTypeEmail,
			Required: true,
		}

		m, err := r.prepareFieldDBMap(field, "obj-1", "field-1")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if m[constants.FieldID] != "field-1" {
			t.Errorf("ID = %v, want field-1", m[constants.FieldID])
		}
		if m[constants.FieldObjectID] != "obj-1" {
			t.Errorf("ObjectID = %v, want obj-1", m[constants.FieldObjectID])
		}
		if m[constants.FieldAPIName] != "Email" {
			t.Errorf("APIName = %v, want Email", m[constants.FieldAPIName])
		}
		if m[constants.FieldSysField_Required] != true {
			t.Errorf("Required = %v, want true", m[constants.FieldSysField_Required])
		}
	})

	t.Run("field with return type and formula", func(t *testing.T) {
		formulaExpr := "amount * tax_rate"
		rt := models.FieldType("double")
		field := &models.FieldMetadata{
			APIName:    "TaxAmount",
			Label:      "Tax Amount",
			Type:       constants.FieldTypeFormula,
			Formula:    &formulaExpr,
			ReturnType: &rt,
		}

		m, err := r.prepareFieldDBMap(field, "obj-1", "field-2")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		formula, ok := m[constants.FieldSysField_Formula].(sql.NullString)
		if !ok {
			t.Fatalf("Formula should be sql.NullString, got %T", m[constants.FieldSysField_Formula])
		}
		if !formula.Valid || formula.String != "amount * tax_rate" {
			t.Errorf("Formula = %v, want 'amount * tax_rate'", formula)
		}

		returnType, ok := m[constants.FieldSysField_ReturnType].(sql.NullString)
		if !ok {
			t.Fatalf("ReturnType should be sql.NullString, got %T", m[constants.FieldSysField_ReturnType])
		}
		if !returnType.Valid || returnType.String != "double" {
			t.Errorf("ReturnType = %v, want 'double'", returnType)
		}
	})
}

// -----------------------------------------------------------------------
// getObjectInsertQuery  (schema_helpers.go)
// -----------------------------------------------------------------------

func TestGetObjectInsertQuery(t *testing.T) {
	r := &SchemaRepository{db: nil}

	q := r.getObjectInsertQuery()

	if !strings.Contains(q, "INSERT INTO") {
		t.Error("query should contain INSERT INTO")
	}
	if !strings.Contains(q, constants.TableObject) {
		t.Errorf("query should reference table %q", constants.TableObject)
	}
	if !strings.Contains(q, "ON DUPLICATE KEY UPDATE") {
		t.Error("query should contain ON DUPLICATE KEY UPDATE")
	}
	if !strings.Contains(q, "NOW()") {
		t.Error("query should contain NOW()")
	}
}

func TestGetObjectStrictInsertQuery(t *testing.T) {
	r := &SchemaRepository{db: nil}

	q := r.getObjectStrictInsertQuery()

	if !strings.Contains(q, "INSERT INTO") {
		t.Error("query should contain INSERT INTO")
	}
	if !strings.Contains(q, constants.TableObject) {
		t.Errorf("query should reference table %q", constants.TableObject)
	}
	// Strict insert should NOT have ON DUPLICATE KEY
	if strings.Contains(q, "ON DUPLICATE KEY UPDATE") {
		t.Error("strict insert should NOT contain ON DUPLICATE KEY UPDATE")
	}
}

func TestGetFieldInsertQuery(t *testing.T) {
	r := &SchemaRepository{db: nil}

	q := r.getFieldInsertQuery()

	if !strings.Contains(q, "INSERT INTO") {
		t.Error("query should contain INSERT INTO")
	}
	if !strings.Contains(q, constants.TableField) {
		t.Errorf("query should reference table %q", constants.TableField)
	}
	if !strings.Contains(q, "ON DUPLICATE KEY UPDATE") {
		t.Error("query should contain ON DUPLICATE KEY UPDATE")
	}
	if !strings.Contains(q, "NOW()") {
		t.Error("query should contain NOW()")
	}
}

// -----------------------------------------------------------------------
// prepareObjectDBMap — extended branch coverage
// -----------------------------------------------------------------------

func TestPrepareObjectDBMap_AllBranches(t *testing.T) {
	r := &SchemaRepository{db: nil}

	t.Run("object with icon and list fields", func(t *testing.T) {
		desc := "Test description"
		appID := "app-123"
		pathField := "/path"
		themeColor := "#00FF00"
		obj := &models.ObjectMetadata{
			ID:           "obj-3",
			APIName:      "TestObj",
			Label:        "Test",
			PluralLabel:  "Tests",
			Icon:         "standard:account",
			Description:  &desc,
			SharingModel: constants.SharingModelPublicRead,
			AppID:        &appID,
			ListFields:   []string{"Name", "Email", "Phone"},
			PathField:    &pathField,
			ThemeColor:   &themeColor,
		}

		m, err := r.prepareObjectDBMap(obj)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Icon should be valid
		icon, ok := m[constants.FieldSysObject_Icon].(sql.NullString)
		if !ok || !icon.Valid || icon.String != "standard:account" {
			t.Errorf("Icon = %v, want standard:account", m[constants.FieldSysObject_Icon])
		}

		// Description
		desc2, ok := m[constants.FieldSysObject_Description].(sql.NullString)
		if !ok || !desc2.Valid || desc2.String != "Test description" {
			t.Errorf("Description = %v, want 'Test description'", m[constants.FieldSysObject_Description])
		}

		// AppID
		appIDVal, ok := m[constants.FieldSysObject_AppID].(sql.NullString)
		if !ok || !appIDVal.Valid || appIDVal.String != "app-123" {
			t.Errorf("AppID = %v, want app-123", m[constants.FieldSysObject_AppID])
		}

		// ListFields should contain JSON
		listFieldsVal, ok := m[constants.FieldSysObject_ListFields].(sql.NullString)
		if !ok || !listFieldsVal.Valid {
			t.Fatal("ListFields should be valid NullString")
		}
		if !strings.Contains(listFieldsVal.String, "Phone") {
			t.Errorf("ListFields JSON should contain Phone: %q", listFieldsVal.String)
		}

		// PathField
		pathVal, ok := m[constants.FieldSysObject_PathField].(sql.NullString)
		if !ok || !pathVal.Valid || pathVal.String != "/path" {
			t.Errorf("PathField = %v, want /path", m[constants.FieldSysObject_PathField])
		}

		// ThemeColor
		colorVal, ok := m[constants.FieldSysObject_ThemeColor].(sql.NullString)
		if !ok || !colorVal.Valid || colorVal.String != "#00FF00" {
			t.Errorf("ThemeColor = %v, want #00FF00", m[constants.FieldSysObject_ThemeColor])
		}
	})

	t.Run("object with nil optionals", func(t *testing.T) {
		obj := &models.ObjectMetadata{
			ID:      "obj-4",
			APIName: "Bare",
			Label:   "Bare",
		}

		m, err := r.prepareObjectDBMap(obj)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Description should be null
		descVal, ok := m[constants.FieldSysObject_Description].(sql.NullString)
		if !ok || descVal.Valid {
			t.Error("Description should be invalid NullString")
		}

		// AppID should be null
		appVal, ok := m[constants.FieldSysObject_AppID].(sql.NullString)
		if !ok || appVal.Valid {
			t.Error("AppID should be invalid NullString")
		}

		// Icon should be null
		iconVal, ok := m[constants.FieldSysObject_Icon].(sql.NullString)
		if !ok || iconVal.Valid {
			t.Error("Icon should be invalid NullString")
		}

		// ListFields should be null
		lfVal, ok := m[constants.FieldSysObject_ListFields].(sql.NullString)
		if !ok || lfVal.Valid {
			t.Error("ListFields should be invalid NullString")
		}

		// PathField should be null
		pfVal, ok := m[constants.FieldSysObject_PathField].(sql.NullString)
		if !ok || pfVal.Valid {
			t.Error("PathField should be invalid NullString")
		}

		// ThemeColor should be null
		tcVal, ok := m[constants.FieldSysObject_ThemeColor].(sql.NullString)
		if !ok || tcVal.Valid {
			t.Error("ThemeColor should be invalid NullString")
		}
	})
}

// -----------------------------------------------------------------------
// prepareFieldDBMap — extended branch coverage
// -----------------------------------------------------------------------

func TestPrepareFieldDBMap_AllBranches(t *testing.T) {
	r := &SchemaRepository{db: nil}

	t.Run("field with all optional fields populated", func(t *testing.T) {
		defaultVal := "N/A"
		helpText := "Enter the value"
		formulaExpr := "a + b"
		rt := models.FieldType("double")
		dr := models.DeleteRule(constants.DeleteRuleSetNull)
		relName := "ParentAccount"
		minLen := 1
		maxLen := 255

		field := &models.FieldMetadata{
			APIName:          "TestField",
			Label:            "Test Field",
			Type:             constants.FieldTypeString,
			Required:         true,
			IsUnique:         true,
			DefaultValue:     &defaultVal,
			HelpText:         &helpText,
			IsSystem:         true,
			IsNameField:      true,
			Options:          models.PicklistOptionsFromStrings([]string{"A", "B", "C"}),
			MinLength:        &minLen,
			MaxLength:        &maxLen,
			ReferenceTo:      []string{"Account", "Contact"},
			Formula:          &formulaExpr,
			ReturnType:       &rt,
			RollupConfig:     &models.RollupConfig{SummaryField: "Amount"},
			IsMasterDetail:   true,
			IsPolymorphic:    true,
			DeleteRule:       &dr,
			RelationshipName: &relName,
			PhysicalName:     "test_field",
		}

		m, err := r.prepareFieldDBMap(field, "obj-x", "field-x")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Check default value
		dv, ok := m[constants.FieldSysField_DefaultValue].(sql.NullString)
		if !ok || !dv.Valid || dv.String != "N/A" {
			t.Errorf("DefaultValue = %v, want N/A", m[constants.FieldSysField_DefaultValue])
		}

		// Check help text
		ht, ok := m[constants.FieldSysField_HelpText].(sql.NullString)
		if !ok || !ht.Valid || ht.String != "Enter the value" {
			t.Errorf("HelpText = %v, want 'Enter the value'", m[constants.FieldSysField_HelpText])
		}

		// Check options is JSON
		optJSON, ok := m[constants.FieldSysField_Options].(string)
		if !ok {
			t.Fatalf("Options should be string, got %T", m[constants.FieldSysField_Options])
		}
		if !strings.Contains(optJSON, "A") {
			t.Errorf("Options JSON should contain A: %q", optJSON)
		}

		// Check rollup config is JSON
		rcJSON, ok := m[constants.FieldSysField_RollupConfig].(string)
		if !ok {
			t.Fatalf("RollupConfig should be string, got %T", m[constants.FieldSysField_RollupConfig])
		}
		if !strings.Contains(rcJSON, "Amount") {
			t.Errorf("RollupConfig JSON should contain Amount: %q", rcJSON)
		}

		// Check min/max length
		minLenVal, ok := m[constants.FieldSysField_MinLength].(sql.NullInt64)
		if !ok || !minLenVal.Valid || minLenVal.Int64 != 1 {
			t.Errorf("MinLength = %v, want 1", m[constants.FieldSysField_MinLength])
		}

		maxLenVal, ok := m[constants.FieldSysField_MaxLength].(sql.NullInt64)
		if !ok || !maxLenVal.Valid || maxLenVal.Int64 != 255 {
			t.Errorf("MaxLength = %v, want 255", m[constants.FieldSysField_MaxLength])
		}

		// ReferenceTo should be JSON
		refTo, ok := m[constants.FieldReferenceTo].(sql.NullString)
		if !ok || !refTo.Valid {
			t.Fatal("ReferenceTo should be valid NullString")
		}
		if !strings.Contains(refTo.String, "Account") || !strings.Contains(refTo.String, "Contact") {
			t.Errorf("ReferenceTo JSON should contain Account and Contact: %q", refTo.String)
		}

		// PhysicalName
		pn, ok := m[constants.ColSysField_PhysicalName].(sql.NullString)
		if !ok || !pn.Valid || pn.String != "test_field" {
			t.Errorf("PhysicalName = %v, want test_field", m[constants.ColSysField_PhysicalName])
		}
	})
}

// -----------------------------------------------------------------------
// prepareFieldDBValues — extended for rollup config
// -----------------------------------------------------------------------

func TestPrepareFieldDBValues_WithRollupConfig(t *testing.T) {
	r := &SchemaRepository{db: nil}

	field := &models.FieldMetadata{
		APIName:      "TotalAmount",
		Label:        "Total Amount",
		Type:         constants.FieldTypeDouble,
		RollupConfig: &models.RollupConfig{SummaryField: "Amount", CalcType: "SUM"},
		PhysicalName: "TotalAmount",
	}

	vals, err := r.prepareFieldDBValues(field)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// RollupConfig (index 15) should be non-nil JSON string
	if vals[15] == nil {
		t.Error("rollupConfig should not be nil")
	}
	rcStr, ok := vals[15].(string)
	if !ok {
		t.Fatalf("rollupConfig should be string, got %T", vals[15])
	}
	if !strings.Contains(rcStr, "Amount") || !strings.Contains(rcStr, "SUM") {
		t.Errorf("rollupConfig JSON = %q, should contain Amount and SUM", rcStr)
	}
}

func TestPrepareFieldDBValues_WithReturnType(t *testing.T) {
	r := &SchemaRepository{db: nil}

	rt := models.FieldType("currency")
	field := &models.FieldMetadata{
		APIName:      "CalcField",
		Label:        "Calculated",
		Type:         constants.FieldTypeFormula,
		ReturnType:   &rt,
		PhysicalName: "CalcField",
	}

	vals, err := r.prepareFieldDBValues(field)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// ReturnType (index 14) should be valid NullString
	rtVal, ok := vals[14].(sql.NullString)
	if !ok {
		t.Fatalf("returnType should be sql.NullString, got %T", vals[14])
	}
	if !rtVal.Valid || rtVal.String != "currency" {
		t.Errorf("ReturnType = %v, want 'currency'", rtVal)
	}
}

func TestPrepareFieldDBValues_WithDeleteRule(t *testing.T) {
	r := &SchemaRepository{db: nil}

	dr := models.DeleteRule(constants.DeleteRuleCascade)
	relName := "Contacts"
	field := &models.FieldMetadata{
		APIName:          "AccountId",
		Label:            "Account",
		Type:             constants.FieldTypeReference,
		DeleteRule:       &dr,
		RelationshipName: &relName,
		PhysicalName:     "AccountId",
	}

	vals, err := r.prepareFieldDBValues(field)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// DeleteRule (index 18)
	drVal, ok := vals[18].(sql.NullString)
	if !ok {
		t.Fatalf("deleteRule should be sql.NullString, got %T", vals[18])
	}
	if !drVal.Valid || drVal.String != "Cascade" {
		t.Errorf("DeleteRule = %v, want 'Cascade'", drVal)
	}

	// RelationshipName (index 19)
	rnVal, ok := vals[19].(sql.NullString)
	if !ok {
		t.Fatalf("relationshipName should be sql.NullString, got %T", vals[19])
	}
	if !rnVal.Valid || rnVal.String != "Contacts" {
		t.Errorf("RelationshipName = %v, want 'Contacts'", rnVal)
	}
}

// -----------------------------------------------------------------------
// prepareObjectDBValues — all branches
// -----------------------------------------------------------------------

func TestPrepareObjectDBValues_WithAllBranches(t *testing.T) {
	r := &SchemaRepository{db: nil}

	desc := "Full test"
	appID := "app-full"
	pathField := "/records"
	themeColor := "#ABC123"
	obj := &models.ObjectMetadata{
		APIName:                 "FullObj",
		Label:                   "Full Object",
		PluralLabel:             "Full Objects",
		Icon:                    "standard:opportunity",
		Description:             &desc,
		IsCustom:                true,
		SharingModel:            constants.SharingModelPublicRead,
		EnableHierarchySharing:  true,
		RequiredWritePermission: "ManageCustomObjects",
		AppID:                   &appID,
		ListFields:              []string{"Name"},
		PathField:               &pathField,
		ThemeColor:              &themeColor,
	}

	vals, err := r.prepareObjectDBValues(obj)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Icon (index 3) should be valid
	icon, ok := vals[3].(sql.NullString)
	if !ok || !icon.Valid || icon.String != "standard:opportunity" {
		t.Errorf("Icon = %v, want standard:opportunity", vals[3])
	}

	// SharingModel should be the specified one (index 7)
	if vals[7] != constants.SharingModelPublicRead {
		t.Errorf("SharingModel = %v, want PublicRead", vals[7])
	}
	if vals[8] != true {
		t.Errorf("EnableHierarchySharing = %v, want true", vals[8])
	}

	// Table type for custom (index 16 — shifted by AccessPolicy at index 11)
	if vals[16] != constants.TableTypeCustomObject {
		t.Errorf("TableType = %v, want custom_object", vals[16])
	}
}
