package metadata

import (
	"context"
	"testing"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
	sharedEvents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

type historyTransitionSchemaRepo struct {
	metadataUpdateFieldSchemaRepo
	admissionCalls int
	calls          int
	changed        bool
	err            error
	tx             interface{}
}

func (r *historyTransitionSchemaRepo) AdmitAdminFieldMetadataWrite(_ context.Context, _, _, _ string, tx interface{}) error {
	r.admissionCalls++
	r.tx = tx
	return nil
}

func (r *historyTransitionSchemaRepo) ApplyHistoryTrackingTransition(_ context.Context, _ string, _ bool, tx interface{}, _, _ bool) (bool, error) {
	r.calls++
	r.tx = tx
	return r.changed, r.err
}

type historyOwnerTxKey struct{}
type historyOwnerTx struct{}

type historyOwnerTxManager struct {
	calls int
}

func (m *historyOwnerTxManager) WithTransaction(ctx context.Context, fn func(context.Context) error) error {
	m.calls++
	txCtx := context.WithValue(ctx, historyOwnerTxKey{}, &historyOwnerTx{})
	txCtx = ports.WithAfterCommitHooks(txCtx)
	txCtx = ports.WithHistoryTrackingAttemptState(txCtx)
	if err := fn(txCtx); err != nil {
		return err
	}
	ports.RunAfterCommitHooks(txCtx)
	return nil
}
func (m *historyOwnerTxManager) WithPessimisticTransaction(ctx context.Context, fn func(context.Context) error) error {
	return m.WithTransaction(ctx, fn)
}
func (m *historyOwnerTxManager) WithRetry(ctx context.Context, fn func(context.Context) error, _ int) error {
	return m.WithTransaction(ctx, fn)
}
func (m *historyOwnerTxManager) WithPessimisticRetry(ctx context.Context, fn func(context.Context) error, _ int) error {
	return m.WithTransaction(ctx, fn)
}
func (m *historyOwnerTxManager) IsTransactionActive(ctx context.Context) bool {
	return ctx.Value(historyOwnerTxKey{}) != nil
}
func (*historyOwnerTxManager) IsPessimisticTx(context.Context) bool { return false }
func (m *historyOwnerTxManager) ExtractTx(ctx context.Context) interface{} {
	return ctx.Value(historyOwnerTxKey{})
}
func (*historyOwnerTxManager) DetachTx(ctx context.Context) context.Context { return ctx }

type historyOwnerEventBus struct {
	order *[]string
}

func (*historyOwnerEventBus) Subscribe(sharedEvents.EventType, ports.EventHandler) func() {
	return func() {}
}
func (b *historyOwnerEventBus) Publish(context.Context, sharedEvents.EventType, interface{}) error {
	*b.order = append(*b.order, "event")
	return nil
}
func (*historyOwnerEventBus) PublishAsyncWithContext(context.Context, sharedEvents.EventType, interface{}) {
}

func TestUpdateFieldTrackHistoryOwnsTransactionAndRunsCacheBeforeEventAfterCommit(t *testing.T) {
	field := models.FieldMetadata{ID: "fld-name", APIName: "Name", Type: "string"}
	obj := &models.ObjectMetadata{ID: "obj-account", APIName: "Account", Fields: []models.FieldMetadata{field}}
	repo := &metadataUpdateFieldRepo{schema: obj}
	schemaRepo := &historyTransitionSchemaRepo{changed: true}
	order := []string{}
	service := NewMetadataService(repo, NewSchemaManager(schemaRepo), &historyOwnerEventBus{order: &order})
	txm := &historyOwnerTxManager{}
	service.SetTransactionManager(txm)
	service.RegisterCacheInvalidationHook(func() { order = append(order, "cache") })

	err := service.UpdateFieldWithPatchPresence(context.Background(), "Account", "Name", &models.FieldMetadata{TrackHistory: true}, false, false, true, false)
	if err != nil {
		t.Fatalf("UpdateFieldWithPatchPresence() error = %v", err)
	}
	if txm.calls != 1 || schemaRepo.admissionCalls != 1 || schemaRepo.calls != 1 || schemaRepo.tx == nil {
		t.Fatalf("owner calls: tx=%d admission=%d transition=%d executor=%v", txm.calls, schemaRepo.admissionCalls, schemaRepo.calls, schemaRepo.tx)
	}
	if schemaRepo.saved != nil {
		t.Fatal("generic SaveFieldMetadataWithIDs was used for TrackHistory transition")
	}
	if len(order) != 2 || order[0] != "cache" || order[1] != "event" {
		t.Fatalf("after-commit order = %v, want [cache event]", order)
	}
}

func TestUpdateFieldTrackHistoryConflictRunsNoHooksAndDoesNotRetry(t *testing.T) {
	field := models.FieldMetadata{ID: "fld-name", APIName: "Name", Type: "string"}
	obj := &models.ObjectMetadata{ID: "obj-account", APIName: "Account", Fields: []models.FieldMetadata{field}}
	schemaRepo := &historyTransitionSchemaRepo{err: ports.ErrHistoryTrackingConflict}
	order := []string{}
	service := NewMetadataService(&metadataUpdateFieldRepo{schema: obj}, NewSchemaManager(schemaRepo), &historyOwnerEventBus{order: &order})
	txm := &historyOwnerTxManager{}
	service.SetTransactionManager(txm)
	service.RegisterCacheInvalidationHook(func() { order = append(order, "cache") })

	err := service.UpdateFieldWithPatchPresence(context.Background(), "Account", "Name", &models.FieldMetadata{TrackHistory: true}, false, false, true, false)
	if err == nil {
		t.Fatal("UpdateFieldWithPatchPresence() error = nil, want conflict")
	}
	if !apperrors.IsConflict(err) {
		t.Fatalf("UpdateFieldWithPatchPresence() error = %T %v, want conflict", err, err)
	}
	if txm.calls != 1 || schemaRepo.admissionCalls != 1 || schemaRepo.calls != 1 {
		t.Fatalf("automatic retries detected: tx=%d admission=%d transition=%d", txm.calls, schemaRepo.admissionCalls, schemaRepo.calls)
	}
	if len(order) != 0 {
		t.Fatalf("rollback hooks = %v, want none", order)
	}
}

func TestUpdateFieldTrackHistorySerializationConflictRunsNoHooksAndDoesNotRetry(t *testing.T) {
	field := models.FieldMetadata{ID: "fld-name", APIName: "Name", Type: "string"}
	obj := &models.ObjectMetadata{ID: "obj-account", APIName: "Account", Fields: []models.FieldMetadata{field}}
	schemaRepo := &historyTransitionSchemaRepo{err: ports.ErrHistorySerializationConflict}
	order := []string{}
	service := NewMetadataService(&metadataUpdateFieldRepo{schema: obj}, NewSchemaManager(schemaRepo), &historyOwnerEventBus{order: &order})
	txm := &historyOwnerTxManager{}
	service.SetTransactionManager(txm)
	service.RegisterCacheInvalidationHook(func() { order = append(order, "cache") })

	err := service.UpdateFieldWithPatchPresence(context.Background(), "Account", "Name", &models.FieldMetadata{TrackHistory: true}, false, false, true, false)
	if err == nil || !apperrors.IsConflict(err) {
		t.Fatalf("UpdateFieldWithPatchPresence() error = %T %v, want conflict", err, err)
	}
	if txm.calls != 1 || schemaRepo.admissionCalls != 1 || schemaRepo.calls != 1 {
		t.Fatalf("automatic retries detected: tx=%d admission=%d transition=%d", txm.calls, schemaRepo.admissionCalls, schemaRepo.calls)
	}
	if len(order) != 0 {
		t.Fatalf("rollback hooks = %v, want none", order)
	}
}

func TestUpdateFieldTrackHistoryRejectsMixedMutationBeforeTransaction(t *testing.T) {
	field := models.FieldMetadata{ID: "fld-name", APIName: "Name", Type: "string"}
	obj := &models.ObjectMetadata{ID: "obj-account", APIName: "Account", Fields: []models.FieldMetadata{field}}
	schemaRepo := &historyTransitionSchemaRepo{changed: true}
	service := NewMetadataService(&metadataUpdateFieldRepo{schema: obj}, NewSchemaManager(schemaRepo), nil)
	txm := &historyOwnerTxManager{}
	service.SetTransactionManager(txm)

	err := service.UpdateFieldWithPatchPresence(context.Background(), "Account", "Name", &models.FieldMetadata{Label: "Mixed", TrackHistory: true}, false, false, true, false)
	if err == nil {
		t.Fatal("mixed TrackHistory mutation error = nil")
	}
	if txm.calls != 0 || schemaRepo.admissionCalls != 0 || schemaRepo.calls != 0 {
		t.Fatalf("mixed mutation reached transaction: tx=%d admission=%d transition=%d", txm.calls, schemaRepo.admissionCalls, schemaRepo.calls)
	}
}
