package orchestrator

import (
	"context"
	"fmt"
	"math/big"
	"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"
	"go.uber.org/zap"
)

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")
	}

	stepID := execStep.Code
	logger := p.logger.With(
		zap.String("payment_ref", payment.PaymentRef),
		zap.String("step_id", stepID),
		zap.String("rail", string(step.Rail)),
		zap.String("action", string(step.Action)),
		zap.Int("idx", idx),
	)

	logger.Debug("Executing payment plan step")

	if isStepFinal(execStep) {
		logger.Debug("Step already in final state, skipping execution",
			zap.String("state", string(execStep.State)),
		)
		return false, nil
	}

	switch step.Action {

	case model.RailOperationMove:
		logger.Debug("Posting ledger move")
		amount, err := requireMoney(cloneMoney(step.Amount), "ledger move amount")
		if err != nil {
			logger.Warn("Ledger move amount invalid", zap.Error(err))
			return false, err
		}
		ref, err := p.postLedgerMove(ctx, payment, step, protoMoney(amount), planStepIdempotencyKey(payment, idx, step), idx)
		if err != nil {
			logger.Warn("Ledger move failed", zap.Error(err))
			return false, err
		}
		execStep.TransferRef = strings.TrimSpace(ref)
		setExecutionStepStatus(execStep, model.OperationStateSuccess)
		logger.Info("Ledger move completed", zap.String("journal_ref", ref))
		return false, nil

	case model.RailOperationDebit, model.RailOperationExternalDebit:
		logger.Debug("Posting ledger debit")
		amount, err := requireMoney(cloneMoney(step.Amount), "ledger debit amount")
		if err != nil {
			logger.Warn("Ledger debit amount invalid", zap.Error(err))
			return false, err
		}
		ref, err := p.postLedgerDebit(ctx, payment, protoMoney(amount), charges, planStepIdempotencyKey(payment, idx, step), idx, step.Action, quote)
		if err != nil {
			logger.Warn("Ledger debit failed", zap.Error(err))
			return false, err
		}
		ensureExecutionRefs(payment).DebitEntryRef = ref
		setExecutionStepStatus(execStep, model.OperationStateSuccess)
		logger.Info("Ledger debit completed", zap.String("journal_ref", ref))
		return false, nil

	case model.RailOperationCredit, model.RailOperationExternalCredit:
		logger.Debug("Posting ledger credit")
		amount, err := requireMoney(cloneMoney(step.Amount), "ledger credit amount")
		if err != nil {
			logger.Warn("Ledger credit amount invalid", zap.Error(err))
			return false, err
		}
		ref, err := p.postLedgerCredit(ctx, payment, protoMoney(amount), planStepIdempotencyKey(payment, idx, step), idx, step.Action, quote)
		if err != nil {
			logger.Warn("Ledger credit failed", zap.Error(err))
			return false, err
		}
		ensureExecutionRefs(payment).CreditEntryRef = ref
		setExecutionStepStatus(execStep, model.OperationStateSuccess)
		logger.Info("Ledger credit completed", zap.String("journal_ref", ref))
		return false, nil

	case model.RailOperationFXConvert:
		logger.Debug("Applying FX conversion")
		if err := p.applyFX(ctx, payment, quote, charges, paymentDescription(payment), cloneMetadata(payment.Metadata), ensureExecutionRefs(payment)); err != nil {
			logger.Warn("FX conversion failed", zap.Error(err))
			return false, err
		}
		setExecutionStepStatus(execStep, model.OperationStateSuccess)
		logger.Info("FX conversion completed")
		return false, nil

	case model.RailOperationObserveConfirm:
		setExecutionStepStatus(execStep, model.OperationStateWaiting)
		logger.Info("ObserveConfirm step set to waiting for external confirmation")
		return true, nil

	case model.RailOperationSend:
		logger.Debug("Executing send step")
		async, err := p.executeSendStep(ctx, payment, step, execStep, quote, idx)
		if err != nil {
			setExecutionStepStatus(execStep, model.OperationStateFailed)
			execStep.Error = err.Error()
			logger.Warn("Send step failed", zap.Error(err))
			return false, err
		}

		return async, nil

	case model.RailOperationFee:
		logger.Debug("Executing fee step")
		async, err := p.executeFeeStep(ctx, payment, step, execStep, idx)
		if err != nil {
			logger.Warn("Fee step failed", zap.Error(err))
			return false, err
		}
		logger.Info("Fee step submitted")
		return async, nil

	default:
		logger.Warn("Unsupported payment plan action")
		return false, merrors.InvalidArgument("payment plan: unsupported action")
	}
}

func sub(a, b string) (string, error) {
	ra, ok := new(big.Rat).SetString(a)
	if !ok {
		return "", fmt.Errorf("invalid number: %s", a)
	}

	rb, ok := new(big.Rat).SetString(b)
	if !ok {
		return "", fmt.Errorf("invalid number: %s", b)
	}

	ra.Sub(ra, rb)

	// 2 знака после запятой (как у тебя)
	return ra.FloatString(2), nil
}

func (p *paymentExecutor) executeSendStep(
	ctx context.Context,
	payment *model.Payment,
	step *model.PaymentStep,
	execStep *model.ExecutionStep,
	quote *orchestratorv1.PaymentQuote,
	idx int,
) (bool, error) {

	stepID := execStep.Code
	logger := p.logger.With(
		zap.String("payment_ref", payment.PaymentRef),
		zap.String("step_id", stepID),
		zap.String("rail", string(step.Rail)),
		zap.String("action", string(step.Action)),
		zap.Int("idx", idx),
	)

	logger.Debug("Executing send step")

	switch step.Rail {

	case model.RailCrypto:
		logger.Debug("Preparing crypto transfer")

		amount, err := requireMoney(cloneMoney(step.Amount), "crypto send amount")
		if err != nil {
			logger.Warn("Invalid crypto amount", zap.Error(err))
			return false, err
		}

		if !p.deps.railGateways.available() {
			logger.Warn("Rail gateway unavailable")
			return false, merrors.Internal("rail gateway unavailable")
		}

		fromRole, toRole := roleHintsForStep(payment.PaymentPlan, idx)
		req, err := p.buildCryptoTransferRequest(
			payment,
			amount,
			model.RailOperationSend,
			planStepIdempotencyKey(payment, idx, step),
			execStep.OperationRef,
			quote,
			fromRole, toRole,
		)
		if err != nil {
			logger.Warn("Failed to build crypto transfer request", zap.Error(err))
			return false, err
		}

		gw, err := p.deps.railGateways.resolve(ctx, step)
		if err != nil {
			logger.Warn("Failed to resolve rail gateway", zap.Error(err))
			return false, err
		}

		logger.Debug("Sending crypto transfer",
			zap.String("idempotency", req.IdempotencyKey), zap.String("intent_ref", req.IntentRef),
			zap.String("operation_ref", req.OperationRef),
		)

		result, err := gw.Send(ctx, req)
		if err != nil {
			execStep.Error = strings.TrimSpace(err.Error())
			setExecutionStepStatus(execStep, model.OperationStateFailed)

			payment.State = model.PaymentStateFailed
			payment.FailureCode = model.PaymentFailureCodeChain

			logger.Warn("Send failed; step marked as failed", zap.Error(err))
			return false, nil
		}

		execStep.TransferRef = strings.TrimSpace(result.ReferenceID)
		logger.Info("Crypto transfer submitted",
			zap.String("transfer_ref", execStep.TransferRef),
		)

		exec := ensureExecutionRefs(payment)
		if exec.ChainTransferRef == "" && execStep.TransferRef != "" {
			exec.ChainTransferRef = execStep.TransferRef
		}

		if execStep.TransferRef != "" {
			linkRailObservation(payment, step.Rail, execStep.TransferRef, stepID)
		}

		setExecutionStepStatus(execStep, model.OperationStateWaiting)
		return true, nil

	case model.RailCardPayout:
		logger.Debug("Submitting card payout")

		amount, err := requireMoney(cloneMoney(step.Amount), "card payout amount")
		if err != nil {
			logger.Warn("Invalid card payout amount", zap.Error(err))
			return false, err
		}

		fromRole, toRole := roleHintsForStep(payment.PaymentPlan, idx)
		ref, err := p.submitCardPayoutPlan(
			ctx,
			payment,
			execStep.OperationRef,
			protoMoney(amount),
			fromRole, toRole,
		)
		if err != nil {
			logger.Warn("Card payout submission failed", zap.Error(err))
			return false, err
		}

		execStep.TransferRef = ref
		ensureExecutionRefs(payment).CardPayoutRef = ref

		logger.Info("Card payout submitted", zap.String("payout_ref", ref))

		setExecutionStepStatus(execStep, model.OperationStateWaiting)
		return true, nil

	case model.RailProviderSettlement:
		logger.Debug("Preparing provider settlement transfer")

		amount, err := requireMoney(cloneMoney(payment.Intent.Amount), "provider settlement amount")
		if err != nil {
			logger.Warn("Invalid provider settlement amount", zap.Error(err))
			return false, err
		}
		fee, err := requireMoney(cloneMoney(payment.LastQuote.ExpectedFeeTotal), "provider settlement amount")
		if err != nil {
			logger.Warn("Invalid provider settlement amount", zap.Error(err))
			return false, err
		}
		if fee.Currency != amount.Currency {
			logger.Warn("Fee and amount currencies do not match")
			return false, merrors.DataConflict("settlement payment: currencies mismatch")
		}

		if !p.deps.railGateways.available() {
			logger.Warn("Rail gateway unavailable")
			return false, merrors.Internal("rail gateway unavailable")
		}

		fromRole, toRole := roleHintsForStep(payment.PaymentPlan, idx)
		req, err := p.buildProviderSettlementTransferRequest(
			payment,
			step,
			execStep.OperationRef,
			amount,
			quote,
			idx,
			fromRole, toRole)
		if err != nil {
			logger.Warn("Failed to build provider settlement request", zap.Error(err))
			return false, err
		}

		gw, err := p.deps.railGateways.resolve(ctx, step)
		if err != nil {
			logger.Warn("Failed to resolve rail gateway", zap.Error(err))
			return false, err
		}

		logger.Info("Sending provider settlement transfer",
			zap.String("idempotency", req.IdempotencyKey), zap.String("intent_ref", req.IntentRef),
		)

		result, err := gw.Send(ctx, req)
		if err != nil {
			execStep.Error = strings.TrimSpace(err.Error())
			setExecutionStepStatus(execStep, model.OperationStateFailed)

			payment.State = model.PaymentStateFailed
			payment.FailureCode = model.PaymentFailureCodeSettlement

			logger.Warn("Send failed; step marked as failed", zap.Error(err))
			return false, nil
		}

		execStep.TransferRef = strings.TrimSpace(result.ReferenceID)
		if execStep.TransferRef == "" {
			execStep.TransferRef = strings.TrimSpace(req.IdempotencyKey)
		}

		logger.Info("Provider settlement submitted",
			zap.String("transfer_ref", execStep.TransferRef),
		)

		linkProviderSettlementObservation(payment, execStep.TransferRef)
		setExecutionStepStatus(execStep, model.OperationStateWaiting)
		return true, nil

	case model.RailFiatOnRamp:
		logger.Warn("Fiat on-ramp not implemented")
		return false, merrors.InvalidArgument("payment plan: fiat on-ramp execution not implemented")

	default:
		logger.Warn("Unsupported send rail")
		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) {

	if payment == nil || step == nil || execStep == nil {
		return false, merrors.InvalidArgument("payment plan: fee step is required")
	}

	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")
		}

		fromRole, toRole := roleHintsForStep(payment.PaymentPlan, idx)

		req, err := p.buildCryptoTransferRequest(
			payment,
			amount,
			model.RailOperationFee,
			planStepIdempotencyKey(payment, idx, step),
			execStep.OperationRef,
			nil,
			fromRole,
			toRole,
		)
		if err != nil {
			return false, err
		}

		gw, err := p.deps.railGateways.resolve(ctx, step)
		if err != nil {
			return false, err
		}

		p.logger.Debug("Executing crypto fee transfer",
			zap.String("payment_ref", payment.PaymentRef),
			zap.String("step_id", planStepID(step, idx)),
			zap.String("amount", amount.GetAmount()),
			zap.String("currency", amount.GetCurrency()),
		)

		result, err := gw.Send(ctx, req)
		if err != nil {
			p.logger.Warn("Crypto fee transfer failed to submit", zap.Error(err),
				zap.String("payment_ref", payment.PaymentRef),
			)
			return false, nil
		}

		execStep.TransferRef = strings.TrimSpace(result.ReferenceID)

		if execStep.TransferRef != "" {
			ensureExecutionRefs(payment).FeeTransferRef = execStep.TransferRef
		}

		// ВАЖНО: больше не Submitted
		setExecutionStepStatus(execStep, model.OperationStateWaiting)

		p.logger.Info("Crypto fee transfer submitted, waiting confirmation",
			zap.String("payment_ref", payment.PaymentRef),
			zap.String("transfer_ref", execStep.TransferRef),
		)

		return true, nil

	default:
		return false, merrors.InvalidArgument("payment plan: unsupported fee rail")
	}
}
