package automation

import (
	"context"
	"fmt"
	"testing"
	"time"

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

type flowInterviewTxKey struct{}

type flowInterviewUpdateCall struct {
	tableName string
	id        string
	updates   models.SObject
}

type flowInterviewPersistenceStub struct {
	runTxCalls     int
	runPessTxCalls int
	lockRow        models.SObject
	inserts        []models.SObject
	updates        []flowInterviewUpdateCall
}

func (p *flowInterviewPersistenceStub) Insert(ctx context.Context, tableName string, record models.SObject, user *models.UserSession) (models.SObject, error) {
	cloned := cloneSObject(record)
	p.inserts = append(p.inserts, cloned)
	return cloned, nil
}

func (p *flowInterviewPersistenceStub) PreviewBeforeInsert(ctx context.Context, tableName string, record models.SObject, user *models.UserSession) (models.SObject, error) {
	panic("flowInterviewPersistenceStub.PreviewBeforeInsert: not implemented")
}

func (p *flowInterviewPersistenceStub) PrelockUpdateParents(ctx context.Context, tableName string, id string, updates models.SObject, user *models.UserSession) error {
	p.updates = append(p.updates, flowInterviewUpdateCall{
		tableName: tableName,
		id:        id,
		updates:   cloneSObject(updates),
	})
	return nil
}

func (p *flowInterviewPersistenceStub) Update(ctx context.Context, tableName string, id string, updates models.SObject, user *models.UserSession) error {
	p.updates = append(p.updates, flowInterviewUpdateCall{
		tableName: tableName,
		id:        id,
		updates:   cloneSObject(updates),
	})
	return nil
}

func (p *flowInterviewPersistenceStub) PreviewBeforeUpdate(ctx context.Context, tableName string, id string, updates models.SObject, internalStamps models.SObject, user *models.UserSession) (*models.PreparedRecordUpdate, error) {
	panic("flowInterviewPersistenceStub.PreviewBeforeUpdate: not implemented")
}

func (p *flowInterviewPersistenceStub) CommitPreparedUpdate(ctx context.Context, tableName string, id string, prepared *models.PreparedRecordUpdate, extraUpdates models.SObject, user *models.UserSession) error {
	panic("flowInterviewPersistenceStub.CommitPreparedUpdate: not implemented")
}

func (p *flowInterviewPersistenceStub) Delete(ctx context.Context, tableName string, id string, user *models.UserSession) error {
	panic("flowInterviewPersistenceStub.Delete: not implemented")
}

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

func (p *flowInterviewPersistenceStub) GetLock(ctx context.Context, tableName string, id string) (models.SObject, error) {
	if p.lockRow == nil {
		return nil, nil
	}
	return cloneSObject(p.lockRow), nil
}

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

func (p *flowInterviewPersistenceStub) RunInTransaction(ctx context.Context, fn func(context.Context) error) error {
	if _, ok := ctx.Value(flowInterviewTxKey{}).(struct{}); ok {
		return fn(ctx)
	}
	p.runTxCalls++
	return fn(context.WithValue(ctx, flowInterviewTxKey{}, struct{}{}))
}

func (p *flowInterviewPersistenceStub) IsTransactionActive(ctx context.Context) bool {
	_, ok := ctx.Value(flowInterviewTxKey{}).(struct{})
	return ok
}

func (p *flowInterviewPersistenceStub) RunInPessimisticTransaction(ctx context.Context, fn func(context.Context) error) error {
	if _, ok := ctx.Value(flowInterviewTxKey{}).(struct{}); ok {
		return fmt.Errorf("pessimistic transaction cannot start inside active plain tx")
	}
	p.runPessTxCalls++
	return fn(context.WithValue(ctx, flowInterviewTxKey{}, struct{}{}))
}

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

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

func TestFlowInterviewServiceClaimPausedInterviewUsesPessimisticTransaction(t *testing.T) {
	persistence := &flowInterviewPersistenceStub{
		lockRow: models.SObject{
			constants.FieldID: "fi-claim",
			constants.FieldFlowInterview_InterviewStatus: models.FlowInterviewStatusPaused,
		},
	}
	service := NewFlowInterviewService(persistence, nil)

	claimed, err := service.ClaimPausedInterview(context.Background(), "fi-claim", &models.UserSession{ID: "005-user"})
	if err != nil {
		t.Fatalf("ClaimPausedInterview: %v", err)
	}
	if !claimed {
		t.Fatal("ClaimPausedInterview returned claimed=false, want true")
	}
	if persistence.runPessTxCalls != 1 {
		t.Fatalf("RunInPessimisticTransaction calls = %d, want 1", persistence.runPessTxCalls)
	}
	if persistence.runTxCalls != 0 {
		t.Fatalf("RunInTransaction calls = %d, want 0", persistence.runTxCalls)
	}
	if len(persistence.updates) != 1 {
		t.Fatalf("Update calls = %d, want 1", len(persistence.updates))
	}
	update := persistence.updates[0]
	if update.tableName != constants.TableFlowInterview || update.id != "fi-claim" {
		t.Fatalf("Update target = %s/%s, want %s/fi-claim", update.tableName, update.id, constants.TableFlowInterview)
	}
	if got := update.updates[constants.FieldFlowInterview_InterviewStatus]; got != models.FlowInterviewStatusRunning {
		t.Fatalf("InterviewStatus update = %v, want %s", got, models.FlowInterviewStatusRunning)
	}
}

func TestPauseForWaitUsesTransactionForTransientInterview(t *testing.T) {
	persistence := &flowInterviewPersistenceStub{}
	exec := &FlowExecutor{persistence: persistence}
	interview := &models.FlowInterview{
		FlowVersionId:  "fv-1",
		InterviewLabel: "Pause Me",
		OwnerId:        "005-owner",
		ContextData:    map[string]interface{}{"foo": "bar"},
		IsTransient:    true,
	}
	user := &models.UserSession{ID: "005-user"}
	pausedUntil := time.Now().UTC().Add(5 * time.Minute)

	if err := exec.pauseForWait(context.Background(), interview, "WaitNode", pausedUntil, user); err != nil {
		t.Fatalf("pauseForWait: %v", err)
	}
	if persistence.runTxCalls != 1 {
		t.Fatalf("RunInTransaction calls = %d, want 1", persistence.runTxCalls)
	}
	if persistence.runPessTxCalls != 0 {
		t.Fatalf("RunInPessimisticTransaction calls = %d, want 0", persistence.runPessTxCalls)
	}
	if len(persistence.inserts) != 1 {
		t.Fatalf("Insert calls = %d, want 1", len(persistence.inserts))
	}
	if interview.IsTransient {
		t.Fatal("interview.IsTransient = true, want false after pauseForWait")
	}
	if interview.InterviewStatus != models.FlowInterviewStatusPaused {
		t.Fatalf("InterviewStatus = %s, want %s", interview.InterviewStatus, models.FlowInterviewStatusPaused)
	}
	if interview.CurrentElement != "WaitNode" {
		t.Fatalf("CurrentElement = %s, want WaitNode", interview.CurrentElement)
	}
	if interview.PausedUntil == nil {
		t.Fatal("PausedUntil = nil, want non-nil")
	}
}

func TestPauseForWaitUsesTransactionForPersistedInterview(t *testing.T) {
	persistence := &flowInterviewPersistenceStub{}
	exec := &FlowExecutor{persistence: persistence}
	interview := &models.FlowInterview{
		Id:             "fi-existing",
		FlowVersionId:  "fv-1",
		InterviewLabel: "Pause Existing",
		ContextData:    map[string]interface{}{"foo": "bar"},
		IsTransient:    false,
	}
	user := &models.UserSession{ID: "005-user"}
	pausedUntil := time.Now().UTC().Add(10 * time.Minute)

	if err := exec.pauseForWait(context.Background(), interview, "WaitNode", pausedUntil, user); err != nil {
		t.Fatalf("pauseForWait: %v", err)
	}
	if persistence.runTxCalls != 1 {
		t.Fatalf("RunInTransaction calls = %d, want 1", persistence.runTxCalls)
	}
	if persistence.runPessTxCalls != 0 {
		t.Fatalf("RunInPessimisticTransaction calls = %d, want 0", persistence.runPessTxCalls)
	}
	if len(persistence.inserts) != 0 {
		t.Fatalf("Insert calls = %d, want 0", len(persistence.inserts))
	}
	if len(persistence.updates) != 1 {
		t.Fatalf("Update calls = %d, want 1", len(persistence.updates))
	}
	update := persistence.updates[0]
	if update.tableName != constants.TableFlowInterview || update.id != "fi-existing" {
		t.Fatalf("Update target = %s/%s, want %s/fi-existing", update.tableName, update.id, constants.TableFlowInterview)
	}
	if got := update.updates[constants.FieldFlowInterview_InterviewStatus]; got != models.FlowInterviewStatusPaused {
		t.Fatalf("InterviewStatus update = %v, want %s", got, models.FlowInterviewStatusPaused)
	}
}

func TestPauseForWaitReusesExistingTransactionForRecordTriggeredWait(t *testing.T) {
	persistence := &flowInterviewPersistenceStub{}
	exec := &FlowExecutor{persistence: persistence}
	interview := &models.FlowInterview{
		FlowVersionId:  "fv-1",
		InterviewLabel: "Pause In Save Tx",
		OwnerId:        "005-owner",
		ContextData:    map[string]interface{}{"foo": "bar"},
		IsTransient:    true,
	}
	user := &models.UserSession{ID: "005-user"}
	pausedUntil := time.Now().UTC().Add(15 * time.Minute)

	err := persistence.RunInTransaction(context.Background(), func(txCtx context.Context) error {
		return exec.pauseForWait(txCtx, interview, "WaitNode", pausedUntil, user)
	})
	if err != nil {
		t.Fatalf("pauseForWait inside active transaction: %v", err)
	}
	if persistence.runTxCalls != 1 {
		t.Fatalf("RunInTransaction calls = %d, want 1 outer call with inner reuse", persistence.runTxCalls)
	}
	if persistence.runPessTxCalls != 0 {
		t.Fatalf("RunInPessimisticTransaction calls = %d, want 0 when active tx already exists", persistence.runPessTxCalls)
	}
	if len(persistence.inserts) != 1 {
		t.Fatalf("Insert calls = %d, want 1", len(persistence.inserts))
	}
	if interview.IsTransient {
		t.Fatal("interview.IsTransient = true, want false after pauseForWait")
	}
}

func cloneSObject(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
}

func (p *flowInterviewPersistenceStub) String() string {
	return fmt.Sprintf("runTx=%d runPessTx=%d inserts=%d updates=%d", p.runTxCalls, p.runPessTxCalls, len(p.inserts), len(p.updates))
}
