package wallet

import (
	"context"
	"encoding/json"
	"math"
	"strconv"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
)

// ExternalRechargeConfig 保存 H5 外部充值需要公开给业务链路的运行配置。
type ExternalRechargeConfig struct {
	USDTTRC20Enabled bool
	USDTTRC20Address string
	MifaPayNotifyURL string
	MifaPayReturnURL string
}

// MifaPayCreateOrderRequest 是 wallet-service 传给 MiFaPay client 的已校验下单快照。
type MifaPayCreateOrderRequest struct {
	OrderID             string
	TargetUserID        int64
	ProductName         string
	CoinAmount          int64
	AmountMinor         int64
	CurrencyCode        string
	CountryCode         string
	PayWay              string
	PayType             string
	NotifyURL           string
	ReturnURL           string
	ClientIP            string
	Language            string
	ProviderAmountMinor int64
}

type MifaPayCreateOrderResponse struct {
	OrderID         string
	ProviderOrderID string
	PayURL          string
	RawJSON         string
}

type MifaPayNotifyData struct {
	OrderID         string
	ProviderOrderID string
	OrderStatus     string
	Amount          string
	Currency        string
	PayType         string
	RawJSON         string
}

// MifaPayClient 隔离 MiFaPay RSA 签名、验签和 HTTP 协议细节，service 只处理订单状态。
type MifaPayClient interface {
	CreateOrder(ctx context.Context, req MifaPayCreateOrderRequest) (MifaPayCreateOrderResponse, error)
	ParseNotification(notification ledger.MifaPayNotification) (MifaPayNotifyData, error)
}

// TronUSDTClient 校验用户提交的 TRC20 tx_hash 是否真实打到平台共享地址。
type TronUSDTClient interface {
	VerifyUSDTTransfer(ctx context.Context, txHash string, toAddress string, amountMinor int64) (string, error)
}

func normalizeExternalRechargeConfig(config ExternalRechargeConfig) ExternalRechargeConfig {
	config.USDTTRC20Address = strings.TrimSpace(config.USDTTRC20Address)
	config.MifaPayNotifyURL = strings.TrimSpace(config.MifaPayNotifyURL)
	config.MifaPayReturnURL = strings.TrimSpace(config.MifaPayReturnURL)
	return config
}

// ListThirdPartyPaymentChannels 返回后台三方支付管理页需要的渠道、国家和方式树。
func (s *Service) ListThirdPartyPaymentChannels(ctx context.Context, query ledger.ListThirdPartyPaymentChannelsQuery) ([]ledger.ThirdPartyPaymentChannel, error) {
	if s.repository == nil {
		return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	query.AppCode = appcode.Normalize(query.AppCode)
	if strings.TrimSpace(query.Status) != "" {
		query.Status = ledger.NormalizeThirdPartyPaymentStatus(query.Status)
		if query.Status == "" {
			return nil, xerr.New(xerr.InvalidArgument, "status is invalid")
		}
	}
	ctx = appcode.WithContext(ctx, query.AppCode)
	return s.repository.ListThirdPartyPaymentChannels(ctx, query)
}

func (s *Service) SetThirdPartyPaymentMethodStatus(ctx context.Context, command ledger.ThirdPartyPaymentMethodStatusCommand) (ledger.ThirdPartyPaymentMethod, error) {
	if command.MethodID <= 0 || command.OperatorUserID <= 0 {
		return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.InvalidArgument, "payment method and operator are required")
	}
	if s.repository == nil {
		return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	command.AppCode = appcode.Normalize(command.AppCode)
	ctx = appcode.WithContext(ctx, command.AppCode)
	return s.repository.SetThirdPartyPaymentMethodStatus(ctx, command)
}

func (s *Service) UpdateThirdPartyPaymentRate(ctx context.Context, command ledger.ThirdPartyPaymentRateCommand) (ledger.ThirdPartyPaymentMethod, error) {
	command.AppCode = appcode.Normalize(command.AppCode)
	command.USDToCurrencyRate = strings.TrimSpace(command.USDToCurrencyRate)
	if command.MethodID <= 0 || command.OperatorUserID <= 0 || !validNonNegativeDecimal(command.USDToCurrencyRate) {
		return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.InvalidArgument, "payment rate command is invalid")
	}
	if s.repository == nil {
		return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	ctx = appcode.WithContext(ctx, command.AppCode)
	return s.repository.UpdateThirdPartyPaymentRate(ctx, command)
}

// ListH5RechargeOptions 只返回目标用户当前可买商品和已配置汇率的支付方式。
func (s *Service) ListH5RechargeOptions(ctx context.Context, query ledger.H5RechargeOptionsQuery) (ledger.H5RechargeOptions, error) {
	query.AppCode = appcode.Normalize(query.AppCode)
	query.AudienceType = ledger.NormalizeRechargeAudienceType(query.AudienceType)
	query.TargetCountryCode = strings.ToUpper(strings.TrimSpace(query.TargetCountryCode))
	if query.TargetUserID <= 0 || query.TargetRegionID <= 0 || query.AudienceType == "" {
		return ledger.H5RechargeOptions{}, xerr.New(xerr.InvalidArgument, "h5 recharge options query is incomplete")
	}
	if s.repository == nil {
		return ledger.H5RechargeOptions{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	ctx = appcode.WithContext(ctx, query.AppCode)
	options, err := s.repository.ListH5RechargeOptions(ctx, query)
	if err != nil {
		return ledger.H5RechargeOptions{}, err
	}
	if s.mifaPay == nil {
		options.PaymentMethods = nil
	}
	options.USDTTRC20Enabled = s.externalRecharge.USDTTRC20Enabled && s.externalRecharge.USDTTRC20Address != ""
	options.USDTTRC20Address = s.externalRecharge.USDTTRC20Address
	return options, nil
}

// CreateH5RechargeOrder 创建 H5 外部充值订单；只有回调或链上校验成功后才会入账。
func (s *Service) CreateH5RechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand) (ledger.ExternalRechargeOrder, error) {
	command = normalizeCreateExternalRechargeOrderCommand(command)
	if err := validateCreateExternalRechargeOrderCommand(command); err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	if s.repository == nil {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	ctx = appcode.WithContext(ctx, command.AppCode)
	product, err := s.repository.GetRechargeProduct(ctx, command.AppCode, command.ProductID)
	if err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	if err := validateExternalRechargeProduct(product, command); err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	switch command.ProviderCode {
	case ledger.PaymentProviderUSDTTRC20:
		return s.createUSDTRechargeOrder(ctx, command, product)
	case ledger.PaymentProviderMifaPay:
		return s.createMifaPayRechargeOrder(ctx, command, product)
	default:
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.InvalidArgument, "payment provider is invalid")
	}
}

func (s *Service) createUSDTRechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct) (ledger.ExternalRechargeOrder, error) {
	if !s.externalRecharge.USDTTRC20Enabled || s.externalRecharge.USDTTRC20Address == "" {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "usdt trc20 recharge is not configured")
	}
	return s.repository.CreateExternalRechargeOrder(ctx, command, product, nil, amountMicroToUSDMinor(product.AmountMicro), s.externalRecharge.USDTTRC20Address)
}

func (s *Service) createMifaPayRechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct) (ledger.ExternalRechargeOrder, error) {
	if s.mifaPay == nil {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "mifapay client is not configured")
	}
	method, err := s.repository.GetThirdPartyPaymentMethod(ctx, command.AppCode, command.PaymentMethodID)
	if err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	if err := validateMifaPayMethodForOrder(method, command); err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	providerAmountMinor, err := calculateProviderAmountMinor(amountMicroToUSDMinor(product.AmountMicro), method.USDToCurrencyRate)
	if err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	order, err := s.repository.CreateExternalRechargeOrder(ctx, command, product, &method, providerAmountMinor, "")
	if err != nil || order.IdempotentReplay {
		return order, err
	}
	notifyURL := firstNonEmpty(command.NotifyURL, s.externalRecharge.MifaPayNotifyURL)
	returnURL := firstNonEmpty(command.ReturnURL, s.externalRecharge.MifaPayReturnURL)
	if notifyURL == "" {
		_, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, "mifapay notify_url is not configured", "")
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "mifapay notify_url is not configured")
	}
	resp, err := s.mifaPay.CreateOrder(ctx, MifaPayCreateOrderRequest{
		OrderID:             order.OrderID,
		TargetUserID:        order.TargetUserID,
		ProductName:         order.ProductName,
		CoinAmount:          order.CoinAmount,
		AmountMinor:         order.USDMinorAmount,
		CurrencyCode:        order.CurrencyCode,
		CountryCode:         order.CountryCode,
		PayWay:              order.PayWay,
		PayType:             order.PayType,
		NotifyURL:           notifyURL,
		ReturnURL:           returnURL,
		ClientIP:            command.ClientIP,
		Language:            command.Language,
		ProviderAmountMinor: providerAmountMinor,
	})
	if err != nil {
		_, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, err.Error(), "")
		return ledger.ExternalRechargeOrder{}, err
	}
	return s.repository.MarkExternalRechargeOrderRedirected(ctx, command.AppCode, order.OrderID, resp.ProviderOrderID, resp.PayURL, resp.RawJSON)
}

// SubmitH5RechargeTx 把用户提交的 tx_hash 交给 TRON 适配器校验，通过后立即入账。
func (s *Service) SubmitH5RechargeTx(ctx context.Context, command ledger.SubmitExternalRechargeTxCommand) (ledger.ExternalRechargeOrder, error) {
	command.AppCode = appcode.Normalize(command.AppCode)
	command.OrderID = strings.TrimSpace(command.OrderID)
	command.TxHash = strings.TrimSpace(command.TxHash)
	if command.OrderID == "" || command.TargetUserID <= 0 || command.TxHash == "" {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.InvalidArgument, "usdt tx command is incomplete")
	}
	if s.repository == nil {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	ctx = appcode.WithContext(ctx, command.AppCode)
	order, err := s.repository.GetExternalRechargeOrder(ctx, command.AppCode, command.OrderID)
	if err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	if order.TargetUserID != command.TargetUserID {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.PermissionDenied, "external recharge order does not belong to user")
	}
	if order.ProviderCode != ledger.PaymentProviderUSDTTRC20 || order.Status != ledger.ExternalRechargeStatusPending {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Conflict, "external recharge order is not pending usdt order")
	}
	if s.tronUSDT == nil {
		return s.repository.AttachExternalRechargeTx(ctx, command, "")
	}
	rawJSON, err := s.tronUSDT.VerifyUSDTTransfer(ctx, command.TxHash, order.ReceiveAddress, order.USDMinorAmount)
	if err != nil {
		_, _ = s.repository.AttachExternalRechargeTx(ctx, command, rawJSON)
		return ledger.ExternalRechargeOrder{}, err
	}
	if _, err := s.repository.AttachExternalRechargeTx(ctx, command, rawJSON); err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	return s.repository.CreditExternalRechargeOrder(ctx, command.AppCode, command.OrderID, "", command.TxHash, rawJSON)
}

func (s *Service) GetH5RechargeOrder(ctx context.Context, appCode string, orderID string, targetUserID int64) (ledger.ExternalRechargeOrder, error) {
	if s.repository == nil {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	ctx = appcode.WithContext(ctx, appcode.Normalize(appCode))
	order, err := s.repository.GetExternalRechargeOrder(ctx, appcode.FromContext(ctx), strings.TrimSpace(orderID))
	if err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	if targetUserID > 0 && order.TargetUserID != targetUserID {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.PermissionDenied, "external recharge order does not belong to user")
	}
	return order, nil
}

// HandleMifapayNotify 只在验签、订单号、币种和金额全部匹配后入账；重复回调返回 SUCCESS。
func (s *Service) HandleMifapayNotify(ctx context.Context, appCode string, notification ledger.MifaPayNotification) (ledger.ExternalRechargeOrder, bool, error) {
	if s.mifaPay == nil {
		return ledger.ExternalRechargeOrder{}, false, xerr.New(xerr.Unavailable, "mifapay client is not configured")
	}
	if s.repository == nil {
		return ledger.ExternalRechargeOrder{}, false, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	ctx = appcode.WithContext(ctx, appcode.Normalize(appCode))
	data, err := s.mifaPay.ParseNotification(notification)
	if err != nil {
		return ledger.ExternalRechargeOrder{}, false, err
	}
	order, err := s.repository.GetExternalRechargeOrder(ctx, appcode.FromContext(ctx), data.OrderID)
	if err != nil {
		return ledger.ExternalRechargeOrder{}, false, err
	}
	if !strings.EqualFold(data.OrderStatus, "SUCCESS") {
		order, err = s.repository.MarkExternalRechargeOrderFailed(ctx, order.AppCode, order.OrderID, "mifapay order status "+data.OrderStatus, data.RawJSON)
		return order, true, err
	}
	if !mifaPayNotifyMatchesOrder(data, order) {
		order, err = s.repository.MarkExternalRechargeOrderFailed(ctx, order.AppCode, order.OrderID, "mifapay callback amount or currency mismatch", data.RawJSON)
		return order, false, err
	}
	credited, err := s.repository.CreditExternalRechargeOrder(ctx, order.AppCode, order.OrderID, data.ProviderOrderID, "", data.RawJSON)
	return credited, err == nil, err
}

func normalizeCreateExternalRechargeOrderCommand(command ledger.CreateExternalRechargeOrderCommand) ledger.CreateExternalRechargeOrderCommand {
	command.AppCode = appcode.Normalize(command.AppCode)
	command.CommandID = strings.TrimSpace(command.CommandID)
	command.TargetCountryCode = strings.ToUpper(strings.TrimSpace(command.TargetCountryCode))
	command.AudienceType = ledger.NormalizeRechargeAudienceType(command.AudienceType)
	command.ProviderCode = ledger.NormalizePaymentProvider(command.ProviderCode)
	command.ReturnURL = strings.TrimSpace(command.ReturnURL)
	command.NotifyURL = strings.TrimSpace(command.NotifyURL)
	command.ClientIP = strings.TrimSpace(command.ClientIP)
	command.Language = strings.TrimSpace(command.Language)
	if command.Language == "" {
		command.Language = "en"
	}
	return command
}

func validateCreateExternalRechargeOrderCommand(command ledger.CreateExternalRechargeOrderCommand) error {
	if command.CommandID == "" || command.TargetUserID <= 0 || command.TargetRegionID <= 0 || command.ProductID <= 0 {
		return xerr.New(xerr.InvalidArgument, "external recharge command is incomplete")
	}
	if command.AudienceType == "" || command.ProviderCode == "" {
		return xerr.New(xerr.InvalidArgument, "external recharge command type is invalid")
	}
	if len(command.CommandID) > 128 {
		return xerr.New(xerr.InvalidArgument, "command_id is too long")
	}
	return nil
}

func validateExternalRechargeProduct(product ledger.RechargeProduct, command ledger.CreateExternalRechargeOrderCommand) error {
	if product.Status != ledger.RechargeProductStatusActive || !product.Enabled {
		return xerr.New(xerr.Conflict, "recharge product is not active")
	}
	if product.Platform != ledger.RechargeProductPlatformWeb {
		return xerr.New(xerr.InvalidArgument, "recharge product is not h5 product")
	}
	if product.AudienceType != command.AudienceType {
		return xerr.New(xerr.Conflict, "recharge product audience does not match")
	}
	if !rechargeProductSupportsRegion(product, command.TargetRegionID) {
		return xerr.New(xerr.Conflict, "recharge product is not available in user region")
	}
	return nil
}

func validateMifaPayMethodForOrder(method ledger.ThirdPartyPaymentMethod, command ledger.CreateExternalRechargeOrderCommand) error {
	if method.ProviderCode != ledger.PaymentProviderMifaPay || method.Status != ledger.ThirdPartyPaymentStatusActive {
		return xerr.New(xerr.Conflict, "mifapay method is not active")
	}
	if method.CountryCode != "GLOBAL" && !strings.EqualFold(method.CountryCode, command.TargetCountryCode) {
		return xerr.New(xerr.Conflict, "mifapay method country does not match")
	}
	if _, err := calculateProviderAmountMinor(100, method.USDToCurrencyRate); err != nil {
		return err
	}
	return nil
}

func calculateProviderAmountMinor(usdMinor int64, rate string) (int64, error) {
	if usdMinor <= 0 {
		return 0, xerr.New(xerr.InvalidArgument, "usd amount is invalid")
	}
	value, err := strconv.ParseFloat(strings.TrimSpace(rate), 64)
	if err != nil || value <= 0 {
		return 0, xerr.New(xerr.InvalidArgument, "payment exchange rate is invalid")
	}
	return int64(math.Round(float64(usdMinor) * value)), nil
}

func amountMicroToUSDMinor(amountMicro int64) int64 {
	if amountMicro <= 0 {
		return 0
	}
	return amountMicro / 10_000
}

func validNonNegativeDecimal(value string) bool {
	parsed, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
	return err == nil && parsed >= 0
}

func mifaPayNotifyMatchesOrder(data MifaPayNotifyData, order ledger.ExternalRechargeOrder) bool {
	if !strings.EqualFold(data.Currency, order.CurrencyCode) {
		return false
	}
	if data.PayType != "" && !strings.EqualFold(data.PayType, order.PayType) {
		return false
	}
	amount, err := strconv.ParseInt(strings.TrimSpace(data.Amount), 10, 64)
	return err == nil && amount == order.ProviderAmountMinor
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if value = strings.TrimSpace(value); value != "" {
			return value
		}
	}
	return ""
}

func mustJSONText(value any) string {
	body, err := json.Marshal(value)
	if err != nil {
		return "{}"
	}
	return string(body)
}
