package psvc

import (
	"context"
	"strings"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/sexec"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/xplan"
	svcshared "github.com/tech/sendico/payments/orchestrator/internal/service/shared"
	"github.com/tech/sendico/pkg/mlogger"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
)

type defaultLedgerExecutor struct{}
type defaultCryptoExecutor struct{}
type defaultProviderSettlementExecutor struct{}
type defaultCardPayoutExecutor struct{}
type defaultObserveConfirmExecutor struct{}
type defaultGuardExecutor struct{}

// NewDefaultExecutors constructs the baseline executor registry and applies any
// provided overrides.
func NewDefaultExecutors(logger mlogger.Logger, overrides sexec.Dependencies) sexec.Registry {
	deps := sexec.Dependencies{
		Logger:             logger,
		Ledger:             defaultLedgerExecutor{},
		Crypto:             defaultCryptoExecutor{},
		ProviderSettlement: defaultProviderSettlementExecutor{},
		CardPayout:         defaultCardPayoutExecutor{},
		ObserveConfirm:     defaultObserveConfirmExecutor{},
		Guard:              defaultGuardExecutor{},
	}
	if overrides.Logger != nil {
		deps.Logger = overrides.Logger
	}
	if overrides.Ledger != nil {
		deps.Ledger = overrides.Ledger
	}
	if overrides.Crypto != nil {
		deps.Crypto = overrides.Crypto
	}
	if overrides.ProviderSettlement != nil {
		deps.ProviderSettlement = overrides.ProviderSettlement
	}
	if overrides.CardPayout != nil {
		deps.CardPayout = overrides.CardPayout
	}
	if overrides.ObserveConfirm != nil {
		deps.ObserveConfirm = overrides.ObserveConfirm
	}
	if overrides.Guard != nil {
		deps.Guard = overrides.Guard
	}
	return sexec.New(deps)
}

func (defaultLedgerExecutor) ExecuteLedger(_ context.Context, req sexec.StepRequest) (*sexec.ExecuteOutput, error) {
	step := req.StepExecution
	step.State = agg.StepStateCompleted
	step.FailureCode = ""
	step.FailureMsg = ""
	return &sexec.ExecuteOutput{StepExecution: step}, nil
}

func (defaultCryptoExecutor) ExecuteCrypto(_ context.Context, req sexec.StepRequest) (*sexec.ExecuteOutput, error) {
	return asyncOutput(req.StepExecution, "operation_ref", "crypto:"+req.Step.StepRef), nil
}

func (defaultProviderSettlementExecutor) ExecuteProviderSettlement(_ context.Context, req sexec.StepRequest) (*sexec.ExecuteOutput, error) {
	return asyncOutput(req.StepExecution, "operation_ref", "provider:"+req.Step.StepRef), nil
}

func (defaultCardPayoutExecutor) ExecuteCardPayout(_ context.Context, req sexec.StepRequest) (*sexec.ExecuteOutput, error) {
	return asyncOutput(req.StepExecution, "card_payout_ref", "card:"+req.Step.StepRef), nil
}

func (defaultObserveConfirmExecutor) ExecuteObserveConfirm(_ context.Context, req sexec.StepRequest) (*sexec.ExecuteOutput, error) {
	refs := inheritedExternalRefs(req.Payment, req.Step, req.StepExecution)
	if len(refs) == 0 {
		refs = append(refs, agg.ExternalRef{
			Kind: "operation_ref",
			Ref:  "observe:" + req.Step.StepRef,
		})
	}
	step := req.StepExecution
	step.State = agg.StepStateRunning
	step.ExternalRefs = refs
	step.ExecutedMoney = inheritedExecutedMoney(req.Payment, req.Step, req.StepExecution)
	step.ConvertedMoney = inheritedConvertedMoney(req.Payment, req.Step, req.StepExecution)
	step.FailureCode = ""
	step.FailureMsg = ""
	return &sexec.ExecuteOutput{
		StepExecution: step,
		Async:         true,
	}, nil
}

func (defaultGuardExecutor) ExecuteGuard(_ context.Context, req sexec.StepRequest) (*sexec.ExecuteOutput, error) {
	conditions := quoteExecutionConditions(req.Payment)
	switch guardKind(req.Step) {
	case xplan.StepKindLiquidityCheck:
		return executeLiquidityGuard(req.StepExecution, conditions), nil
	case xplan.StepKindPrefunding:
		return executePrefundingGuard(req.StepExecution, conditions), nil
	default:
		return failedOutput(req.StepExecution,
			"guard.unsupported_step",
			"unsupported guard step: step_code="+strings.TrimSpace(req.Step.StepCode),
		), nil
	}
}

func asyncOutput(step agg.StepExecution, kind, ref string) *sexec.ExecuteOutput {
	step.State = agg.StepStateRunning
	step.ExternalRefs = append(step.ExternalRefs, agg.ExternalRef{
		Kind: strings.TrimSpace(kind),
		Ref:  strings.TrimSpace(ref),
	})
	step.FailureCode = ""
	step.FailureMsg = ""
	return &sexec.ExecuteOutput{
		StepExecution: step,
		Async:         true,
	}
}

func completedOutput(step agg.StepExecution) *sexec.ExecuteOutput {
	step.State = agg.StepStateCompleted
	step.FailureCode = ""
	step.FailureMsg = ""
	return &sexec.ExecuteOutput{StepExecution: step}
}

func failedOutput(step agg.StepExecution, code, msg string) *sexec.ExecuteOutput {
	step.State = agg.StepStateFailed
	step.FailureCode = strings.TrimSpace(code)
	step.FailureMsg = strings.TrimSpace(msg)
	return &sexec.ExecuteOutput{StepExecution: step}
}

func executeLiquidityGuard(
	step agg.StepExecution,
	conditions *paymenttypes.QuoteExecutionConditions,
) *sexec.ExecuteOutput {
	if conditions == nil {
		return failedOutput(step, "guard.conditions_missing", "liquidity guard requires execution conditions")
	}

	switch conditions.Readiness {
	case paymenttypes.QuoteExecutionReadinessIndicative:
		return failedOutput(step, "guard.indicative_quote", "liquidity guard cannot execute indicative quotes")
	case paymenttypes.QuoteExecutionReadinessLiquidityObtainable:
		return failedOutput(step, "guard.liquidity_not_ready", "liquidity is not yet available at execution time")
	case paymenttypes.QuoteExecutionReadinessUnspecified:
		return failedOutput(step, "guard.readiness_unspecified", "liquidity guard requires explicit readiness")
	default:
		return completedOutput(step)
	}
}

func executePrefundingGuard(
	step agg.StepExecution,
	conditions *paymenttypes.QuoteExecutionConditions,
) *sexec.ExecuteOutput {
	if conditions == nil {
		return failedOutput(step, "guard.conditions_missing", "prefunding guard requires execution conditions")
	}
	if conditions.Readiness == paymenttypes.QuoteExecutionReadinessIndicative {
		return failedOutput(step, "guard.indicative_quote", "prefunding guard cannot execute indicative quotes")
	}
	// Prefunding confirmation is handled by upstream funding flows; this guard
	// currently validates quote executability semantics only.
	return completedOutput(step)
}

func quoteExecutionConditions(payment *agg.Payment) *paymenttypes.QuoteExecutionConditions {
	if payment == nil || payment.QuoteSnapshot == nil {
		return nil
	}
	return payment.QuoteSnapshot.ExecutionConditions
}

func guardKind(step xplan.Step) xplan.StepKind {
	return xplan.GuardStepKind(step)
}

func inheritedExternalRefs(payment *agg.Payment, step xplan.Step, current agg.StepExecution) []agg.ExternalRef {
	refs := appendExternalRefs(nil, current.ExternalRefs...)
	if payment == nil || len(step.DependsOn) == 0 {
		return refs
	}
	index := stepIndexByRef(payment.StepExecutions)
	for i := range step.DependsOn {
		idx, ok := index[strings.TrimSpace(step.DependsOn[i])]
		if !ok || idx < 0 || idx >= len(payment.StepExecutions) {
			continue
		}
		refs = appendExternalRefs(refs, payment.StepExecutions[idx].ExternalRefs...)
	}
	return refs
}

func inheritedExecutedMoney(payment *agg.Payment, step xplan.Step, current agg.StepExecution) *paymenttypes.Money {
	if money := svcshared.CloneMoneyTrimNonEmpty(current.ExecutedMoney); money != nil {
		return money
	}
	if payment == nil || len(step.DependsOn) == 0 {
		return nil
	}
	index := stepIndexByRef(payment.StepExecutions)
	for i := range step.DependsOn {
		idx, ok := index[strings.TrimSpace(step.DependsOn[i])]
		if !ok || idx < 0 || idx >= len(payment.StepExecutions) {
			continue
		}
		if money := svcshared.CloneMoneyTrimNonEmpty(payment.StepExecutions[idx].ExecutedMoney); money != nil {
			return money
		}
	}
	return nil
}

func inheritedConvertedMoney(payment *agg.Payment, step xplan.Step, current agg.StepExecution) *paymenttypes.Money {
	if money := svcshared.CloneMoneyTrimNonEmpty(current.ConvertedMoney); money != nil {
		return money
	}
	if payment == nil || len(step.DependsOn) == 0 {
		return nil
	}
	index := stepIndexByRef(payment.StepExecutions)
	for i := range step.DependsOn {
		idx, ok := index[strings.TrimSpace(step.DependsOn[i])]
		if !ok || idx < 0 || idx >= len(payment.StepExecutions) {
			continue
		}
		if money := svcshared.CloneMoneyTrimNonEmpty(payment.StepExecutions[idx].ConvertedMoney); money != nil {
			return money
		}
	}
	return nil
}

func appendExternalRefs(existing []agg.ExternalRef, additions ...agg.ExternalRef) []agg.ExternalRef {
	out := append([]agg.ExternalRef{}, existing...)
	seen := map[string]struct{}{}
	for i := range out {
		key := externalRefKey(out[i])
		if key == "" {
			continue
		}
		seen[key] = struct{}{}
	}
	for i := range additions {
		ref := agg.ExternalRef{
			GatewayInstanceID: strings.TrimSpace(additions[i].GatewayInstanceID),
			Kind:              strings.TrimSpace(additions[i].Kind),
			Ref:               strings.TrimSpace(additions[i].Ref),
		}
		key := externalRefKey(ref)
		if key == "" {
			continue
		}
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, ref)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func externalRefKey(ref agg.ExternalRef) string {
	if strings.TrimSpace(ref.Kind) == "" || strings.TrimSpace(ref.Ref) == "" {
		return ""
	}
	return strings.ToLower(strings.TrimSpace(ref.GatewayInstanceID)) + "|" +
		strings.ToLower(strings.TrimSpace(ref.Kind)) + "|" +
		strings.ToLower(strings.TrimSpace(ref.Ref))
}
