package orchestrator

import (
	"context"
	"fmt"
	"github.com/tech/sendico/pkg/discovery"
	"strings"

	"github.com/shopspring/decimal"
	"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"
	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	"go.uber.org/zap"
)

type gatewayGuardExecutor struct {
	logger                mlogger.Logger
	gatewayInvokeResolver GatewayInvokeResolver
	gatewayRegistry       GatewayRegistry
}

func (e *gatewayGuardExecutor) ExecuteGuard(ctx context.Context, req sexec.StepRequest) (*sexec.ExecuteOutput, error) {
	conditions := quoteExecutionConditionsForGuard(req.Payment)
	switch xplan.GuardStepKind(req.Step) {
	case xplan.StepKindLiquidityCheck:
		base := executeLiquidityGuardReadiness(req.StepExecution, conditions)
		if base.StepExecution.State != agg.StepStateCompleted {
			return base, nil
		}
		failCode, failMsg := e.probeLiquidity(ctx, req)
		if failCode != "" {
			if e.logger != nil {
				e.logger.Warn("Liquidity preflight probe failed",
					zap.String("payment_ref", paymentRef(req.Payment)),
					zap.String("step_ref", strings.TrimSpace(req.Step.StepRef)),
					zap.String("failure_code", failCode),
					zap.String("failure_message", failMsg),
				)
			}
			return failedOutputForGuard(req.StepExecution, failCode, failMsg), nil
		}
		return completedOutputForGuard(req.StepExecution), nil
	case xplan.StepKindPrefunding:
		return executePrefundingGuardReadiness(req.StepExecution, conditions), nil
	default:
		return failedOutputForGuard(
			req.StepExecution,
			"guard.unsupported_step",
			"unsupported guard step: step_code="+strings.TrimSpace(req.Step.StepCode),
		), nil
	}
}

func (e *gatewayGuardExecutor) probeLiquidity(ctx context.Context, req sexec.StepRequest) (string, string) {
	payment := req.Payment
	if payment == nil {
		return "guard.payment_missing", "liquidity probe requires payment context"
	}

	hopGateway, hopInstanceID, hasCryptoSource := sourceCryptoHop(payment)
	if !hasCryptoSource {
		if e.logger != nil {
			e.logger.Debug("Liquidity preflight probe skipped for non-crypto source",
				zap.String("payment_ref", strings.TrimSpace(payment.PaymentRef)),
				zap.String("step_ref", strings.TrimSpace(req.Step.StepRef)),
			)
		}
		return "", ""
	}

	if e.gatewayInvokeResolver == nil || e.gatewayRegistry == nil {
		return "guard.liquidity_probe_unavailable", "liquidity probe dependencies are not configured"
	}

	walletRef, err := sourceManagedWalletRef(payment)
	if err != nil {
		return "guard.liquidity_probe_unsupported_source", err.Error()
	}

	requiredAmount, requiredCurrency, err := requiredLiquidity(payment)
	if err != nil {
		return "guard.liquidity_probe_error", err.Error()
	}

	resolver := gatewayCryptoExecutor{
		gatewayRegistry: e.gatewayRegistry,
	}
	gateway, err := resolver.resolveGateway(ctx, xplan.Step{
		Gateway:    hopGateway,
		InstanceID: hopInstanceID,
		Rail:       discovery.RailCrypto,
	})
	if err != nil {
		return "guard.liquidity_probe_error", err.Error()
	}

	client, err := e.gatewayInvokeResolver.Resolve(ctx, strings.TrimSpace(gateway.InvokeURI))
	if err != nil {
		return "guard.liquidity_probe_error", err.Error()
	}

	balanceResp, err := client.GetWalletBalance(ctx, &chainv1.GetWalletBalanceRequest{WalletRef: walletRef})
	if err != nil {
		return "guard.liquidity_probe_error", err.Error()
	}
	if balanceResp == nil || balanceResp.GetBalance() == nil || balanceResp.GetBalance().GetAvailable() == nil {
		return "guard.liquidity_probe_error", "wallet balance is missing from gateway response"
	}

	available := balanceResp.GetBalance().GetAvailable()
	availableCurrency := strings.ToUpper(strings.TrimSpace(available.GetCurrency()))
	availableAmount := strings.TrimSpace(available.GetAmount())
	if availableCurrency == "" || availableAmount == "" {
		return "guard.liquidity_probe_error", "wallet available balance is incomplete"
	}
	if !strings.EqualFold(availableCurrency, requiredCurrency) {
		return "guard.liquidity_probe_currency_mismatch", fmt.Sprintf(
			"wallet balance currency mismatch: available=%s required=%s",
			availableCurrency,
			requiredCurrency,
		)
	}

	availableDec, err := decimal.NewFromString(availableAmount)
	if err != nil {
		return "guard.liquidity_probe_error", "wallet available amount is invalid"
	}
	requiredDec, err := decimal.NewFromString(requiredAmount)
	if err != nil {
		return "guard.liquidity_probe_error", "required liquidity amount is invalid"
	}
	if availableDec.Cmp(requiredDec) < 0 {
		return "guard.liquidity_insufficient", fmt.Sprintf(
			"insufficient liquidity: available=%s required=%s currency=%s wallet_ref=%s",
			availableAmount,
			requiredAmount,
			requiredCurrency,
			walletRef,
		)
	}

	if e.logger != nil {
		e.logger.Info("Liquidity preflight probe passed",
			zap.String("payment_ref", strings.TrimSpace(payment.PaymentRef)),
			zap.String("step_ref", strings.TrimSpace(req.Step.StepRef)),
			zap.String("wallet_ref", walletRef),
			zap.String("currency", requiredCurrency),
			zap.String("required_amount", requiredAmount),
			zap.String("available_amount", availableAmount),
			zap.String("gateway_id", strings.TrimSpace(gateway.ID)),
			zap.String("gateway_instance_id", strings.TrimSpace(gateway.InstanceID)),
		)
	}
	return "", ""
}

func sourceCryptoHop(payment *agg.Payment) (gateway string, instanceID string, ok bool) {
	if payment == nil || payment.QuoteSnapshot == nil || payment.QuoteSnapshot.Route == nil {
		return "", "", false
	}
	hops := payment.QuoteSnapshot.Route.Hops
	fallbackGateway := ""
	fallbackInstance := ""
	for i := range hops {
		hop := hops[i]
		if hop == nil || model.ParseRail(hop.Rail) != discovery.RailCrypto {
			continue
		}
		gw := strings.TrimSpace(hop.Gateway)
		inst := strings.TrimSpace(hop.InstanceID)
		if gw == "" && inst == "" {
			continue
		}
		if fallbackGateway == "" && fallbackInstance == "" {
			fallbackGateway = gw
			fallbackInstance = inst
		}
		if hop.Role == paymenttypes.QuoteRouteHopRoleSource {
			return gw, inst, true
		}
	}
	if fallbackGateway != "" || fallbackInstance != "" {
		return fallbackGateway, fallbackInstance, true
	}
	return "", "", false
}

func requiredLiquidity(payment *agg.Payment) (amount string, currency string, err error) {
	if payment == nil {
		return "", "", merrors.InvalidArgument("payment is required")
	}
	base := effectiveSourceAmount(payment)
	if base == nil {
		return "", "", merrors.InvalidArgument("source amount is required")
	}

	amount = strings.TrimSpace(base.Amount)
	currency = strings.ToUpper(strings.TrimSpace(base.Currency))
	if amount == "" || currency == "" {
		return "", "", merrors.InvalidArgument("source amount is invalid")
	}

	// If total cost is same-currency and greater, probe against total liquidity required.
	if payment.QuoteSnapshot != nil && payment.QuoteSnapshot.TotalCost != nil {
		total := payment.QuoteSnapshot.TotalCost
		totalAmount := strings.TrimSpace(total.Amount)
		totalCurrency := strings.ToUpper(strings.TrimSpace(total.Currency))
		if totalAmount != "" && strings.EqualFold(totalCurrency, currency) {
			totalDec, totalErr := decimal.NewFromString(totalAmount)
			baseDec, baseErr := decimal.NewFromString(amount)
			if totalErr == nil && baseErr == nil && totalDec.Cmp(baseDec) > 0 {
				amount = totalAmount
			}
		}
	}

	return amount, currency, nil
}

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

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

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

func executePrefundingGuardReadiness(
	step agg.StepExecution,
	conditions *paymenttypes.QuoteExecutionConditions,
) *sexec.ExecuteOutput {
	if conditions == nil {
		return failedOutputForGuard(step, "guard.conditions_missing", "prefunding guard requires execution conditions")
	}
	if conditions.Readiness == paymenttypes.QuoteExecutionReadinessIndicative {
		return failedOutputForGuard(step, "guard.indicative_quote", "prefunding guard cannot execute indicative quotes")
	}
	return completedOutputForGuard(step)
}

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

func failedOutputForGuard(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 paymentRef(payment *agg.Payment) string {
	if payment == nil {
		return ""
	}
	return strings.TrimSpace(payment.PaymentRef)
}

var _ sexec.GuardExecutor = (*gatewayGuardExecutor)(nil)
