package wallet

import (
	"context"
	"strconv"
	"strings"
	"time"

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

const (
	mifaPayReceiptOrderDateLookbackDays = 30
	v5PayReceiptCandidateLimit          = 32
)

// VerifyCoinSellerRechargeReceipt 只读取三方支付商或链上公开数据，给运营返回可复核凭证快照。
func (s *Service) VerifyCoinSellerRechargeReceipt(ctx context.Context, command ledger.VerifyCoinSellerRechargeReceiptCommand) (ledger.CoinSellerRechargeReceiptVerification, error) {
	command = normalizeVerifyCoinSellerRechargeReceiptCommand(command)
	if err := validateVerifyCoinSellerRechargeReceiptCommand(command); err != nil {
		return ledger.CoinSellerRechargeReceiptVerification{}, err
	}
	ctx = appcode.WithContext(ctx, command.AppCode)
	switch command.ProviderCode {
	case ledger.PaymentProviderMifaPay:
		return s.verifyMifaPayRechargeReceipt(ctx, command)
	case ledger.PaymentProviderV5Pay:
		return s.verifyV5PayRechargeReceipt(ctx, command)
	case ledger.PaymentProviderUSDTTRC20:
		return s.verifyUSDTRechargeReceipt(ctx, command, "trc20", s.externalRecharge.USDTTRC20Enabled, firstNonEmpty(command.ReceiveAddress, s.externalRecharge.usdtTRC20AddressForApp(command.AppCode)), s.tronUSDT)
	case ledger.PaymentProviderUSDTBEP20:
		return s.verifyUSDTRechargeReceipt(ctx, command, "bep20", s.externalRecharge.USDTBEP20Enabled, firstNonEmpty(command.ReceiveAddress, s.externalRecharge.usdtBEP20AddressForApp(command.AppCode)), s.bscUSDT)
	case ledger.PaymentProviderUSDTC2C:
		// C2C 是 Binance 账户内收款事实，不属于 TRON/BSC；空地址和关闭的链上开关不影响只读查单。
		return s.verifyUSDTRechargeReceipt(ctx, command, "c2c", false, "", nil)
	case ledger.PaymentProviderUSDTOffchain:
		// Binance Off-chain 是 Deposit History 中的 TRX 入账事实；后台 SQL 地址优先于 YAML，
		// 但仍要求 TRC20 功能开启，避免已停用的收款链被人工入口绕过。
		return s.verifyUSDTRechargeReceipt(ctx, command, "offchain", s.externalRecharge.USDTTRC20Enabled, firstNonEmpty(command.ReceiveAddress, s.externalRecharge.usdtTRC20AddressForApp(command.AppCode)), nil)
	default:
		return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.InvalidArgument, "receipt provider is invalid")
	}
}

func normalizeVerifyCoinSellerRechargeReceiptCommand(command ledger.VerifyCoinSellerRechargeReceiptCommand) ledger.VerifyCoinSellerRechargeReceiptCommand {
	command.AppCode = appcode.Normalize(command.AppCode)
	command.RequestID = strings.TrimSpace(command.RequestID)
	command.ProviderCode = ledger.NormalizePaymentProvider(command.ProviderCode)
	command.ExternalOrderNo = strings.TrimSpace(command.ExternalOrderNo)
	command.Chain = normalizeReceiptChain(command.Chain)
	command.ProviderCountryCode = strings.ToUpper(strings.TrimSpace(command.ProviderCountryCode))
	command.ProviderCurrencyCode = strings.ToUpper(strings.TrimSpace(command.ProviderCurrencyCode))
	command.ProviderPayType = strings.TrimSpace(command.ProviderPayType)
	command.ReceiveAddress = strings.TrimSpace(command.ReceiveAddress)
	return command
}

func validateVerifyCoinSellerRechargeReceiptCommand(command ledger.VerifyCoinSellerRechargeReceiptCommand) error {
	if command.ProviderCode == "" || command.ExternalOrderNo == "" {
		return xerr.New(xerr.InvalidArgument, "receipt verification request is incomplete")
	}
	if len(command.ExternalOrderNo) > 128 {
		return xerr.New(xerr.InvalidArgument, "external_order_no is too long")
	}
	switch command.ProviderCode {
	case ledger.PaymentProviderUSDTTRC20, ledger.PaymentProviderUSDTBEP20, ledger.PaymentProviderUSDTOffchain:
		if command.USDMinorAmount <= 0 {
			return xerr.New(xerr.InvalidArgument, "usdt receipt amount is invalid")
		}
	}
	return nil
}

func (s *Service) verifyMifaPayRechargeReceipt(ctx context.Context, command ledger.VerifyCoinSellerRechargeReceiptCommand) (ledger.CoinSellerRechargeReceiptVerification, error) {
	if s.mifaPay == nil {
		return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "mifapay client is not configured")
	}
	snapshot := s.receiptSnapshot(command)
	orderDateMS := command.OrderDateMS
	if orderDateMS <= 0 {
		// MiFaPay 查单需要订单日期；后台新流程不让运营填写日期，因此优先用本地支付订单快照补齐。
		orderDateMS = s.externalRechargeOrderCreatedAtMS(ctx, command.AppCode, command.ExternalOrderNo, ledger.PaymentProviderMifaPay)
	}
	var lastNotFound string
	for _, orderCreatedAtMS := range s.mifaPayReceiptOrderDateCandidates(orderDateMS) {
		query, err := s.mifaPay.QueryOrder(ctx, MifaPayQueryOrderRequest{
			OrderID:          command.ExternalOrderNo,
			OrderCreatedAtMS: orderCreatedAtMS,
		})
		if err != nil {
			if providerOrderNotFound(err) {
				lastNotFound = err.Error()
				continue
			}
			// 签名、HTTP、网络或未知 provider 拒绝都直接返回给后台；这些错误不能被日期回查吞掉。
			snapshot.FailureReason = err.Error()
			return snapshot, nil
		}
		return fillMifaPayReceiptSnapshot(snapshot, command, query), nil
	}
	if orderDateMS > 0 {
		snapshot.FailureReason = firstNonEmpty(lastNotFound, "mifapay order not found")
	} else {
		snapshot.FailureReason = "mifapay order not found in recent 30 days"
	}
	return snapshot, nil
}

func fillMifaPayReceiptSnapshot(snapshot ledger.CoinSellerRechargeReceiptVerification, command ledger.VerifyCoinSellerRechargeReceiptCommand, query MifaPayQueryOrderResponse) ledger.CoinSellerRechargeReceiptVerification {
	snapshot.ProviderOrderID = strings.TrimSpace(query.ProviderOrderID)
	snapshot.Status = strings.TrimSpace(query.Status)
	snapshot.CurrencyCode = strings.ToUpper(strings.TrimSpace(query.Currency))
	snapshot.RawJSON = strings.TrimSpace(query.RawJSON)
	amount, amountErr := parseMifaPayReceiptAmountMinor(query.Amount, snapshot.CurrencyCode)
	if amountErr == nil {
		snapshot.ProviderAmountMinor = amount
	}
	if query.OrderID != "" && !strings.EqualFold(query.OrderID, command.ExternalOrderNo) {
		snapshot.FailureReason = "mifapay order id mismatch"
		return snapshot
	}
	if !isMifaPayReceiptPaid(query.Status) {
		snapshot.FailureReason = "mifapay order status " + firstNonEmpty(strings.TrimSpace(query.Status), "unknown")
		return snapshot
	}
	if command.ProviderCurrencyCode != "" && snapshot.CurrencyCode != "" && !strings.EqualFold(snapshot.CurrencyCode, command.ProviderCurrencyCode) {
		snapshot.FailureReason = "mifapay currency mismatch"
		return snapshot
	}
	if amountErr != nil || snapshot.ProviderAmountMinor <= 0 {
		// MiFaPay 付款成功但金额字段缺失/坏值时，后台无法建立资金事实，必须失败而不是退化成只验订单号。
		snapshot.FailureReason = "mifapay amount is invalid"
		return snapshot
	}
	if command.ProviderAmountMinor > 0 {
		if snapshot.ProviderAmountMinor != command.ProviderAmountMinor {
			snapshot.FailureReason = "mifapay amount mismatch"
			return snapshot
		}
	}
	if command.ProviderPayType != "" && query.PayType != "" && !strings.EqualFold(query.PayType, command.ProviderPayType) {
		snapshot.FailureReason = "mifapay pay_type mismatch"
		return snapshot
	}
	snapshot.Verified = true
	return snapshot
}

func (s *Service) mifaPayReceiptOrderDateCandidates(orderDateMS int64) []int64 {
	if orderDateMS > 0 {
		return []int64{orderDateMS}
	}
	// 本地没有快照时只按 UTC 自然日做 30 天 bounded 回查，防止一次后台校验演变成无界 provider 扫描。
	now := s.now().UTC()
	startOfToday := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
	candidates := make([]int64, 0, mifaPayReceiptOrderDateLookbackDays+1)
	for dayOffset := 0; dayOffset <= mifaPayReceiptOrderDateLookbackDays; dayOffset++ {
		candidates = append(candidates, startOfToday.AddDate(0, 0, -dayOffset).UnixMilli())
	}
	return candidates
}

func (s *Service) verifyV5PayRechargeReceipt(ctx context.Context, command ledger.VerifyCoinSellerRechargeReceiptCommand) (ledger.CoinSellerRechargeReceiptVerification, error) {
	if s.v5Pay == nil {
		return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "v5pay client is not configured")
	}
	snapshot := s.receiptSnapshot(command)
	candidates, err := s.v5PayReceiptQueryCandidates(ctx, command)
	if err != nil {
		return ledger.CoinSellerRechargeReceiptVerification{}, err
	}
	if len(candidates) == 0 {
		snapshot.FailureReason = "v5pay country/currency candidates are not configured"
		return snapshot, nil
	}
	for _, candidate := range candidates {
		query, err := s.v5Pay.QueryOrder(ctx, V5PayQueryOrderRequest{
			OrderID:      command.ExternalOrderNo,
			CountryCode:  candidate.CountryCode,
			CurrencyCode: candidate.CurrencyCode,
		})
		if err != nil {
			if providerOrderNotFound(err) {
				continue
			}
			snapshot.FailureReason = err.Error()
			return snapshot, nil
		}
		return fillV5PayReceiptSnapshot(snapshot, command, query), nil
	}
	snapshot.FailureReason = "v5pay order not found for enabled country/currency candidates"
	return snapshot, nil
}

func fillV5PayReceiptSnapshot(snapshot ledger.CoinSellerRechargeReceiptVerification, command ledger.VerifyCoinSellerRechargeReceiptCommand, query V5PayQueryOrderResponse) ledger.CoinSellerRechargeReceiptVerification {
	snapshot.ProviderOrderID = strings.TrimSpace(query.ProviderOrderID)
	snapshot.Status = strings.TrimSpace(query.Status)
	snapshot.CurrencyCode = strings.ToUpper(strings.TrimSpace(query.Currency))
	snapshot.RawJSON = strings.TrimSpace(query.RawJSON)
	amount, amountErr := parseProviderAmountMinor(query.Amount)
	if amountErr == nil {
		snapshot.ProviderAmountMinor = amount
	}
	if query.OrderID != "" && !strings.EqualFold(query.OrderID, command.ExternalOrderNo) {
		snapshot.FailureReason = "v5pay order id mismatch"
		return snapshot
	}
	if !isV5PayReceiptPaid(query.Status) {
		snapshot.FailureReason = "v5pay order status " + firstNonEmpty(strings.TrimSpace(query.Status), "unknown")
		return snapshot
	}
	if command.ProviderCurrencyCode != "" && snapshot.CurrencyCode != "" && !strings.EqualFold(snapshot.CurrencyCode, command.ProviderCurrencyCode) {
		snapshot.FailureReason = "v5pay currency mismatch"
		return snapshot
	}
	if amountErr != nil || snapshot.ProviderAmountMinor <= 0 {
		// V5Pay 人工校验依赖回包金额建立付款事实；即使内部调用未传期望金额，坏金额也不能放行。
		snapshot.FailureReason = "v5pay amount is invalid"
		return snapshot
	}
	if command.ProviderAmountMinor > 0 {
		if snapshot.ProviderAmountMinor != command.ProviderAmountMinor {
			snapshot.FailureReason = "v5pay amount mismatch"
			return snapshot
		}
	}
	if command.ProviderPayType != "" && query.ProductType != "" && !strings.EqualFold(query.ProductType, command.ProviderPayType) {
		snapshot.FailureReason = "v5pay product_type mismatch"
		return snapshot
	}
	snapshot.Verified = true
	return snapshot
}

func parseMifaPayReceiptAmountMinor(value string, currencyCode string) (int64, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return 0, strconv.ErrSyntax
	}
	parsed, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		return 0, err
	}
	if mifaPayUsesWholeMajorAmount(currencyCode) {
		// MiFaPay INR 查单回包沿用上游 contract：amount 是整数卢比主单位；本地快照仍统一存 minor。
		return parsed * 100, nil
	}
	return parsed, nil
}

type v5PayReceiptQueryCandidate struct {
	CountryCode  string
	CurrencyCode string
}

func (s *Service) v5PayReceiptQueryCandidates(ctx context.Context, command ledger.VerifyCoinSellerRechargeReceiptCommand) ([]v5PayReceiptQueryCandidate, error) {
	candidates := make([]v5PayReceiptQueryCandidate, 0, 4)
	seen := make(map[string]struct{})
	addCandidate := func(countryCode string, currencyCode string) {
		if len(candidates) >= v5PayReceiptCandidateLimit {
			return
		}
		countryCode = strings.ToUpper(strings.TrimSpace(countryCode))
		currencyCode = strings.ToUpper(strings.TrimSpace(currencyCode))
		if countryCode == "" || currencyCode == "" {
			return
		}
		key := countryCode + "|" + currencyCode
		if _, ok := seen[key]; ok {
			return
		}
		seen[key] = struct{}{}
		candidates = append(candidates, v5PayReceiptQueryCandidate{CountryCode: countryCode, CurrencyCode: currencyCode})
	}

	if command.ProviderCountryCode != "" && command.ProviderCurrencyCode != "" {
		addCandidate(command.ProviderCountryCode, command.ProviderCurrencyCode)
		return candidates, nil
	}
	// V5Pay 查单需要国家/币种；运营只填订单号时，先从本地外部充值订单补条件，命中率最高且不会扩大查询面。
	if order, ok := s.externalRechargeOrder(ctx, command.AppCode, command.ExternalOrderNo, ledger.PaymentProviderV5Pay); ok {
		addCandidate(order.CountryCode, order.CurrencyCode)
	}
	if s.repository == nil {
		return candidates, nil
	}
	channels, err := s.ListThirdPartyPaymentChannels(ctx, ledger.ListThirdPartyPaymentChannelsQuery{
		AppCode:                command.AppCode,
		ProviderCode:           ledger.PaymentProviderV5Pay,
		Status:                 ledger.ThirdPartyPaymentStatusActive,
		IncludeDisabledMethods: false,
	})
	if err != nil {
		return nil, err
	}
	// 本地快照缺失时，只枚举当前 APP 启用的 V5Pay 国家/币种组合，并受候选上限保护，避免误配导致查单风暴。
	for _, preferProvidedFields := range []bool{true, false} {
		for _, channel := range channels {
			if channel.ProviderCode != ledger.PaymentProviderV5Pay || channel.Status != ledger.ThirdPartyPaymentStatusActive {
				continue
			}
			for _, method := range channel.Methods {
				if method.ProviderCode != ledger.PaymentProviderV5Pay || method.Status != ledger.ThirdPartyPaymentStatusActive {
					continue
				}
				matchesProvidedCountry := command.ProviderCountryCode == "" || strings.EqualFold(method.CountryCode, command.ProviderCountryCode)
				matchesProvidedCurrency := command.ProviderCurrencyCode == "" || strings.EqualFold(method.CurrencyCode, command.ProviderCurrencyCode)
				if preferProvidedFields != (matchesProvidedCountry && matchesProvidedCurrency) {
					continue
				}
				addCandidate(method.CountryCode, method.CurrencyCode)
			}
		}
	}
	return candidates, nil
}

func (s *Service) externalRechargeOrderCreatedAtMS(ctx context.Context, appCode string, orderID string, providerCode string) int64 {
	if order, ok := s.externalRechargeOrder(ctx, appCode, orderID, providerCode); ok {
		return order.CreatedAtMS
	}
	return 0
}

func (s *Service) externalRechargeOrder(ctx context.Context, appCode string, orderID string, providerCode string) (ledger.ExternalRechargeOrder, bool) {
	if s == nil || s.repository == nil {
		return ledger.ExternalRechargeOrder{}, false
	}
	order, err := s.repository.GetExternalRechargeOrder(ctx, appCode, orderID)
	if err != nil || order.ProviderCode != providerCode {
		return ledger.ExternalRechargeOrder{}, false
	}
	return order, true
}

type receiptUSDTClient interface {
	VerifyUSDTTransfer(ctx context.Context, txHash string, toAddress string, amountMinor int64) (string, error)
}

func (s *Service) verifyUSDTRechargeReceipt(ctx context.Context, command ledger.VerifyCoinSellerRechargeReceiptCommand, chain string, enabled bool, receiveAddress string, client receiptUSDTClient) (ledger.CoinSellerRechargeReceiptVerification, error) {
	snapshot := s.receiptSnapshot(command)
	snapshot.Chain = chain
	snapshot.CurrencyCode = ledger.PaidCurrencyUSDT
	snapshot.ProviderAmountMinor = command.USDMinorAmount
	snapshot.ProviderOrderID = command.ExternalOrderNo
	snapshot.ReceiveAddress = strings.TrimSpace(receiveAddress)
	if chain == "c2c" {
		// 查单来源由运营显式选择；C2C 只读取 Binance 账户事实，绝不从 TRON/BSC 隐式降级或猜测。
		return s.verifyBinanceTransferReceipt(ctx, command, snapshot, binanceNetworkForReceiptChain(chain))
	}
	if !enabled || snapshot.ReceiveAddress == "" {
		return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "usdt "+chain+" receipt verification is not configured")
	}
	if chain == "offchain" {
		// Off-chain 编号不是公链 hash，只能走 Binance Deposit History；地址和金额仍必须与当前 App 配置精确一致。
		return s.verifyBinanceTransferReceipt(ctx, command, snapshot, binanceNetworkForReceiptChain(chain))
	}
	if !isLikelyChainTransactionHash(command.ExternalOrderNo) {
		// TRON/BSC 只接受标准交易哈希；非链上编号必须由运营明确改选 C2C，避免跨事实源误校验。
		return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.InvalidArgument, "usdt "+chain+" receipt requires transaction hash")
	}
	if client == nil {
		return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "usdt "+chain+" client is not configured")
	}
	rawJSON, err := client.VerifyUSDTTransfer(ctx, command.ExternalOrderNo, snapshot.ReceiveAddress, command.USDMinorAmount)
	snapshot.RawJSON = strings.TrimSpace(rawJSON)
	if err != nil {
		snapshot.Status = "unverified"
		snapshot.FailureReason = err.Error()
		return snapshot, nil
	}
	snapshot.Status = "confirmed"
	snapshot.Verified = true
	return snapshot, nil
}

func (s *Service) verifyBinanceTransferReceipt(ctx context.Context, command ledger.VerifyCoinSellerRechargeReceiptCommand, snapshot ledger.CoinSellerRechargeReceiptVerification, network string) (ledger.CoinSellerRechargeReceiptVerification, error) {
	if s.binance == nil {
		return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "binance client is not configured")
	}
	// 非链上编号可能来自 Deposit History 的 off-chain transfer，也可能来自 Funding Account 的 Pay C2C。
	// Binance client 必须在收款账号侧验证正向入账；付款方截图本身不能成为放币依据。
	record, err := s.binance.FindUSDTTransfer(ctx, ports.BinanceTransferLookupRequest{
		AppCode:     command.AppCode,
		ExternalID:  command.ExternalOrderNo,
		Coin:        ledger.PaidCurrencyUSDT,
		Network:     network,
		ToAddress:   snapshot.ReceiveAddress,
		AmountMinor: command.USDMinorAmount,
		OrderTimeMS: command.OrderDateMS,
	})
	if err != nil {
		snapshot.Status = "unverified"
		snapshot.FailureReason = err.Error()
		return snapshot, nil
	}
	snapshot.Verified = true
	snapshot.Status = "confirmed"
	snapshot.ProviderOrderID = record.TxID
	snapshot.ProviderAmountMinor = record.AmountMinor
	if snapshot.Chain == "c2c" && strings.TrimSpace(record.Coin) != "" {
		// C2C Pay 实际收款可能是 USDT 或 USDC；凭证必须保存 Binance 返回币种，不能继续伪装成表单默认 USDT。
		snapshot.CurrencyCode = strings.ToUpper(strings.TrimSpace(record.Coin))
	}
	// Pay C2C 没有链上收款地址；只有 Deposit History 命中时才用 Binance 返回地址覆盖配置快照。
	if snapshot.Chain != "c2c" && strings.TrimSpace(record.Address) != "" {
		snapshot.ReceiveAddress = record.Address
	}
	snapshot.RawJSON = strings.TrimSpace(record.RawJSON)
	return snapshot, nil
}

func binanceNetworkForReceiptChain(chain string) string {
	// Binance Deposit History 使用交易所网络代码而不是后台 provider 名称；Pay C2C 不校验网络，
	// 但同一个入口也支持 off-chain Deposit History，因此这里必须保留准确映射。
	switch strings.ToLower(strings.TrimSpace(chain)) {
	case "bep20":
		return "BSC"
	case "c2c":
		return "C2C"
	case "offchain":
		return "OFFCHAIN"
	default:
		return "TRX"
	}
}

func (s *Service) receiptSnapshot(command ledger.VerifyCoinSellerRechargeReceiptCommand) ledger.CoinSellerRechargeReceiptVerification {
	return ledger.CoinSellerRechargeReceiptVerification{
		ProviderCode:    command.ProviderCode,
		ExternalOrderNo: command.ExternalOrderNo,
		Chain:           command.Chain,
		VerifiedAtMS:    s.now().UTC().UnixMilli(),
	}
}

func isLikelyChainTransactionHash(value string) bool {
	value = strings.TrimPrefix(strings.ToLower(strings.TrimSpace(value)), "0x")
	if len(value) != 64 {
		return false
	}
	for _, r := range value {
		if (r < '0' || r > '9') && (r < 'a' || r > 'f') {
			return false
		}
	}
	return true
}

func isMifaPayReceiptPaid(status string) bool {
	switch strings.ToUpper(strings.TrimSpace(status)) {
	case "1", "SUCCESS":
		return true
	default:
		return false
	}
}

func isV5PayReceiptPaid(status string) bool {
	return strings.TrimSpace(status) == "2"
}

func normalizeReceiptChain(chain string) string {
	chain = strings.ToLower(strings.TrimSpace(chain))
	chain = strings.ReplaceAll(chain, "-", "_")
	switch chain {
	case "trc20", "trx", "tron", "usdt_trc20":
		return "trc20"
	case "bep20", "bsc", "bnb", "usdt_bep20":
		return "bep20"
	default:
		return chain
	}
}

func providerOrderNotFound(err error) bool {
	if err == nil {
		return false
	}
	if xerr.IsCode(err, xerr.NotFound) {
		return true
	}
	message := strings.ToLower(err.Error())
	return strings.Contains(message, "not found") ||
		strings.Contains(message, "not exist") ||
		strings.Contains(message, "does not exist") ||
		strings.Contains(message, "no data") ||
		strings.Contains(message, "order不存在") ||
		strings.Contains(message, "订单不存在")
}
