package gateway_funding_profile

import (
	"strings"

	"github.com/tech/sendico/payments/quotation/internal/shared"
	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
)

func BuildFundingGateFromProfile(
	profile *GatewayFundingProfile,
	requiredAmount *moneyv1.Money,
) (*QuoteFundingGate, error) {
	if profile == nil {
		return nil, nil //nolint:nilnil // nil gate means no funding profile is configured
	}

	mode := shared.NormalizeFundingMode(profile.Mode)
	gate := &QuoteFundingGate{
		Mode:           mode,
		RequiredAmount: cloneProtoMoney(requiredAmount),
		Funding:        clonePaymentEndpoint(profile.FundingDestination),
		Fee:            clonePaymentEndpoint(profile.FeeDestination),
		Reserve:        cloneReservePolicy(profile.Reserve),
		DepositCheck:   cloneDepositCheck(profile.DepositCheck),
	}

	switch mode {
	case model.FundingModeNone:
		return gate, nil

	case model.FundingModeBalanceReserve:
		if gate.Reserve == nil {
			return nil, merrors.InvalidArgument("funding profile: reserve policy is required for balance_reserve")
		}
		hasBlock := strings.TrimSpace(gate.Reserve.BlockAccountRef) != ""
		hasRoles := gate.Reserve.FromRole != nil && gate.Reserve.ToRole != nil
		if !hasBlock && !hasRoles {
			return nil, merrors.InvalidArgument("funding profile: block account or reserve roles are required for balance_reserve")
		}
		if gate.RequiredAmount == nil {
			return nil, merrors.InvalidArgument("funding profile: required amount is required for balance_reserve")
		}
		return gate, nil

	case model.FundingModeDepositObserved:
		if gate.DepositCheck == nil {
			return nil, merrors.InvalidArgument("funding profile: deposit check policy is required for deposit_observed")
		}
		if strings.TrimSpace(gate.DepositCheck.WalletRef) == "" {
			return nil, merrors.InvalidArgument("funding profile: deposit wallet_ref is required for deposit_observed")
		}
		if gate.DepositCheck.ExpectedAmount == nil {
			gate.DepositCheck.ExpectedAmount = cloneProtoMoney(requiredAmount)
		}
		if gate.DepositCheck.ExpectedAmount == nil {
			return nil, merrors.InvalidArgument("funding profile: deposit expected_amount is required for deposit_observed")
		}
		return gate, nil

	default:
		return nil, merrors.InvalidArgument("funding profile: mode is required")
	}
}

func cloneProtoMoney(src *moneyv1.Money) *moneyv1.Money {
	return shared.CloneProtoMoneyTrim(src)
}

func clonePaymentEndpoint(src *model.PaymentEndpoint) *model.PaymentEndpoint {
	if src == nil {
		return nil
	}
	result := &model.PaymentEndpoint{
		Type:       src.Type,
		InstanceID: strings.TrimSpace(src.InstanceID),
	}
	if src.Ledger != nil {
		result.Ledger = &model.LedgerEndpoint{
			LedgerAccountRef:       strings.TrimSpace(src.Ledger.LedgerAccountRef),
			ContraLedgerAccountRef: strings.TrimSpace(src.Ledger.ContraLedgerAccountRef),
		}
	}
	if src.ManagedWallet != nil {
		result.ManagedWallet = &model.ManagedWalletEndpoint{
			ManagedWalletRef: strings.TrimSpace(src.ManagedWallet.ManagedWalletRef),
			Asset:            cloneAsset(src.ManagedWallet.Asset),
		}
	}
	if src.ExternalChain != nil {
		result.ExternalChain = &model.ExternalChainEndpoint{
			Asset:   cloneAsset(src.ExternalChain.Asset),
			Address: strings.TrimSpace(src.ExternalChain.Address),
			Memo:    strings.TrimSpace(src.ExternalChain.Memo),
		}
	}
	if src.Card != nil {
		result.Card = &model.CardEndpoint{
			Pan:               strings.TrimSpace(src.Card.Pan),
			Token:             strings.TrimSpace(src.Card.Token),
			Cardholder:        strings.TrimSpace(src.Card.Cardholder),
			CardholderSurname: strings.TrimSpace(src.Card.CardholderSurname),
			ExpMonth:          src.Card.ExpMonth,
			ExpYear:           src.Card.ExpYear,
			Country:           strings.TrimSpace(src.Card.Country),
			MaskedPan:         strings.TrimSpace(src.Card.MaskedPan),
		}
	}
	return result
}

func cloneAsset(src *paymenttypes.Asset) *paymenttypes.Asset {
	if src == nil {
		return nil
	}
	return &paymenttypes.Asset{
		Chain:           strings.TrimSpace(src.Chain),
		TokenSymbol:     strings.TrimSpace(src.TokenSymbol),
		ContractAddress: strings.TrimSpace(src.ContractAddress),
	}
}

func cloneStringMap(src map[string]string) map[string]string {
	if len(src) == 0 {
		return nil
	}
	out := make(map[string]string, len(src))
	for k, v := range src {
		kk := strings.TrimSpace(k)
		if kk == "" {
			continue
		}
		out[kk] = strings.TrimSpace(v)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func cloneReservePolicy(src *ReservePolicy) *ReservePolicy {
	if src == nil {
		return nil
	}
	result := &ReservePolicy{
		DebitAccountRef: strings.TrimSpace(src.DebitAccountRef),
		BlockAccountRef: strings.TrimSpace(src.BlockAccountRef),
		ReleaseOnFail:   src.ReleaseOnFail,
		ReleaseOnCancel: src.ReleaseOnCancel,
	}
	if src.FromRole != nil {
		role := *src.FromRole
		result.FromRole = &role
	}
	if src.ToRole != nil {
		role := *src.ToRole
		result.ToRole = &role
	}
	return result
}

func cloneDepositCheck(src *model.DepositCheckPolicy) *model.DepositCheckPolicy {
	if src == nil {
		return nil
	}
	return &model.DepositCheckPolicy{
		WalletRef:        strings.TrimSpace(src.WalletRef),
		ExpectedAmount:   cloneProtoMoney(src.ExpectedAmount),
		MinConfirmations: src.MinConfirmations,
		TimeoutSeconds:   src.TimeoutSeconds,
	}
}
