package packs

import (
	"testing"

	sqlmock "github.com/DATA-DOG/go-sqlmock"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
)

// TestBuildFieldMetadataStampsSoleAutonumberAsNameField is Test A from issue #1079.
//
// A pack ObjectSpec whose only display candidate is a sole autonumber field
// (e.g. Order.OrderNumber) must produce a FieldWithContext with IsNameField=true
// for that field even when the pack payload JSON omits "isNameField".
// Root cause: apply path never stamped the flag; every downstream consumer
// (analytics, layout seeding, name hydration) reads IsNameField from _System_Field.
func TestBuildFieldMetadataStampsSoleAutonumberAsNameField(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:      "Order",
			Label:        "Order",
			TableType:    "standard_object",
			Category:     "crm",
			SharingModel: "ControlledByParent",
			Fields: []FieldSpec{
				// No isNameField declared — matches the real Order spec payload.
				{APIName: "OrderNumber", Label: "Order Number", Type: "autonumber"},
				{APIName: "Status", Label: "Status", Type: "string", Required: true},
			},
		},
	}

	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	fields, err := buildFieldMetadata("crm-pack", specs, sm, false)
	if err != nil {
		t.Fatalf("buildFieldMetadata: %v", err)
	}

	var found *metadata.FieldWithContext
	for i := range fields {
		if fields[i].TableName == "Order" && fields[i].Field != nil && fields[i].Field.APIName == "OrderNumber" {
			found = &fields[i]
			break
		}
	}
	if found == nil {
		t.Fatal("OrderNumber FieldWithContext not found in buildFieldMetadata output")
	}
	if !found.Field.IsNameField {
		t.Error("Order.OrderNumber: IsNameField = false, want true (sole autonumber must be stamped as name field at apply time)")
	}
	if found.Field.Type != constants.FieldTypeAutonumber {
		t.Errorf("Order.OrderNumber: Type = %q, want %q", found.Field.Type, constants.FieldTypeAutonumber)
	}
}

// TestBuildFieldMetadataSoleAutonumberStampIsDeterministic is Test B from issue #1079.
//
// buildFieldMetadata must produce identical IsNameField values across repeated
// calls with the same spec (deterministic → DB INSERT ON DUPLICATE KEY UPDATE
// is idempotent → _System_Field row count cannot inflate on repeated apply).
func TestBuildFieldMetadataSoleAutonumberStampIsDeterministic(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:      "Quote",
			Label:        "Quote",
			TableType:    "standard_object",
			Category:     "crm",
			SharingModel: "ControlledByParent",
			Fields: []FieldSpec{
				{APIName: "QuoteNumber", Label: "Quote Number", Type: "autonumber"},
				{APIName: "TotalPrice", Label: "Total Price", Type: "currency"},
			},
		},
	}

	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))

	var results []bool
	for run := range 3 {
		fields, err := buildFieldMetadata("crm-pack", specs, sm, false)
		if err != nil {
			t.Fatalf("run %d: buildFieldMetadata: %v", run, err)
		}
		for _, f := range fields {
			if f.TableName == "Quote" && f.Field != nil && f.Field.APIName == "QuoteNumber" {
				results = append(results, f.Field.IsNameField)
				break
			}
		}
	}

	if len(results) != 3 {
		t.Fatalf("QuoteNumber not found in all 3 runs (got %d)", len(results))
	}
	if results[0] != results[1] || results[1] != results[2] {
		t.Errorf("IsNameField differs across runs: %v (must be deterministic)", results)
	}
	if !results[0] {
		t.Error("Quote.QuoteNumber: IsNameField = false on all runs, want true")
	}
}

// TestBuildFieldMetadataDoesNotStampWhenExplicitNameFieldExists guards that
// the sole-autonumber stamp does not activate when another field already carries
// IsNameField=true (e.g. Account has Name with isNameField=true in payload).
func TestBuildFieldMetadataDoesNotStampWhenExplicitNameFieldExists(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:      "Account",
			Label:        "Account",
			TableType:    "standard_object",
			Category:     "crm",
			SharingModel: "Public",
			Fields: []FieldSpec{
				{APIName: "Name", Label: "Account Name", Type: "string", IsNameField: true},
				{APIName: "AccountNumber", Label: "Account Number", Type: "autonumber"},
			},
		},
	}

	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	fields, err := buildFieldMetadata("crm-pack", specs, sm, false)
	if err != nil {
		t.Fatalf("buildFieldMetadata: %v", err)
	}

	for _, f := range fields {
		if f.TableName != "Account" || f.Field == nil {
			continue
		}
		switch f.Field.APIName {
		case "Name":
			if !f.Field.IsNameField {
				t.Error("Account.Name: IsNameField = false, want true (explicit declaration must be preserved)")
			}
		case "AccountNumber":
			if f.Field.IsNameField {
				t.Error("Account.AccountNumber: IsNameField = true, want false (must not stamp when explicit name field exists)")
			}
		}
	}
}

// TestBuildFieldMetadataDoesNotStampWhenMultipleAutonumbers guards that
// the sole-autonumber stamp does not activate when an object declares
// more than one autonumber field (ambiguous — no stamp).
func TestBuildFieldMetadataDoesNotStampWhenMultipleAutonumbers(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:      "MultiAuto",
			Label:        "Multi Auto",
			TableType:    "standard_object",
			Category:     "crm",
			SharingModel: "Public",
			Fields: []FieldSpec{
				{APIName: "Num1", Label: "Number 1", Type: "autonumber"},
				{APIName: "Num2", Label: "Number 2", Type: "autonumber"},
			},
		},
	}

	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	fields, err := buildFieldMetadata("crm-pack", specs, sm, false)
	if err != nil {
		t.Fatalf("buildFieldMetadata: %v", err)
	}

	for _, f := range fields {
		if f.TableName != "MultiAuto" || f.Field == nil {
			continue
		}
		if f.Field.IsNameField {
			t.Errorf("MultiAuto.%s: IsNameField = true, want false (ambiguous multi-autonumber must not be stamped)", f.Field.APIName)
		}
	}
}

// TestLoadNameFieldMapIsReadOnly is Test C from issue #1079.
//
// loadNameFieldMap is called from both the backfill and verify code paths.
// It MUST remain a pure SELECT helper — no UPDATE/INSERT on _System_Field.
// Verified via sqlmock: only the expected SELECT query is issued; any unexpected
// write statement would cause sqlmock to return an error.
func TestLoadNameFieldMapIsReadOnly(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(`SELECT o\.ApiName, f\.ApiName`).
		WillReturnRows(sqlmock.NewRows([]string{"objApi", "fieldApi"}).
			AddRow("Order", "OrderNumber").
			AddRow("Quote", "QuoteNumber"))

	result, err := loadNameFieldMap(db)
	if err != nil {
		t.Fatalf("loadNameFieldMap: %v", err)
	}

	if got := result["order"]; got != "OrderNumber" {
		t.Errorf("name field for 'order' = %q, want 'OrderNumber'", got)
	}
	if got := result["quote"]; got != "QuoteNumber" {
		t.Errorf("name field for 'quote' = %q, want 'QuoteNumber'", got)
	}

	// All expectations met and ONLY the SELECT was expected.
	// Any unexpected UPDATE/INSERT would have caused an error above or here.
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("sql expectations not met (loadNameFieldMap issued unexpected statements): %v", err)
	}
}
