package orchestrator

import (
	"context"
	"strings"

	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
)

func (p *paymentExecutor) executePlanStep(ctx context.Context, payment *model.Payment, step *model.PaymentStep, execStep *model.ExecutionStep, quote *orchestratorv1.PaymentQuote, charges []*ledgerv1.PostingLine, idx int) (bool, error) {
	if payment == nil || step == nil || execStep == nil {
		return false, merrors.InvalidArgument("payment plan: step is required")
	}

	switch step.Action {
	case model.RailOperationDebit:
		amount, err := requireMoney(cloneMoney(step.Amount), "ledger debit amount")
		if err != nil {
			return false, err
		}
		ref, err := p.postLedgerDebit(ctx, payment, protoMoney(amount), charges, planStepIdempotencyKey(payment, idx, step), idx, quote)
		if err != nil {
			return false, err
		}
		ensureExecutionRefs(payment).DebitEntryRef = ref
		setExecutionStepStatus(execStep, executionStepStatusConfirmed)
		return false, nil
	case model.RailOperationCredit:
		amount, err := requireMoney(cloneMoney(step.Amount), "ledger credit amount")
		if err != nil {
			return false, err
		}
		ref, err := p.postLedgerCredit(ctx, payment, protoMoney(amount), planStepIdempotencyKey(payment, idx, step), idx, quote)
		if err != nil {
			return false, err
		}
		ensureExecutionRefs(payment).CreditEntryRef = ref
		setExecutionStepStatus(execStep, executionStepStatusConfirmed)
		return false, nil
	case model.RailOperationBlock:
		if step.Rail != model.RailLedger {
			return false, merrors.InvalidArgument("payment plan: block requires ledger rail")
		}
		amount, err := requireMoney(cloneMoney(step.Amount), "ledger block amount")
		if err != nil {
			return false, err
		}
		ref, err := p.postLedgerBlock(ctx, payment, protoMoney(amount), planStepIdempotencyKey(payment, idx, step), idx)
		if err != nil {
			return false, err
		}
		execStep.TransferRef = strings.TrimSpace(ref)
		setExecutionStepStatus(execStep, executionStepStatusConfirmed)
		return false, nil
	case model.RailOperationRelease:
		if step.Rail != model.RailLedger {
			return false, merrors.InvalidArgument("payment plan: release requires ledger rail")
		}
		amount, err := requireMoney(cloneMoney(step.Amount), "ledger release amount")
		if err != nil {
			return false, err
		}
		ref, err := p.postLedgerRelease(ctx, payment, protoMoney(amount), planStepIdempotencyKey(payment, idx, step), idx)
		if err != nil {
			return false, err
		}
		execStep.TransferRef = strings.TrimSpace(ref)
		setExecutionStepStatus(execStep, executionStepStatusConfirmed)
		return false, nil
	case model.RailOperationFXConvert:
		if err := p.applyFX(ctx, payment, quote, charges, paymentDescription(payment), cloneMetadata(payment.Metadata), ensureExecutionRefs(payment)); err != nil {
			return false, err
		}
		setExecutionStepStatus(execStep, executionStepStatusConfirmed)
		return false, nil
	case model.RailOperationObserveConfirm:
		setExecutionStepStatus(execStep, executionStepStatusSubmitted)
		return true, nil
	case model.RailOperationSend:
		return p.executeSendStep(ctx, payment, step, execStep, quote, idx)
	case model.RailOperationFee:
		return p.executeFeeStep(ctx, payment, step, execStep, idx)
	default:
		return false, merrors.InvalidArgument("payment plan: unsupported action")
	}
}

func (p *paymentExecutor) executeSendStep(ctx context.Context, payment *model.Payment, step *model.PaymentStep, execStep *model.ExecutionStep, quote *orchestratorv1.PaymentQuote, idx int) (bool, error) {
	switch step.Rail {
	case model.RailCrypto:
		amount, err := requireMoney(cloneMoney(step.Amount), "crypto send amount")
		if err != nil {
			return false, err
		}
		if !p.deps.railGateways.available() {
			return false, merrors.Internal("rail gateway unavailable")
		}
		req, err := p.buildCryptoTransferRequest(payment, amount, model.RailOperationSend, planStepIdempotencyKey(payment, idx, step), quote)
		if err != nil {
			return false, err
		}
		gw, err := p.deps.railGateways.resolve(ctx, step)
		if err != nil {
			return false, err
		}
		result, err := gw.Send(ctx, req)
		if err != nil {
			return false, err
		}
		execStep.TransferRef = strings.TrimSpace(result.ReferenceID)
		exec := ensureExecutionRefs(payment)
		if exec.ChainTransferRef == "" && execStep.TransferRef != "" {
			exec.ChainTransferRef = execStep.TransferRef
		}
		setExecutionStepStatus(execStep, executionStepStatusSubmitted)
		return true, nil
	case model.RailCardPayout:
		amount, err := requireMoney(cloneMoney(step.Amount), "card payout amount")
		if err != nil {
			return false, err
		}
		ref, err := p.submitCardPayoutPlan(ctx, payment, protoMoney(amount))
		if err != nil {
			return false, err
		}
		execStep.TransferRef = ref
		ensureExecutionRefs(payment).CardPayoutRef = ref
		setExecutionStepStatus(execStep, executionStepStatusSubmitted)
		return true, nil
	case model.RailProviderSettlement:
		amount, err := requireMoney(cloneMoney(step.Amount), "provider settlement amount")
		if err != nil {
			return false, err
		}
		if !p.deps.railGateways.available() {
			return false, merrors.Internal("rail gateway unavailable")
		}
		req, err := p.buildProviderSettlementTransferRequest(payment, step, amount, quote, idx)
		if err != nil {
			return false, err
		}
		gw, err := p.deps.railGateways.resolve(ctx, step)
		if err != nil {
			return false, err
		}
		result, err := gw.Send(ctx, req)
		if err != nil {
			return false, err
		}
		execStep.TransferRef = strings.TrimSpace(result.ReferenceID)
		if execStep.TransferRef == "" {
			execStep.TransferRef = strings.TrimSpace(req.IdempotencyKey)
		}
		linkProviderSettlementObservation(payment, execStep.TransferRef)
		setExecutionStepStatus(execStep, executionStepStatusSubmitted)
		return true, nil
	case model.RailFiatOnRamp:
		return false, merrors.InvalidArgument("payment plan: fiat on-ramp execution not implemented")
	default:
		return false, merrors.InvalidArgument("payment plan: unsupported send rail")
	}
}

func (p *paymentExecutor) executeFeeStep(ctx context.Context, payment *model.Payment, step *model.PaymentStep, execStep *model.ExecutionStep, idx int) (bool, error) {
	switch step.Rail {
	case model.RailCrypto:
		amount, err := requireMoney(cloneMoney(step.Amount), "crypto fee amount")
		if err != nil {
			return false, err
		}
		if !p.deps.railGateways.available() {
			return false, merrors.Internal("rail gateway unavailable")
		}
		req, err := p.buildCryptoTransferRequest(payment, amount, model.RailOperationFee, planStepIdempotencyKey(payment, idx, step), nil)
		if err != nil {
			return false, err
		}
		gw, err := p.deps.railGateways.resolve(ctx, step)
		if err != nil {
			return false, err
		}
		result, err := gw.Send(ctx, req)
		if err != nil {
			return false, err
		}
		execStep.TransferRef = strings.TrimSpace(result.ReferenceID)
		if execStep.TransferRef != "" {
			ensureExecutionRefs(payment).FeeTransferRef = execStep.TransferRef
		}
		setExecutionStepStatus(execStep, executionStepStatusSubmitted)
		return true, nil
	default:
		return false, merrors.InvalidArgument("payment plan: unsupported fee rail")
	}
}
