package persistence

import (
	"database/sql"
	"fmt"
	"testing"
	"time"
)

// PopulatedScannable populates scan destinations with reasonable default values
// to exercise the post-Scan branching logic (e.g., NullString.Valid checks).
type PopulatedScannable struct {
	values []interface{}
}

func (p *PopulatedScannable) Scan(dest ...interface{}) error {
	for i, d := range dest {
		if i >= len(p.values) {
			break
		}
		v := p.values[i]
		if v == nil {
			continue
		}
		switch ptr := d.(type) {
		case *string:
			if s, ok := v.(string); ok {
				*ptr = s
			}
		case *bool:
			if b, ok := v.(bool); ok {
				*ptr = b
			}
		case *int:
			if n, ok := v.(int); ok {
				*ptr = n
			}
		case *sql.NullString:
			if s, ok := v.(string); ok {
				*ptr = sql.NullString{String: s, Valid: true}
			}
		case *sql.NullBool:
			if b, ok := v.(bool); ok {
				*ptr = sql.NullBool{Bool: b, Valid: true}
			}
		case *sql.NullFloat64:
			if f, ok := v.(float64); ok {
				*ptr = sql.NullFloat64{Float64: f, Valid: true}
			}
		case *sql.NullInt64:
			if n, ok := v.(int64); ok {
				*ptr = sql.NullInt64{Int64: n, Valid: true}
			}
		case *sql.NullTime:
			if t, ok := v.(time.Time); ok {
				*ptr = sql.NullTime{Time: t, Valid: true}
			}
		case *interface{}:
			*ptr = v
		}
	}
	return nil
}

func TestScanObjectPopulated(t *testing.T) {
	repo := &MetadataRepository{}

	// ColsSystemObject: 26 columns
	// ID, APIName, TableType, Label, PluralLabel, AppID, Icon, Description,
	// ThemeColor, IsCustom, Searchable, SharingModel, IsCustomized, EnableHierarchySharing, RequiredWritePermission, RequiredReadPermission, DeleteMode, PathField, ListFields,
	// IsDeleted, OwnerID, CreatedByID, LastModifiedByID, CreatedDate, LastModifiedDate
	values := make([]interface{}, 26)
	values[0] = "obj-1"                                                    // ID
	values[1] = "Account"                                                  // APIName
	values[2] = "custom_object"                                            // TableType
	values[3] = "Account"                                                  // Label
	values[4] = "Accounts"                                                 // PluralLabel
	values[5] = "app-1"                                                    // AppID
	values[6] = "standard:account"                                         // Icon
	values[7] = "Test description"                                         // Description
	values[8] = "#FF0000"                                                  // ThemeColor
	values[9] = true                                                       // IsCustom
	values[10] = false                                                     // Searchable
	values[11] = "Private"                                                 // SharingModel
	values[12] = false                                                     // IsCustomized
	values[13] = true                                                      // EnableHierarchySharing
	values[14] = "ModifyMetadata"                                          // RequiredWritePermission
	values[15] = "ViewSetup"                                               // RequiredReadPermission
	values[16] = `{"mode":"derived","fieldRead":{"inherit":"objectRead"}}` // AccessPolicy
	values[17] = nil                                                       // DeleteMode
	values[18] = "/path"                                                   // PathField
	values[19] = `["Name","Email"]`                                        // ListFields JSON
	values[20] = false                                                     // IsDeleted
	values[21] = nil                                                       // OwnerID
	values[22] = nil                                                       // CreatedByID
	values[23] = nil                                                       // LastModifiedByID
	values[24] = time.Now()                                                // CreatedDate
	values[25] = time.Now()                                                // LastModifiedDate

	scanner := &PopulatedScannable{values: values}
	obj, err := repo.scanObject(scanner)
	if err != nil {
		t.Fatalf("scanObject() error = %v", err)
	}
	if obj == nil {
		t.Fatal("scanObject() returned nil")
	}
	if obj.APIName != "Account" {
		t.Errorf("APIName = %q, want Account", obj.APIName)
	}
	if obj.Label != "Account" {
		t.Errorf("Label = %q, want Account", obj.Label)
	}
	if !obj.IsCustom {
		t.Error("IsCustom should be true")
	}
	if obj.Icon != "standard:account" {
		t.Errorf("Icon = %q, want standard:account", obj.Icon)
	}
	if obj.Description == nil || *obj.Description != "Test description" {
		t.Errorf("Description = %v, want 'Test description'", obj.Description)
	}
	if obj.ThemeColor == nil || *obj.ThemeColor != "#FF0000" {
		t.Errorf("ThemeColor = %v, want #FF0000", obj.ThemeColor)
	}
	if obj.AppID == nil || *obj.AppID != "app-1" {
		t.Errorf("AppID = %v, want app-1", obj.AppID)
	}
	if obj.PathField == nil || *obj.PathField != "/path" {
		t.Errorf("PathField = %v, want /path", obj.PathField)
	}
	if obj.AccessPolicy == nil || !obj.AccessPolicy.IsDerived() {
		t.Errorf("AccessPolicy = %#v, want derived policy", obj.AccessPolicy)
	}
	if !obj.EnableHierarchySharing {
		t.Error("EnableHierarchySharing should be true")
	}
	if obj.IsCustomized {
		t.Error("IsCustomized should be false")
	}
}

func TestScanFieldPopulated(t *testing.T) {
	repo := &MetadataRepository{}

	// ColsSystemField: 43 columns after issue #1582 RFC v1.6 removed
	// Source / IsCompound / ComposedFrom (now derived on read).
	values := make([]interface{}, 43)
	values[0] = "field-1"              // ID
	values[1] = "Account"              // ObjectAPIName (ObjectID)
	values[2] = "Email"                // APIName
	values[3] = "Email"                // Label
	values[4] = "email_col"            // PhysicalName
	values[5] = "email"                // Type
	values[6] = true                   // Required
	values[7] = true                   // IsUnique
	values[8] = false                  // IsSystem
	values[9] = false                  // IsNameField
	values[10] = true                  // Createable
	values[11] = true                  // Updateable
	values[12] = false                 // Indexed
	values[13] = nil                   // Options
	values[14] = nil                   // ReferenceTo
	values[15] = nil                   // DeleteRule
	values[16] = nil                   // Formula
	values[17] = nil                   // ReturnType
	values[18] = nil                   // FormulaMode
	values[19] = "default@example.com" // DefaultValue
	values[20] = "Enter email"         // HelpText
	values[21] = nil                   // Description
	values[22] = false                 // TrackHistory
	values[23] = nil                   // MinValue
	values[24] = nil                   // MaxValue
	values[25] = nil                   // MinLength
	values[26] = nil                   // MaxLength
	values[27] = nil                   // Regex
	values[28] = nil                   // RegexMessage
	values[29] = nil                   // Validator
	values[30] = nil                   // ControllingField
	values[31] = nil                   // PicklistDependency
	values[32] = nil                   // RollupConfig
	values[33] = false                 // IsMasterDetail
	values[34] = false                 // IsPolymorphic
	values[35] = nil                   // RelationshipName
	values[36] = false                 // IsDeleted
	values[37] = nil                   // OwnerID
	values[38] = nil                   // CreatedByID
	values[39] = nil                   // LastModifiedByID
	values[40] = time.Now()            // CreatedDate
	values[41] = time.Now()            // LastModifiedDate
	values[42] = false                 // IsCustomized

	scanner := &PopulatedScannable{values: values}
	field, objectAPIName, err := repo.scanField(scanner)
	if err != nil {
		t.Fatalf("scanField() error = %v", err)
	}
	if field == nil {
		t.Fatal("scanField() returned nil field")
	}
	if objectAPIName != "Account" {
		t.Errorf("objectAPIName = %q, want Account", objectAPIName)
	}
	if field.APIName != "Email" {
		t.Errorf("APIName = %q, want Email", field.APIName)
	}
	if field.PhysicalName != "email_col" {
		t.Errorf("PhysicalName = %q, want email_col", field.PhysicalName)
	}
	if field.DefaultValue == nil || *field.DefaultValue != "default@example.com" {
		t.Errorf("DefaultValue = %v, want default@example.com", field.DefaultValue)
	}
	if field.HelpText == nil || *field.HelpText != "Enter email" {
		t.Errorf("HelpText = %v, want 'Enter email'", field.HelpText)
	}
}

func TestScanFieldPopulatedWithAllOptionals(t *testing.T) {
	repo := &MetadataRepository{}

	// Column count shrank from 45 → 42 in issue #1582 RFC v1.6: Source /
	// IsCompound / ComposedFrom columns removed from _System_Field (now
	// derived at read time by fieldprovenance).
	values := make([]interface{}, 42)
	values[0] = "field-2"
	values[1] = "Contact"
	values[2] = "AccountId"
	values[3] = "Account"
	values[4] = "account_id"
	values[5] = "reference"
	values[6] = false                                         // Required
	values[7] = false                                         // IsUnique
	values[8] = false                                         // IsSystem
	values[9] = false                                         // IsNameField
	values[10] = true                                         // Createable
	values[11] = true                                         // Updateable
	values[12] = false                                        // Indexed
	values[13] = `["Open","Closed"]`                          // Options JSON
	values[14] = `["Account"]`                                // ReferenceTo JSON
	values[15] = "Cascade"                                    // DeleteRule
	values[16] = "amount * rate"                              // Formula
	values[17] = "double"                                     // ReturnType
	values[18] = "active"                                     // FormulaMode
	values[19] = nil                                          // DefaultValue
	values[20] = nil                                          // HelpText
	values[21] = nil                                          // Description
	values[22] = true                                         // TrackHistory
	values[23] = 0.0                                          // MinValue
	values[24] = 100.0                                        // MaxValue
	values[25] = int64(1)                                     // MinLength
	values[26] = int64(255)                                   // MaxLength
	values[27] = `^[a-z]+$`                                   // Regex
	values[28] = "Must be lowercase"                          // RegexMessage
	values[29] = "email_validator"                            // Validator
	values[30] = "ParentField"                                // ControllingField
	values[31] = `{"A":["B"]}`                                // PicklistDependency JSON
	values[32] = `{"summaryField":"Amount","calcType":"SUM"}` // RollupConfig JSON
	values[33] = true                                         // IsMasterDetail
	values[34] = true                                         // IsPolymorphic
	values[35] = "Accounts"                                   // RelationshipName
	values[36] = false                                        // IsDeleted
	values[37] = nil                                          // OwnerID
	values[38] = nil                                          // CreatedByID
	values[39] = nil                                          // LastModifiedByID
	values[40] = time.Now()                                   // CreatedDate
	values[41] = time.Now()                                   // LastModifiedDate

	scanner := &PopulatedScannable{values: values}
	field, _, err := repo.scanField(scanner)
	if err != nil {
		t.Fatalf("scanField() error = %v", err)
	}

	if field.Formula == nil || *field.Formula != "amount * rate" {
		t.Errorf("Formula = %v, want 'amount * rate'", field.Formula)
	}
	if field.ReturnType == nil || string(*field.ReturnType) != "double" {
		t.Errorf("ReturnType = %v, want double", field.ReturnType)
	}
	if field.DeleteRule == nil || string(*field.DeleteRule) != "Cascade" {
		t.Errorf("DeleteRule = %v, want Cascade", field.DeleteRule)
	}
	if !field.IsMasterDetail {
		t.Error("IsMasterDetail should be true")
	}
	if !field.IsPolymorphic {
		t.Error("IsPolymorphic should be true")
	}
	if field.RelationshipName == nil || *field.RelationshipName != "Accounts" {
		t.Errorf("RelationshipName = %v, want Accounts", field.RelationshipName)
	}
	if field.ControllingField == nil || *field.ControllingField != "ParentField" {
		t.Errorf("ControllingField = %v, want ParentField", field.ControllingField)
	}
	if field.Regex == nil || *field.Regex != `^[a-z]+$` {
		t.Errorf("Regex = %v, want ^[a-z]+$", field.Regex)
	}
	if field.RegexMessage == nil || *field.RegexMessage != "Must be lowercase" {
		t.Errorf("RegexMessage = %v, want 'Must be lowercase'", field.RegexMessage)
	}
	if field.Validator == nil || *field.Validator != "email_validator" {
		t.Errorf("Validator = %v, want email_validator", field.Validator)
	}
	if field.RollupConfig == nil {
		t.Error("RollupConfig should not be nil")
	}
	if len(field.ReferenceTo) != 1 || field.ReferenceTo[0] != "Account" {
		t.Errorf("ReferenceTo = %v, want [Account]", field.ReferenceTo)
	}
	if field.MinLength == nil || *field.MinLength != 1 {
		t.Errorf("MinLength = %v, want 1", field.MinLength)
	}
	if field.MaxLength == nil || *field.MaxLength != 255 {
		t.Errorf("MaxLength = %v, want 255", field.MaxLength)
	}

	_ = fmt.Sprintf("coverage helper: %v", field.TrackHistory)
}
