package orchestrator

import (
	"context"
	"strings"

	"github.com/shopspring/decimal"
	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model/account_role"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
)

func (p *paymentExecutor) submitCardPayoutPlan(ctx context.Context, payment *model.Payment, operationRef string, amount *moneyv1.Money, fromRole, toRole *account_role.AccountRole) (string, error) {
	if payment == nil {
		return "", merrors.InvalidArgument("payment is required")
	}
	if !p.deps.mntx.available() {
		return "", merrors.Internal("card_gateway_unavailable")
	}
	intent := payment.Intent
	card := intent.Destination.Card
	if card == nil {
		return "", merrors.InvalidArgument("card payout: card endpoint is required")
	}
	if amount == nil || strings.TrimSpace(amount.GetAmount()) == "" || strings.TrimSpace(amount.GetCurrency()) == "" {
		return "", merrors.InvalidArgument("card payout: amount is required")
	}

	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)
	if strings.TrimSpace(string(mergeAccountRole(fromRole))) != "" {
		if meta == nil {
			meta = map[string]string{}
		}
		meta[account_role.MetadataKeyFromRole] = strings.TrimSpace(string(mergeAccountRole(fromRole)))
	}
	if strings.TrimSpace(string(mergeAccountRole(toRole))) != "" {
		if meta == nil {
			meta = map[string]string{}
		}
		meta[account_role.MetadataKeyToRole] = strings.TrimSpace(string(mergeAccountRole(toRole)))
	}
	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,
			OperationRef:       operationRef,
			IntentRef:          payment.Intent.Ref,
			IdempotencyKey:     payment.IdempotencyKey,
		}
		resp, err := p.deps.mntx.client.CreateCardTokenPayout(ctx, req)
		if err != nil {
			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,
			OperationRef:       operationRef,
			IntentRef:          payment.Intent.Ref,
			IdempotencyKey:     payment.IdempotencyKey,
		}
		resp, err := p.deps.mntx.client.CreateCardPayout(ctx, req)
		if err != nil {
			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 := ensureExecutionRefs(payment)
	if exec.CardPayoutRef == "" {
		exec.CardPayoutRef = strings.TrimSpace(state.GetPayoutId())
	}
	return exec.CardPayoutRef, nil
}

func mergeAccountRole(role *account_role.AccountRole) account_role.AccountRole {
	if role == nil {
		return ""
	}
	return account_role.AccountRole(strings.TrimSpace(string(*role)))
}

func (p *paymentExecutor) resolveCardRoute(intent model.PaymentIntent) (CardGatewayRoute, error) {
	if p.svc != nil {
		return p.svc.cardRoute(p.gatewayKeyFromIntent(intent))
	}
	key := p.gatewayKeyFromIntent(intent)
	route, ok := p.deps.cardRoutes[key]
	if !ok {
		return CardGatewayRoute{}, merrors.InvalidArgument("card routing missing for gateway " + key)
	}
	if strings.TrimSpace(route.FundingAddress) == "" {
		return CardGatewayRoute{}, merrors.InvalidArgument("card funding address is required for gateway " + key)
	}
	return route, nil
}

func (p *paymentExecutor) gatewayKeyFromIntent(intent model.PaymentIntent) string {
	key := strings.TrimSpace(intent.Attributes["gateway"])
	if key == "" && intent.Destination.Card != nil {
		key = defaultCardGateway
	}
	return strings.ToLower(key)
}
