package psvc

import (
	"fmt"
	"strings"

	"github.com/shopspring/decimal"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/batchmeta"
	svcshared "github.com/tech/sendico/payments/orchestrator/internal/service/shared"
	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/discovery"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/xplan"
)

func plannedMoneyForStep(
	step xplan.Step,
	intent model.PaymentIntent,
	quote *model.PaymentQuoteSnapshot,
) (*paymenttypes.Money, *paymenttypes.Money) {
	action := model.ParseRailOperation(string(step.Action))

	switch step.Rail {
	case discovery.RailLedger:
		return plannedLedgerMoney(step, intent, quote, action), nil
	case discovery.RailCrypto:
		switch action {
		case discovery.RailOperationSend:
			return plannedSourceMoney(intent, quote), nil
		case discovery.RailOperationFee:
			return plannedFeeMoney(intent, quote), nil
		}
	case discovery.RailProviderSettlement:
		if action == discovery.RailOperationFXConvert {
			return plannedSourceMoney(intent, quote), plannedSettlementMoney(intent, quote)
		}
	case discovery.RailCardPayout:
		if action == discovery.RailOperationSend {
			return plannedCardPayoutMoney(step, intent, quote), nil
		}
	}

	if override, ok := batchmeta.AmountFromMetadata(step.Metadata); ok {
		return svcshared.CloneMoneyTrimNonEmpty(override), nil
	}
	return nil, nil
}

func plannedLedgerMoney(
	step xplan.Step,
	intent model.PaymentIntent,
	quote *model.PaymentQuoteSnapshot,
	action model.RailOperation,
) *paymenttypes.Money {
	if override, ok := batchmeta.AmountFromMetadata(step.Metadata); ok {
		return svcshared.CloneMoneyTrimNonEmpty(override)
	}

	sourceMoney := plannedSourceMoney(intent, quote)
	settlementMoney := plannedSettlementMoney(intent, quote)
	payoutMoney := settlementMoney

	if fromRail, toRail, ok := plannedLedgerBoundaryRails(step.StepCode, quote); ok {
		switch {
		case isLedgerExternalRail(fromRail) && isLedgerExternalRail(toRail):
			return sourceMoney
		case isLedgerExternalRail(fromRail) && isLedgerInternalRail(toRail):
			return settlementMoney
		case isLedgerInternalRail(fromRail) && isLedgerExternalRail(toRail):
			if toRail == discovery.RailCardPayout {
				return payoutMoney
			}
			return settlementMoney
		case isLedgerInternalRail(fromRail) && isLedgerInternalRail(toRail):
			return settlementMoney
		}
	}

	switch action {
	case discovery.RailOperationCredit, discovery.RailOperationExternalCredit:
		return settlementMoney
	case discovery.RailOperationDebit, discovery.RailOperationExternalDebit:
		if sourceMoney != nil {
			return sourceMoney
		}
		return settlementMoney
	case discovery.RailOperationMove, discovery.RailOperationBlock, discovery.RailOperationRelease:
		if settlementMoney != nil {
			return settlementMoney
		}
		return sourceMoney
	default:
		return nil
	}
}

func plannedSourceMoney(intent model.PaymentIntent, quote *model.PaymentQuoteSnapshot) *paymenttypes.Money {
	if quote != nil && quote.DebitAmount != nil {
		return svcshared.CloneMoneyTrimNonEmpty(quote.DebitAmount)
	}
	return svcshared.CloneMoneyTrimNonEmpty(intent.Amount)
}

func plannedSettlementMoney(intent model.PaymentIntent, quote *model.PaymentQuoteSnapshot) *paymenttypes.Money {
	if quote != nil && quote.ExpectedSettlementAmount != nil {
		return svcshared.CloneMoneyTrimNonEmpty(quote.ExpectedSettlementAmount)
	}
	return plannedSourceMoney(intent, quote)
}

func plannedCardPayoutMoney(step xplan.Step, intent model.PaymentIntent, quote *model.PaymentQuoteSnapshot) *paymenttypes.Money {
	if override, ok := batchmeta.AmountFromMetadata(step.Metadata); ok {
		return svcshared.CloneMoneyTrimNonEmpty(override)
	}
	if quote != nil && quote.ExpectedSettlementAmount != nil {
		return svcshared.CloneMoneyTrimNonEmpty(quote.ExpectedSettlementAmount)
	}
	return svcshared.CloneMoneyTrimNonEmpty(intent.Amount)
}

func plannedFeeMoney(intent model.PaymentIntent, quote *model.PaymentQuoteSnapshot) *paymenttypes.Money {
	if quote == nil || len(quote.FeeLines) == 0 {
		return nil
	}

	sourceCurrency := ""
	if source := plannedSourceMoney(intent, quote); source != nil {
		sourceCurrency = strings.TrimSpace(source.Currency)
	}

	total := decimal.Zero
	currency := ""
	for i := range quote.FeeLines {
		line := quote.FeeLines[i]
		if line == nil || line.GetMoney() == nil {
			continue
		}

		lineCurrency := strings.TrimSpace(line.GetMoney().GetCurrency())
		if lineCurrency == "" {
			continue
		}
		if sourceCurrency != "" && !strings.EqualFold(sourceCurrency, lineCurrency) {
			continue
		}
		if currency == "" {
			currency = strings.ToUpper(lineCurrency)
		} else if !strings.EqualFold(currency, lineCurrency) {
			return nil
		}

		amountRaw := strings.TrimSpace(line.GetMoney().GetAmount())
		amount, err := decimal.NewFromString(amountRaw)
		if err != nil {
			continue
		}
		amount = amount.Abs()
		if amount.IsZero() {
			continue
		}
		if line.GetSide() == paymenttypes.EntrySideCredit {
			total = total.Sub(amount)
		} else {
			total = total.Add(amount)
		}
	}

	if total.Sign() <= 0 || strings.TrimSpace(currency) == "" {
		return nil
	}
	return &paymenttypes.Money{
		Amount:   total.String(),
		Currency: strings.ToUpper(strings.TrimSpace(currency)),
	}
}

func plannedLedgerBoundaryRails(stepCode string, quote *model.PaymentQuoteSnapshot) (model.Rail, model.Rail, bool) {
	fromIndex, toIndex, ok := parseLedgerEdgeStepCode(stepCode)
	if !ok || quote == nil || quote.Route == nil {
		return discovery.RailUnspecified, discovery.RailUnspecified, false
	}

	var fromRail model.Rail = discovery.RailUnspecified
	var toRail model.Rail = discovery.RailUnspecified
	for i := range quote.Route.Hops {
		hop := quote.Route.Hops[i]
		if hop == nil {
			continue
		}
		if hop.Index == fromIndex {
			fromRail = model.ParseRail(hop.Rail)
		}
		if hop.Index == toIndex {
			toRail = model.ParseRail(hop.Rail)
		}
	}
	if fromRail == discovery.RailUnspecified || toRail == discovery.RailUnspecified {
		return discovery.RailUnspecified, discovery.RailUnspecified, false
	}
	return fromRail, toRail, true
}

func parseLedgerEdgeStepCode(stepCode string) (uint32, uint32, bool) {
	code := strings.ToLower(strings.TrimSpace(stepCode))
	if !strings.HasPrefix(code, "edge.") || !strings.Contains(code, ".ledger.") {
		return 0, 0, false
	}
	var (
		from uint32
		to   uint32
		op   string
	)
	if _, err := fmt.Sscanf(code, "edge.%d_%d.ledger.%s", &from, &to, &op); err != nil {
		return 0, 0, false
	}
	if strings.TrimSpace(op) == "" {
		return 0, 0, false
	}
	return from, to, true
}

func isLedgerInternalRail(rail model.Rail) bool {
	return rail == discovery.RailLedger
}

func isLedgerExternalRail(rail model.Rail) bool {
	switch rail {
	case discovery.RailCrypto, discovery.RailProviderSettlement, discovery.RailCardPayout, discovery.RailFiatOnRamp:
		return true
	default:
		return false
	}
}
