package automation

import (
	"context"
	"testing"

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

type actionCompositeTxKey struct{}
type actionCompositeActiveKey struct{}

type recordingActionTxManager struct {
	runTxCalls     int
	runPessTxCalls int
}

func (m *recordingActionTxManager) WithTransaction(ctx context.Context, fn func(context.Context) error) error {
	m.runTxCalls++
	txCtx := context.WithValue(ctx, actionCompositeTxKey{}, "optimistic")
	txCtx = context.WithValue(txCtx, actionCompositeActiveKey{}, true)
	return fn(txCtx)
}

func (m *recordingActionTxManager) WithPessimisticTransaction(ctx context.Context, fn func(context.Context) error) error {
	m.runPessTxCalls++
	txCtx := context.WithValue(ctx, actionCompositeTxKey{}, "pessimistic")
	txCtx = context.WithValue(txCtx, actionCompositeActiveKey{}, true)
	return fn(txCtx)
}

func (m *recordingActionTxManager) WithRetry(ctx context.Context, fn func(context.Context) error, _ int) error {
	return m.WithTransaction(ctx, fn)
}

func (m *recordingActionTxManager) WithPessimisticRetry(ctx context.Context, fn func(context.Context) error, _ int) error {
	return m.WithPessimisticTransaction(ctx, fn)
}

func (m *recordingActionTxManager) IsTransactionActive(ctx context.Context) bool {
	active, _ := ctx.Value(actionCompositeActiveKey{}).(bool)
	return active
}
func (m *recordingActionTxManager) IsPessimisticTx(ctx context.Context) bool {
	marker, _ := ctx.Value(actionCompositeTxKey{}).(string)
	return marker == "pessimistic"
}
func (m *recordingActionTxManager) ExtractTx(context.Context) interface{} { return nil }
func (m *recordingActionTxManager) DetachTx(ctx context.Context) context.Context {
	return ctx
}

var _ ports.TransactionManager = (*recordingActionTxManager)(nil)

type actionCompositePersistenceStub struct {
	insertCalls []models.SObject
	updateCalls []models.SObject
	insertCtx   []string
	updateCtx   []string
	insertErr   error
	updateErr   error
}

func (p *actionCompositePersistenceStub) Insert(ctx context.Context, tableName string, record models.SObject, user *models.UserSession) (models.SObject, error) {
	p.insertCalls = append(p.insertCalls, cloneActionCompositeSObject(record))
	if marker, _ := ctx.Value(actionCompositeTxKey{}).(string); marker != "" {
		p.insertCtx = append(p.insertCtx, marker)
	}
	if p.insertErr != nil {
		return nil, p.insertErr
	}
	out := cloneActionCompositeSObject(record)
	out[constants.FieldID] = "new-record"
	return out, nil
}

func (p *actionCompositePersistenceStub) PreviewBeforeInsert(context.Context, string, models.SObject, *models.UserSession) (models.SObject, error) {
	panic("actionCompositePersistenceStub.PreviewBeforeInsert: not implemented")
}

func (p *actionCompositePersistenceStub) PrelockUpdateParents(ctx context.Context, tableName string, id string, updates models.SObject, user *models.UserSession) error {
	p.updateCalls = append(p.updateCalls, cloneActionCompositeSObject(updates))
	if marker, _ := ctx.Value(actionCompositeTxKey{}).(string); marker != "" {
		p.updateCtx = append(p.updateCtx, marker)
	}
	return p.updateErr
}

func (p *actionCompositePersistenceStub) Update(ctx context.Context, tableName string, id string, updates models.SObject, user *models.UserSession) error {
	p.updateCalls = append(p.updateCalls, cloneActionCompositeSObject(updates))
	if marker, _ := ctx.Value(actionCompositeTxKey{}).(string); marker != "" {
		p.updateCtx = append(p.updateCtx, marker)
	}
	return nil
}

func (p *actionCompositePersistenceStub) PreviewBeforeUpdate(context.Context, string, string, models.SObject, models.SObject, *models.UserSession) (*models.PreparedRecordUpdate, error) {
	panic("actionCompositePersistenceStub.PreviewBeforeUpdate: not implemented")
}

func (p *actionCompositePersistenceStub) CommitPreparedUpdate(context.Context, string, string, *models.PreparedRecordUpdate, models.SObject, *models.UserSession) error {
	panic("actionCompositePersistenceStub.CommitPreparedUpdate: not implemented")
}

func (p *actionCompositePersistenceStub) Delete(context.Context, string, string, *models.UserSession) error {
	panic("actionCompositePersistenceStub.Delete: not implemented")
}

func (p *actionCompositePersistenceStub) GetRaw(context.Context, string, string) (models.SObject, error) {
	panic("actionCompositePersistenceStub.GetRaw: not implemented")
}

func (p *actionCompositePersistenceStub) GetLock(context.Context, string, string) (models.SObject, error) {
	panic("actionCompositePersistenceStub.GetLock: not implemented")
}

func (p *actionCompositePersistenceStub) GetRecordType(context.Context, string) (string, error) {
	panic("actionCompositePersistenceStub.GetRecordType: not implemented")
}

func (p *actionCompositePersistenceStub) RunInTransaction(ctx context.Context, fn func(context.Context) error) error {
	return fn(ctx)
}

func (p *actionCompositePersistenceStub) IsTransactionActive(context.Context) bool { return false }

func (p *actionCompositePersistenceStub) RunInPessimisticTransaction(ctx context.Context, fn func(context.Context) error) error {
	return fn(ctx)
}

func (p *actionCompositePersistenceStub) DetachTx(ctx context.Context) context.Context {
	return ctx
}

func (p *actionCompositePersistenceStub) RecomputeActiveFormulaProjection(_ context.Context, _ *models.UserSession, _, _ string, _ []string) (models.SObject, error) {
	return nil, nil
}

func TestActionServiceExecuteCompositeUsesPessimisticTransactionForCreateStep(t *testing.T) {
	persistence := &actionCompositePersistenceStub{}
	txm := &recordingActionTxManager{}
	service := &ActionService{persistence: persistence, txManager: txm}
	action := &models.ActionMetadata{
		ID:            "composite-create",
		ObjectAPIName: "SourceObject",
		Type:          constants.ActionTypeComposite,
		Config: map[string]interface{}{
			constants.ConfigKeySteps: []interface{}{
				map[string]interface{}{
					constants.FieldID:             "step-create",
					constants.FieldSysAction_Type: constants.ActionTypeCreateRecord,
					constants.ConfigTargetObject:  "TargetObject",
					constants.ConfigFieldMappings: map[string]interface{}{"Name": "created-from-composite"},
				},
			},
		},
	}
	ctx := &ActionContext{Record: models.SObject{"Id": "source-1"}, User: &models.UserSession{ID: "005-user"}, Results: map[string]map[string]interface{}{}}

	if err := service.executeComposite(context.Background(), action, ctx); err != nil {
		t.Fatalf("executeComposite(create): %v", err)
	}
	if txm.runPessTxCalls != 1 {
		t.Fatalf("WithPessimisticTransaction calls = %d, want 1", txm.runPessTxCalls)
	}
	if txm.runTxCalls != 0 {
		t.Fatalf("WithTransaction calls = %d, want 0", txm.runTxCalls)
	}
	if len(persistence.insertCalls) != 1 {
		t.Fatalf("Insert calls = %d, want 1", len(persistence.insertCalls))
	}
	if got := persistence.insertCtx[0]; got != "pessimistic" {
		t.Fatalf("Insert ctx marker = %q, want pessimistic", got)
	}
}

func TestActionServiceExecuteCompositePreservesOnlyRecordValidation(t *testing.T) {
	newService := func(persistence *actionCompositePersistenceStub) *ActionService {
		return &ActionService{persistence: persistence, txManager: &recordingActionTxManager{}}
	}
	newContext := func() *ActionContext {
		return &ActionContext{
			Record:  models.SObject{constants.FieldID: "source-1"},
			User:    &models.UserSession{ID: "005-user"},
			Results: map[string]map[string]interface{}{},
		}
	}

	t.Run("record mutation validation", func(t *testing.T) {
		service := newService(&actionCompositePersistenceStub{
			insertErr: apperrors.NewValidationError("Name", "is required"),
		})
		action := &models.ActionMetadata{
			ID:            "composite-create",
			ObjectAPIName: "SourceObject",
			Type:          constants.ActionTypeComposite,
			Config: map[string]interface{}{
				constants.ConfigKeySteps: []interface{}{map[string]interface{}{
					constants.FieldID:             "step-create",
					constants.FieldSysAction_Type: constants.ActionTypeCreateRecord,
					constants.ConfigTargetObject:  "TargetObject",
					constants.ConfigFieldMappings: map[string]interface{}{"Name": "created-from-composite"},
				}},
			},
		}

		err := service.executeComposite(context.Background(), action, newContext())
		if _, ok := flowClientValidationForPresentation(err); !ok {
			t.Fatalf("executeComposite() error = %T %v, want marked record validation", err, err)
		}
	})

	t.Run("step configuration validation", func(t *testing.T) {
		service := newService(&actionCompositePersistenceStub{})
		action := &models.ActionMetadata{
			ID:            "composite-create",
			ObjectAPIName: "SourceObject",
			Type:          constants.ActionTypeComposite,
			Config: map[string]interface{}{
				constants.ConfigKeySteps: []interface{}{map[string]interface{}{
					constants.FieldID:             "step-create",
					constants.FieldSysAction_Type: constants.ActionTypeCreateRecord,
					constants.ConfigTargetObject:  "TargetObject",
				}},
			},
		}

		err := service.executeComposite(context.Background(), action, newContext())
		if !apperrors.IsInternal(err) {
			t.Fatalf("executeComposite() error = %T %v, want InternalError", err, err)
		}
		if _, ok := flowClientValidationForPresentation(err); ok {
			t.Fatalf("executeComposite() surfaced configuration validation: %v", err)
		}
	})
}

func TestActionServiceExecuteCompositeUsesPessimisticTransactionForUpdateStep(t *testing.T) {
	persistence := &actionCompositePersistenceStub{}
	txm := &recordingActionTxManager{}
	service := &ActionService{persistence: persistence, txManager: txm}
	action := &models.ActionMetadata{
		ID:            "composite-update",
		ObjectAPIName: "SourceObject",
		Type:          constants.ActionTypeComposite,
		Config: map[string]interface{}{
			constants.ConfigKeySteps: []interface{}{
				map[string]interface{}{
					constants.FieldID:             "step-update",
					constants.FieldSysAction_Type: constants.ActionTypeUpdateRecord,
					constants.ConfigTargetObject:  "TargetObject",
					constants.ConfigRecordID:      "target-1",
					constants.ConfigFieldMappings: map[string]interface{}{"Status": "Updated"},
				},
			},
		},
	}
	ctx := &ActionContext{Record: models.SObject{"Id": "source-1"}, User: &models.UserSession{ID: "005-user"}, Results: map[string]map[string]interface{}{}}

	if err := service.executeComposite(context.Background(), action, ctx); err != nil {
		t.Fatalf("executeComposite(update): %v", err)
	}
	if txm.runPessTxCalls != 1 {
		t.Fatalf("WithPessimisticTransaction calls = %d, want 1", txm.runPessTxCalls)
	}
	if txm.runTxCalls != 0 {
		t.Fatalf("WithTransaction calls = %d, want 0", txm.runTxCalls)
	}
	if len(persistence.updateCalls) != 1 {
		t.Fatalf("Update calls = %d, want 1", len(persistence.updateCalls))
	}
	if got := persistence.updateCtx[0]; got != "pessimistic" {
		t.Fatalf("Update ctx marker = %q, want pessimistic", got)
	}
}

func TestActionServiceExecuteCompositeReusesExistingPessimisticTransaction(t *testing.T) {
	persistence := &actionCompositePersistenceStub{}
	txm := &recordingActionTxManager{}
	service := &ActionService{persistence: persistence, txManager: txm}
	action := &models.ActionMetadata{
		ID:            "composite-nested",
		ObjectAPIName: "SourceObject",
		Type:          constants.ActionTypeComposite,
		Config: map[string]interface{}{
			constants.ConfigKeySteps: []interface{}{
				map[string]interface{}{
					constants.FieldID:             "step-create",
					constants.FieldSysAction_Type: constants.ActionTypeCreateRecord,
					constants.ConfigTargetObject:  "TargetObject",
					constants.ConfigFieldMappings: map[string]interface{}{"Name": "nested"},
				},
			},
		},
	}
	ctx := &ActionContext{Record: models.SObject{"Id": "source-1"}, User: &models.UserSession{ID: "005-user"}, Results: map[string]map[string]interface{}{}}

	if err := txm.WithPessimisticTransaction(context.Background(), func(txCtx context.Context) error {
		return service.executeComposite(txCtx, action, ctx)
	}); err != nil {
		t.Fatalf("executeComposite(nested): %v", err)
	}
	if txm.runPessTxCalls != 1 {
		t.Fatalf("WithPessimisticTransaction calls = %d, want 1 outer call", txm.runPessTxCalls)
	}
	if txm.runTxCalls != 0 {
		t.Fatalf("WithTransaction calls = %d, want 0 when outer pessimistic tx already exists", txm.runTxCalls)
	}
	if len(persistence.insertCalls) != 1 {
		t.Fatalf("Insert calls = %d, want 1", len(persistence.insertCalls))
	}
	if got := persistence.insertCtx[0]; got != "pessimistic" {
		t.Fatalf("Insert ctx marker = %q, want pessimistic outer tx reuse", got)
	}
}

func TestActionServiceExecuteCompositeFailsClosedInsidePlainTransaction(t *testing.T) {
	persistence := &actionCompositePersistenceStub{}
	txm := &recordingActionTxManager{}
	service := &ActionService{persistence: persistence, txManager: txm}
	action := &models.ActionMetadata{
		ID:            "composite-plain-tx",
		ObjectAPIName: "SourceObject",
		Type:          constants.ActionTypeComposite,
		Config: map[string]interface{}{
			constants.ConfigKeySteps: []interface{}{
				map[string]interface{}{
					constants.FieldID:             "step-create",
					constants.FieldSysAction_Type: constants.ActionTypeCreateRecord,
					constants.ConfigTargetObject:  "TargetObject",
					constants.ConfigFieldMappings: map[string]interface{}{"Name": "nested"},
				},
			},
		},
	}
	ctx := &ActionContext{Record: models.SObject{"Id": "source-1"}, User: &models.UserSession{ID: "005-user"}, Results: map[string]map[string]interface{}{}}

	err := txm.WithTransaction(context.Background(), func(txCtx context.Context) error {
		return service.executeComposite(txCtx, action, ctx)
	})
	if err == nil {
		t.Fatal("executeComposite inside plain transaction = nil, want fail-closed error")
	}
	if err != errCompositeActionNeedsPessimisticBoundary {
		t.Fatalf("executeComposite plain transaction error = %v, want %v", err, errCompositeActionNeedsPessimisticBoundary)
	}
	if txm.runTxCalls != 1 {
		t.Fatalf("WithTransaction calls = %d, want 1 outer call", txm.runTxCalls)
	}
	if txm.runPessTxCalls != 0 {
		t.Fatalf("WithPessimisticTransaction calls = %d, want 0 on fail-closed plain tx path", txm.runPessTxCalls)
	}
	if len(persistence.insertCalls) != 0 {
		t.Fatalf("Insert calls = %d, want 0 when composite action fails closed before nested CRUD", len(persistence.insertCalls))
	}
}

func cloneActionCompositeSObject(in models.SObject) models.SObject {
	if in == nil {
		return nil
	}
	out := make(models.SObject, len(in))
	for k, v := range in {
		out[k] = v
	}
	return out
}
