package metadata

import (
	"context"
	"errors"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

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

// fakeAutoNumberInvalidator records InvalidateObject calls for
// assertion in the Codex Round-5 V1-P2 / V2-H3 wire-up tests.
type fakeAutoNumberInvalidator struct {
	mu      sync.Mutex
	calls   []string
	wantErr error
}

func (f *fakeAutoNumberInvalidator) InvalidateObject(_ context.Context, objectAPIName string) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.calls = append(f.calls, objectAPIName)
	return f.wantErr
}

func (f *fakeAutoNumberInvalidator) Calls() []string {
	f.mu.Lock()
	defer f.mu.Unlock()
	return append([]string(nil), f.calls...)
}

// TestCreateField_AutoNumber_InvalidatesMetadataSource verifies the
// fix for Codex Round-5 V1-P2 / V2-H3: creating an autonumber field
// MUST invalidate the autonumber engine's MetadataSource so the new
// rule is visible without a process restart.
func TestCreateField_AutoNumber_InvalidatesMetadataSource(t *testing.T) {
	ms, metaRepo, _, _ := newTestService()
	ctx := context.Background()
	inv := &fakeAutoNumberInvalidator{}
	ms.SetAutoNumberInvalidator(inv)

	metaRepo.schemas["account"] = &models.ObjectMetadata{
		ID:      "obj_account",
		APIName: "Account",
		Label:   "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Label: "ID", Type: constants.FieldTypeString, IsSystem: true},
		},
	}
	defaultFormat := "ACC-{0000}"
	field := &models.FieldMetadata{
		APIName:      "AccountNumber",
		Label:        "Account Number",
		Type:         constants.FieldTypeAutonumber,
		DefaultValue: &defaultFormat,
	}

	require.NoError(t, ms.CreateField(ctx, "Account", field))

	assert.Equal(t, []string{"Account"}, inv.Calls(),
		"autonumber invalidator should receive exactly one call for the object")
}

// TestCreateField_AutoNumber_InvalidatorErrorDoesNotFailCreate covers
// the non-fatal path: invalidation failure logs but does not fail the
// CreateField call — the field row is committed and a subsequent
// explicit refresh will pick it up. Consistent with the existing
// ConfigureAutoNumberField log-and-continue pattern.
func TestCreateField_AutoNumber_InvalidatorErrorDoesNotFailCreate(t *testing.T) {
	ms, metaRepo, _, _ := newTestService()
	ctx := context.Background()
	inv := &fakeAutoNumberInvalidator{wantErr: errors.New("refresh died")}
	ms.SetAutoNumberInvalidator(inv)

	metaRepo.schemas["account"] = &models.ObjectMetadata{
		ID:      "obj_account",
		APIName: "Account",
		Label:   "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Label: "ID", Type: constants.FieldTypeString, IsSystem: true},
		},
	}
	defaultFormat := "ACC-{0000}"
	field := &models.FieldMetadata{
		APIName:      "AccountNumber",
		Label:        "Account Number",
		Type:         constants.FieldTypeAutonumber,
		DefaultValue: &defaultFormat,
	}
	require.NoError(t, ms.CreateField(ctx, "Account", field))
	assert.Equal(t, []string{"Account"}, inv.Calls())
}

// TestCreateField_NonAutoNumber_DoesNotInvalidate confirms we only
// nudge the autonumber engine on autonumber-type fields — unrelated
// field creates should not pay a full metadata refresh.
func TestCreateField_NonAutoNumber_DoesNotInvalidate(t *testing.T) {
	ms, metaRepo, _, _ := newTestService()
	ctx := context.Background()
	inv := &fakeAutoNumberInvalidator{}
	ms.SetAutoNumberInvalidator(inv)

	metaRepo.schemas["account"] = &models.ObjectMetadata{
		ID:      "obj_account",
		APIName: "Account",
		Label:   "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Label: "ID", Type: constants.FieldTypeString, IsSystem: true},
		},
	}
	field := &models.FieldMetadata{
		APIName: "Industry",
		Label:   "Industry",
		Type:    constants.FieldTypeString,
	}
	require.NoError(t, ms.CreateField(ctx, "Account", field))
	assert.Empty(t, inv.Calls(), "non-autonumber field should not trigger invalidation")
}

// TestBatchCreateFields_AutoNumber_InvalidatesOnce pins the batch
// optimization: a batch of N autonumber fields on the same object
// invalidates the engine's snapshot exactly once.
func TestBatchCreateFields_AutoNumber_InvalidatesOnce(t *testing.T) {
	ms, metaRepo, _, _ := newTestService()
	ctx := context.Background()
	inv := &fakeAutoNumberInvalidator{}
	ms.SetAutoNumberInvalidator(inv)

	metaRepo.schemas["account"] = &models.ObjectMetadata{
		ID:      "obj_account",
		APIName: "Account",
		Label:   "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Label: "ID", Type: constants.FieldTypeString, IsSystem: true},
		},
	}

	fmtA := "A-{0000}"
	fmtB := "B-{0000}"
	_, err := ms.BatchCreateFields(ctx, "Account", []models.FieldMetadata{
		{APIName: "NumberA", Label: "A", Type: constants.FieldTypeAutonumber, DefaultValue: &fmtA},
		{APIName: "NumberB", Label: "B", Type: constants.FieldTypeAutonumber, DefaultValue: &fmtB},
	})
	require.NoError(t, err)
	assert.Equal(t, []string{"Account"}, inv.Calls(),
		"batch of 2 autonumber fields should invalidate exactly once")
}
