package orchestrator

import (
	"context"
	"strings"

	"github.com/shopspring/decimal"
	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
	"go.uber.org/zap"
)

func (s *Service) submitCardPayout(ctx context.Context, payment *model.Payment) error {
	if payment == nil {
		return merrors.InvalidArgument("payment is required")
	}
	if payment.Execution != nil && strings.TrimSpace(payment.Execution.CardPayoutRef) != "" {
		return nil
	}
	intent := payment.Intent
	card := intent.Destination.Card
	if card == nil {
		return merrors.InvalidArgument("card payout: card endpoint is required")
	}
	amount, err := cardPayoutAmount(payment)
	if err != nil {
		return err
	}
	amtDec, err := decimalFromMoney(amount)
	if err != nil {
		return err
	}
	minor := amtDec.Mul(decimal.NewFromInt(100)).IntPart()

	payoutID := payment.PaymentRef
	currency := strings.TrimSpace(amount.GetCurrency())
	holder := strings.TrimSpace(card.Cardholder)
	meta := cloneMetadata(payment.Metadata)
	customer := intent.Customer
	customerID := ""
	customerFirstName := ""
	customerMiddleName := ""
	customerLastName := ""
	customerIP := ""
	customerZip := ""
	customerCountry := ""
	customerState := ""
	customerCity := ""
	customerAddress := ""
	if customer != nil {
		customerID = strings.TrimSpace(customer.ID)
		customerFirstName = strings.TrimSpace(customer.FirstName)
		customerMiddleName = strings.TrimSpace(customer.MiddleName)
		customerLastName = strings.TrimSpace(customer.LastName)
		customerIP = strings.TrimSpace(customer.IP)
		customerZip = strings.TrimSpace(customer.Zip)
		customerCountry = strings.TrimSpace(customer.Country)
		customerState = strings.TrimSpace(customer.State)
		customerCity = strings.TrimSpace(customer.City)
		customerAddress = strings.TrimSpace(customer.Address)
	}
	if customerFirstName == "" {
		customerFirstName = strings.TrimSpace(card.Cardholder)
	}
	if customerLastName == "" {
		customerLastName = strings.TrimSpace(card.CardholderSurname)
	}
	if customerID == "" {
		return merrors.InvalidArgument("card payout: customer id is required")
	}
	if customerFirstName == "" {
		return merrors.InvalidArgument("card payout: customer first name is required")
	}
	if customerLastName == "" {
		return merrors.InvalidArgument("card payout: customer last name is required")
	}
	if customerIP == "" {
		return merrors.InvalidArgument("card payout: customer ip is required")
	}

	var (
		state *mntxv1.CardPayoutState
	)

	if token := strings.TrimSpace(card.Token); token != "" {
		req := &mntxv1.CardTokenPayoutRequest{
			PayoutId:           payoutID,
			CustomerId:         customerID,
			CustomerFirstName:  customerFirstName,
			CustomerMiddleName: customerMiddleName,
			CustomerLastName:   customerLastName,
			CustomerIp:         customerIP,
			CustomerZip:        customerZip,
			CustomerCountry:    customerCountry,
			CustomerState:      customerState,
			CustomerCity:       customerCity,
			CustomerAddress:    customerAddress,
			AmountMinor:        minor,
			Currency:           currency,
			CardToken:          token,
			CardHolder:         holder,
			MaskedPan:          strings.TrimSpace(card.MaskedPan),
			Metadata:           meta,
		}
		resp, err := s.deps.mntx.client.CreateCardTokenPayout(ctx, req)
		if err != nil {
			s.logger.Warn("card token payout failed", zap.Error(err), zap.String("payment_ref", payment.PaymentRef))
			return err
		}
		state = resp.GetPayout()
	} else if pan := strings.TrimSpace(card.Pan); pan != "" {
		req := &mntxv1.CardPayoutRequest{
			PayoutId:           payoutID,
			CustomerId:         customerID,
			CustomerFirstName:  customerFirstName,
			CustomerMiddleName: customerMiddleName,
			CustomerLastName:   customerLastName,
			CustomerIp:         customerIP,
			CustomerZip:        customerZip,
			CustomerCountry:    customerCountry,
			CustomerState:      customerState,
			CustomerCity:       customerCity,
			CustomerAddress:    customerAddress,
			AmountMinor:        minor,
			Currency:           currency,
			CardPan:            pan,
			CardExpYear:        card.ExpYear,
			CardExpMonth:       card.ExpMonth,
			CardHolder:         holder,
			Metadata:           meta,
		}
		resp, err := s.deps.mntx.client.CreateCardPayout(ctx, req)
		if err != nil {
			s.logger.Warn("card payout failed", zap.Error(err), zap.String("payment_ref", payment.PaymentRef))
			return err
		}
		state = resp.GetPayout()
	} else {
		return merrors.InvalidArgument("card payout: either token or pan must be provided")
	}

	if state == nil {
		return merrors.Internal("card payout: missing payout state")
	}
	recordCardPayoutState(payment, state)
	exec := payment.Execution
	if exec == nil {
		exec = &model.ExecutionRefs{}
	}
	if exec.CardPayoutRef == "" {
		exec.CardPayoutRef = strings.TrimSpace(state.GetPayoutId())
	}
	payment.Execution = exec

	plan := ensureExecutionPlan(payment)
	if plan != nil {
		step := ensureExecutionStep(plan, stepCodeCardPayout)
		setExecutionStepRole(step, executionStepRoleConsumer)
		step.Description = "Submit card payout"
		step.Amount = cloneMoney(amount)
		if masked := strings.TrimSpace(card.MaskedPan); masked != "" {
			step.DestinationRef = masked
		}
		if exec.CardPayoutRef != "" {
			step.TransferRef = exec.CardPayoutRef
		}
		setExecutionStepStatus(step, executionStepStatusSubmitted)
		updateExecutionPlanTotalNetworkFee(plan)
	}

	s.logger.Info("card payout submitted", zap.String("payment_ref", payment.PaymentRef), zap.String("payout_id", exec.CardPayoutRef))

	return nil
}

func recordCardPayoutState(payment *model.Payment, state *mntxv1.CardPayoutState) {
	if payment == nil || state == nil {
		return
	}
	if payment.CardPayout == nil {
		payment.CardPayout = &model.CardPayout{}
	}
	payment.CardPayout.PayoutRef = strings.TrimSpace(state.GetPayoutId())
	payment.CardPayout.ProviderPaymentID = strings.TrimSpace(state.GetProviderPaymentId())
	payment.CardPayout.Status = state.GetStatus().String()
	payment.CardPayout.FailureReason = strings.TrimSpace(state.GetProviderMessage())
	payment.CardPayout.ProviderCode = strings.TrimSpace(state.GetProviderCode())
	if payment.CardPayout.CardCountry == "" && payment.Intent.Destination.Card != nil {
		payment.CardPayout.CardCountry = strings.TrimSpace(payment.Intent.Destination.Card.Country)
	}
	if payment.CardPayout.MaskedPan == "" && payment.Intent.Destination.Card != nil {
		payment.CardPayout.MaskedPan = strings.TrimSpace(payment.Intent.Destination.Card.MaskedPan)
	}
	payment.CardPayout.GatewayReference = strings.TrimSpace(state.GetPayoutId())
}

func applyCardPayoutUpdate(payment *model.Payment, payout *mntxv1.CardPayoutState) {
	if payment == nil || payout == nil {
		return
	}
	recordCardPayoutState(payment, payout)

	if payment.Execution == nil {
		payment.Execution = &model.ExecutionRefs{}
	}
	if payment.Execution.CardPayoutRef == "" {
		payment.Execution.CardPayoutRef = strings.TrimSpace(payout.GetPayoutId())
	}

	plan := ensureExecutionPlan(payment)
	if plan != nil {
		step := findExecutionStepByTransferRef(plan, strings.TrimSpace(payout.GetPayoutId()))
		if step == nil {
			step = ensureExecutionStep(plan, stepCodeCardPayout)
			setExecutionStepRole(step, executionStepRoleConsumer)
			if step.TransferRef == "" {
				step.TransferRef = payment.Execution.CardPayoutRef
			}
		}
		switch payout.GetStatus() {
		case mntxv1.PayoutStatus_PAYOUT_STATUS_PROCESSED:
			setExecutionStepStatus(step, executionStepStatusConfirmed)
		case mntxv1.PayoutStatus_PAYOUT_STATUS_FAILED:
			setExecutionStepStatus(step, executionStepStatusFailed)
		case mntxv1.PayoutStatus_PAYOUT_STATUS_PENDING:
			setExecutionStepStatus(step, executionStepStatusSubmitted)
		default:
			setExecutionStepStatus(step, executionStepStatusPlanned)
		}
	}

	payment.State = mapMntxStatusToState(payout.GetStatus())
	switch payout.GetStatus() {
	case mntxv1.PayoutStatus_PAYOUT_STATUS_PROCESSED:
		payment.FailureCode = model.PaymentFailureCodeUnspecified
		payment.FailureReason = ""
	case mntxv1.PayoutStatus_PAYOUT_STATUS_FAILED:
		payment.FailureCode = model.PaymentFailureCodePolicy
		payment.FailureReason = strings.TrimSpace(payout.GetProviderMessage())
	default:
		// leave as-is for pending/unspecified
	}
}

func cardPayoutAmount(payment *model.Payment) (*paymenttypes.Money, error) {
	if payment == nil {
		return nil, merrors.InvalidArgument("payment is required")
	}
	amount := cloneMoney(payment.Intent.Amount)
	if payment.LastQuote != nil {
		settlement := payment.LastQuote.ExpectedSettlementAmount
		if settlement != nil && strings.TrimSpace(settlement.GetAmount()) != "" && strings.TrimSpace(settlement.GetCurrency()) != "" {
			amount = cloneMoney(settlement)
		}
	}
	if amount == nil || strings.TrimSpace(amount.GetAmount()) == "" || strings.TrimSpace(amount.GetCurrency()) == "" {
		return nil, merrors.InvalidArgument("card payout: amount is required")
	}
	return amount, nil
}
