package wallet

import (
	"context"
	"errors"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
	"hyapp/services/wallet-service/internal/service/wallet/ports"
	"strings"
)

type mifaPayProviderPayloadError interface {
	ProviderPayloadJSON() string
}

// 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)
	case ledger.PaymentProviderV5Pay:
		return s.createV5PayRechargeOrder(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) {
	receiveAddress := s.externalRecharge.usdtTRC20AddressForApp(command.AppCode)
	if !s.externalRecharge.USDTTRC20Enabled || receiveAddress == "" {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "usdt trc20 recharge is not configured")
	}
	return s.repository.CreateExternalRechargeOrder(ctx, command, product, nil, amountMicroToUSDMinor(product.AmountMicro), receiveAddress)
}

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.sharedPaymentMethod(ctx, command.AppCode, ledger.PaymentProviderMifaPay, command.PaymentMethodID)
	if err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	if err := validateMifaPayMethodForOrder(method, command); err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	providerAmountMinor, err := calculateMifaPayProviderAmountMinor(amountMicroToUSDMinor(product.AmountMicro), method.USDToCurrencyRate, method.CurrencyCode)
	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:           mifaPayProviderReturnURL(returnURL),
		ClientIP:            command.ClientIP,
		Language:            command.Language,
		ProviderAmountMinor: providerAmountMinor,
		PayerName:           command.PayerName,
		PayerAccount:        command.PayerAccount,
		PayerEmail:          command.PayerEmail,
	})
	if err != nil {
		_, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, err.Error(), mifaPayProviderPayloadJSON(err))
		return ledger.ExternalRechargeOrder{}, normalizeMifaPayOrderError(err)
	}
	return s.repository.MarkExternalRechargeOrderRedirected(ctx, command.AppCode, order.OrderID, resp.ProviderOrderID, resp.PayURL, resp.RawJSON)
}

func (s *Service) createV5PayRechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct) (ledger.ExternalRechargeOrder, error) {
	if s.v5Pay == nil {
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "v5pay client is not configured")
	}
	method, err := s.sharedPaymentMethod(ctx, command.AppCode, ledger.PaymentProviderV5Pay, command.PaymentMethodID)
	if err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	if err := validateV5PayMethodForOrder(method, command); err != nil {
		return ledger.ExternalRechargeOrder{}, err
	}
	providerAmountMinor, err := calculateProviderAmountMinor(amountMicroToUSDMinor(product.AmountMicro), method.USDToCurrencyRate, method.CurrencyCode)
	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.V5PayNotifyURL)
	returnURL := firstNonEmpty(command.ReturnURL, s.externalRecharge.V5PayReturnURL)
	if notifyURL == "" {
		_, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, "v5pay notify_url is not configured", "")
		return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "v5pay notify_url is not configured")
	}
	resp, err := s.v5Pay.CreateOrder(ctx, V5PayCreateOrderRequest{
		OrderID:             order.OrderID,
		TargetUserID:        order.TargetUserID,
		ProductName:         order.ProductName,
		CoinAmount:          order.CoinAmount,
		AmountMinor:         order.USDMinorAmount,
		CurrencyCode:        order.CurrencyCode,
		CountryCode:         order.CountryCode,
		ProductType:         order.PayType,
		NotifyURL:           notifyURL,
		ReturnURL:           v5PayProviderReturnURL(returnURL),
		Language:            command.Language,
		ProviderAmountMinor: providerAmountMinor,
		PayerName:           command.PayerName,
		PayerAccount:        command.PayerAccount,
		PayerEmail:          command.PayerEmail,
	})
	if err != nil {
		_, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, err.Error(), providerPayloadJSON(err))
		return ledger.ExternalRechargeOrder{}, normalizeExternalProviderOrderError(err, "v5pay")
	}
	return s.repository.MarkExternalRechargeOrderRedirected(ctx, command.AppCode, order.OrderID, resp.ProviderOrderID, resp.PayURL, resp.RawJSON)
}

func normalizeMifaPayOrderError(err error) error {
	return normalizeExternalProviderOrderError(err, "mifapay")
}

func mifaPayProviderPayloadJSON(err error) string {
	return providerPayloadJSON(err)
}

func normalizeExternalProviderOrderError(err error, provider string) error {
	if err == nil {
		return nil
	}
	if _, ok := xerr.As(err); ok {
		return err
	}

	return xerr.New(xerr.Unavailable, provider+" order upstream response is invalid")
}

func providerPayloadJSON(err error) string {
	var payloadErr mifaPayProviderPayloadError
	if errors.As(err, &payloadErr) {

		return strings.TrimSpace(payloadErr.ProviderPayloadJSON())
	}
	return ""
}

// SubmitH5RechargeTx 校验用户提交的 TRON 链上哈希或 Binance 站内转账号，通过后立即入账。
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")
	}
	providerOrderID, rawJSON, err := s.verifyH5USDTTransfer(ctx, command, order)
	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, providerOrderID, command.TxHash, rawJSON)
}

// verifyH5USDTTransfer 只根据凭证形态选择事实源：64 位十六进制哈希查 TRON，其他编号查当前 App 的 Binance 收款记录。
// Binance 路径不信任付款方截图；client 会对 Deposit History 或 Pay C2C 的收款方向、币种和金额做强校验。
func (s *Service) verifyH5USDTTransfer(ctx context.Context, command ledger.SubmitExternalRechargeTxCommand, order ledger.ExternalRechargeOrder) (string, string, error) {
	if isLikelyChainTransactionHash(command.TxHash) {
		if s.tronUSDT == nil {
			return "", "", xerr.New(xerr.Unavailable, "tron grid client is not configured")
		}
		rawJSON, err := s.tronUSDT.VerifyUSDTTransfer(ctx, command.TxHash, order.ReceiveAddress, order.USDMinorAmount)
		return "", rawJSON, err
	}
	if s.binance == nil {
		return "", "", xerr.New(xerr.Unavailable, "binance client is not configured")
	}
	record, err := s.binance.FindUSDTTransfer(ctx, ports.BinanceTransferLookupRequest{
		AppCode:     command.AppCode,
		ExternalID:  command.TxHash,
		Coin:        ledger.PaidCurrencyUSDT,
		Network:     "TRX",
		ToAddress:   order.ReceiveAddress,
		AmountMinor: order.USDMinorAmount,
		OrderTimeMS: order.CreatedAtMS,
	})
	if err != nil {
		return "", "", err
	}
	return record.TxID, strings.TrimSpace(record.RawJSON), nil
}

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")
	}
	if refreshed, err := s.refreshMifaPayRechargeOrder(ctx, order); err == nil {
		order = refreshed
	}
	if refreshed, err := s.refreshV5PayRechargeOrder(ctx, order); err == nil {
		order = refreshed
	}
	return order, nil
}
