package wallet

import (
	"context"
	"log/slog"
	"sync"
	"time"

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

// ListRechargeBills 返回充值账单只读列表；所有充值来源必须先在 wallet-service 落充值记录。
func (s *Service) ListRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) ([]ledger.RechargeBill, int64, error) {
	if s.repository == nil {
		return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	query.AppCode = appcode.Normalize(query.AppCode)
	ctx = appcode.WithContext(ctx, query.AppCode)

	return s.repository.ListRechargeBills(ctx, query)
}

// GetRechargeBillSummary 按与账单列表一致的筛选口径聚合充值总和、Google 充值与三方充值。
func (s *Service) GetRechargeBillSummary(ctx context.Context, query ledger.ListRechargeBillsQuery) (ledger.RechargeBillSummary, error) {
	if s.repository == nil {
		return ledger.RechargeBillSummary{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	query.AppCode = appcode.Normalize(query.AppCode)
	ctx = appcode.WithContext(ctx, query.AppCode)
	return s.repository.SummarizeRechargeBills(ctx, query)
}

// BatchGetUserRechargeStats 返回后台详情需要的累计充值聚合；缺失用户不返回占位行，由调用方按 0 展示。
func (s *Service) BatchGetUserRechargeStats(ctx context.Context, appCode string, userIDs []int64) ([]ledger.UserRechargeStats, error) {
	if s.repository == nil {
		return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	appCode = appcode.Normalize(appCode)
	ctx = appcode.WithContext(ctx, appCode)
	return s.repository.BatchGetUserRechargeStats(ctx, userIDs)
}

// GetRechargeBillOverview 汇总财务概览的按日趋势、区域分布与谷歌实付覆盖度；筛选口径与账单列表一致。
func (s *Service) GetRechargeBillOverview(ctx context.Context, query ledger.ListRechargeBillsQuery, tzOffsetMinutes int32) (ledger.RechargeBillOverview, error) {
	if s.repository == nil {
		return ledger.RechargeBillOverview{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	query.AppCode = appcode.Normalize(query.AppCode)
	ctx = appcode.WithContext(ctx, query.AppCode)
	return s.repository.GetRechargeBillOverview(ctx, query, tzOffsetMinutes)
}

// GooglePaymentPriceResult 是单笔 Google 账单实付明细刷新结果；Err 非空表示该笔刷新失败但不影响其他账单。
type GooglePaymentPriceResult struct {
	TransactionID   string
	CurrencyCode    string
	PaidAmountMicro int64
	TaxAmountMicro  int64
	NetAmountMicro  int64
	SyncedAtMS      int64
	Err             string
}

type GooglePaymentPaidSyncBatchResult struct {
	Claimed   int
	Succeeded int
	Failed    int
}

type googlePlayOrdersBatchClient interface {
	GetOrders(ctx context.Context, packageName string, orderIDs []string) (map[string]ledger.GooglePlayOrder, error)
}

const maxGooglePaymentPriceRefreshBatch = 100

// RefreshGooglePaymentPrices 用 Play Orders API 拉取账单的用户实付币种、总额、税额和净收入并写回 payment_orders。
// 服务账号需要 Play Console 的“查看财务数据”权限；单笔失败只标记该笔的 Err，整批继续。
func (s *Service) RefreshGooglePaymentPrices(ctx context.Context, appCode string, transactionIDs []string) ([]GooglePaymentPriceResult, error) {
	if s.repository == nil {
		return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	if s.googlePlay == nil {
		return nil, xerr.New(xerr.Unavailable, "google play client is not configured")
	}
	if len(transactionIDs) == 0 {
		return []GooglePaymentPriceResult{}, nil
	}
	if len(transactionIDs) > maxGooglePaymentPriceRefreshBatch {
		return nil, xerr.New(xerr.InvalidArgument, "too many transaction_ids")
	}
	appCode = appcode.Normalize(appCode)
	ctx = appcode.WithContext(ctx, appCode)
	refs, err := s.repository.ListGooglePaymentOrderRefs(ctx, appCode, transactionIDs)
	if err != nil {
		return nil, err
	}
	results := make([]GooglePaymentPriceResult, 0, len(transactionIDs))
	for _, transactionID := range transactionIDs {
		transactionID = strings.TrimSpace(transactionID)
		if transactionID == "" {
			continue
		}
		ref, ok := refs[transactionID]
		if !ok {
			results = append(results, GooglePaymentPriceResult{TransactionID: transactionID, Err: "not a google payment bill"})
			continue
		}
		result := s.refreshGooglePaymentPrice(ctx, appCode, ref)
		results = append(results, result)
	}
	return results, nil
}

func (s *Service) refreshGooglePaymentPrice(ctx context.Context, appCode string, ref ledger.GooglePaymentOrderRef) GooglePaymentPriceResult {
	if strings.TrimSpace(ref.ProviderOrderID) == "" {
		return s.failedGooglePaymentPrice(ctx, ref, "google order id is missing")
	}
	order, err := s.googlePlay.GetOrder(ctx, ref.PackageName, ref.ProviderOrderID)
	if err != nil {
		return s.failedGooglePaymentPrice(ctx, ref, err.Error())
	}
	return s.refreshGooglePaymentPriceFromOrder(ctx, appCode, ref, order)
}

func (s *Service) refreshGooglePaymentPriceFromOrder(ctx context.Context, appCode string, ref ledger.GooglePaymentOrderRef, order ledger.GooglePlayOrder) GooglePaymentPriceResult {
	result := GooglePaymentPriceResult{TransactionID: ref.TransactionID}
	details := ledger.GooglePaymentPaidDetails{
		CurrencyCode:    order.CurrencyCode,
		PaidAmountMicro: order.TotalAmountMicro,
		TaxAmountMicro:  order.TaxAmountMicro,
		NetAmountMicro:  order.NetAmountMicro,
		SyncedAtMS:      s.now().UnixMilli(),
	}
	if err := s.repository.UpdateGooglePaymentPaidDetails(ctx, appCode, ref.PaymentOrderID, details); err != nil {
		return s.failedGooglePaymentPrice(ctx, ref, err.Error())
	}
	result.CurrencyCode = details.CurrencyCode
	result.PaidAmountMicro = details.PaidAmountMicro
	result.TaxAmountMicro = details.TaxAmountMicro
	result.NetAmountMicro = details.NetAmountMicro
	result.SyncedAtMS = details.SyncedAtMS
	return result
}

func (s *Service) failedGooglePaymentPrice(ctx context.Context, ref ledger.GooglePaymentOrderRef, message string) GooglePaymentPriceResult {
	result := GooglePaymentPriceResult{TransactionID: ref.TransactionID, Err: strings.TrimSpace(message)}
	if err := s.repository.MarkGooglePaymentPaidSyncFailure(ctx, ref, result.Err, s.now().UnixMilli()); err != nil {
		logx.Warn(ctx, "google_paid_sync_failure_persist_failed",
			slog.String("app_code", ref.AppCode),
			slog.String("payment_order_id", ref.PaymentOrderID),
			slog.String("error", err.Error()),
		)
	}
	return result
}

// ProcessGooglePaymentPaidSyncBatch is the durable worker entrypoint. It uses batchGet for the common
// path and bounded single-order fallback when Google rejects a batch containing one stale order ID.
func (s *Service) ProcessGooglePaymentPaidSyncBatch(ctx context.Context, batchSize int) (GooglePaymentPaidSyncBatchResult, error) {
	if s.repository == nil {
		return GooglePaymentPaidSyncBatchResult{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	if s.googlePlay == nil {
		return GooglePaymentPaidSyncBatchResult{}, xerr.New(xerr.Unavailable, "google play client is not configured")
	}
	refs, err := s.repository.ListPendingGooglePaymentOrderRefs(ctx, batchSize, s.now().UnixMilli())
	if err != nil {
		return GooglePaymentPaidSyncBatchResult{}, err
	}
	result := GooglePaymentPaidSyncBatchResult{Claimed: len(refs)}
	if len(refs) == 0 {
		return result, nil
	}
	prices := s.refreshGooglePaymentRefBatch(ctx, refs)
	for _, price := range prices {
		if price.Err == "" {
			result.Succeeded++
		} else {
			result.Failed++
		}
	}
	return result, nil
}

func (s *Service) refreshGooglePaymentRefBatch(ctx context.Context, refs []ledger.GooglePaymentOrderRef) []GooglePaymentPriceResult {
	results := make([]GooglePaymentPriceResult, len(refs))
	groups := make(map[string][]int)
	for index, ref := range refs {
		groups[strings.TrimSpace(ref.PackageName)] = append(groups[strings.TrimSpace(ref.PackageName)], index)
	}
	batchClient, supportsBatch := s.googlePlay.(googlePlayOrdersBatchClient)
	for packageName, indexes := range groups {
		if supportsBatch && packageName != "" {
			orderIDs := make([]string, 0, len(indexes))
			for _, index := range indexes {
				if orderID := strings.TrimSpace(refs[index].ProviderOrderID); orderID != "" {
					orderIDs = append(orderIDs, orderID)
				}
			}
			if len(orderIDs) == len(indexes) {
				if orders, err := batchClient.GetOrders(ctx, packageName, orderIDs); err == nil {
					for _, index := range indexes {
						ref := refs[index]
						order, ok := orders[ref.ProviderOrderID]
						if !ok {
							results[index] = s.failedGooglePaymentPrice(ctx, ref, "google order is not found")
							continue
						}
						results[index] = s.refreshGooglePaymentPriceFromOrder(ctx, ref.AppCode, ref, order)
					}
					continue
				}
			}
		}
		s.refreshGooglePaymentRefSingles(ctx, refs, indexes, results)
	}
	return results
}

func (s *Service) refreshGooglePaymentRefSingles(ctx context.Context, refs []ledger.GooglePaymentOrderRef, indexes []int, results []GooglePaymentPriceResult) {
	const concurrency = 8
	semaphore := make(chan struct{}, concurrency)
	var wait sync.WaitGroup
	for _, index := range indexes {
		wait.Add(1)
		go func(index int) {
			defer wait.Done()
			select {
			case semaphore <- struct{}{}:
				defer func() { <-semaphore }()
			case <-ctx.Done():
				results[index] = s.failedGooglePaymentPrice(ctx, refs[index], ctx.Err().Error())
				return
			}
			results[index] = s.refreshGooglePaymentPrice(ctx, refs[index].AppCode, refs[index])
		}(index)
	}
	wait.Wait()
}

// ListRechargeProducts 返回区域化充值档位；region_id 必须由 gateway 从 user-service 资料解析。
func (s *Service) ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error) {
	if userID <= 0 || regionID <= 0 {
		return nil, nil, xerr.New(xerr.InvalidArgument, "user_id and region_id are required")
	}
	if strings.TrimSpace(platform) != "" {
		platform = ledger.NormalizeRechargeProductPlatform(platform)
		if platform == "" {
			return nil, nil, xerr.New(xerr.InvalidArgument, "platform is invalid")
		}
	}
	audienceType = ledger.NormalizeRechargeAudienceType(audienceType)
	if audienceType == "" {
		return nil, nil, xerr.New(xerr.InvalidArgument, "audience_type is invalid")
	}
	if s.repository == nil {
		return nil, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	ctx = appcode.WithContext(ctx, appcode.FromContext(ctx))
	return s.repository.ListRechargeProducts(ctx, userID, regionID, platform, audienceType)
}

// ListAdminRechargeProducts 返回后台配置列表；后台入口负责 RBAC 和审计。
func (s *Service) ListAdminRechargeProducts(ctx context.Context, query ledger.ListRechargeProductsQuery) ([]ledger.RechargeProduct, int64, error) {
	if s.repository == nil {
		return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	query.AppCode = appcode.Normalize(query.AppCode)
	if strings.TrimSpace(query.Status) != "" {
		query.Status = ledger.NormalizeRechargeProductStatus(query.Status)
		if query.Status == "" {
			return nil, 0, xerr.New(xerr.InvalidArgument, "status is invalid")
		}
	}
	if strings.TrimSpace(query.Platform) != "" {
		query.Platform = ledger.NormalizeRechargeProductPlatform(query.Platform)
		if query.Platform == "" {
			return nil, 0, xerr.New(xerr.InvalidArgument, "platform is invalid")
		}
	}
	if strings.TrimSpace(query.AudienceType) != "" {
		query.AudienceType = ledger.NormalizeRechargeAudienceType(query.AudienceType)
		if query.AudienceType == "" {
			return nil, 0, xerr.New(xerr.InvalidArgument, "audience_type is invalid")
		}
	}
	ctx = appcode.WithContext(ctx, query.AppCode)
	return s.repository.ListAdminRechargeProducts(ctx, query)
}

// CreateRechargeProduct 创建内购商品配置；首版充值资源只允许发 COIN。
func (s *Service) CreateRechargeProduct(ctx context.Context, command ledger.RechargeProductCommand) (ledger.RechargeProduct, error) {
	if s.repository == nil {
		return ledger.RechargeProduct{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	command.AppCode = appcode.Normalize(command.AppCode)
	if err := validateRechargeProductCommand(command, false); err != nil {
		return ledger.RechargeProduct{}, err
	}
	ctx = appcode.WithContext(ctx, command.AppCode)
	return s.repository.CreateRechargeProduct(ctx, command)
}

// UpdateRechargeProduct 覆盖内购商品配置和支持区域，避免状态散落在后台库。
func (s *Service) UpdateRechargeProduct(ctx context.Context, command ledger.RechargeProductCommand) (ledger.RechargeProduct, error) {
	if s.repository == nil {
		return ledger.RechargeProduct{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	command.AppCode = appcode.Normalize(command.AppCode)
	if err := validateRechargeProductCommand(command, true); err != nil {
		return ledger.RechargeProduct{}, err
	}
	ctx = appcode.WithContext(ctx, command.AppCode)
	return s.repository.UpdateRechargeProduct(ctx, command)
}

// DeleteRechargeProduct 删除尚未接入 provider order 的配置事实；订单实现后这里需要先做引用检查。
func (s *Service) DeleteRechargeProduct(ctx context.Context, appCode string, productID int64) error {
	if productID <= 0 {
		return xerr.New(xerr.InvalidArgument, "product_id is required")
	}
	if s.repository == nil {
		return xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	ctx = appcode.WithContext(ctx, appcode.Normalize(appCode))
	return s.repository.DeleteRechargeProduct(ctx, appcode.FromContext(ctx), productID)
}

// ConfirmGooglePayment 校验 Google Play purchase token，原子入账并记录支付审计。
func (s *Service) ConfirmGooglePayment(ctx context.Context, command ledger.GooglePaymentCommand) (ledger.GooglePaymentReceipt, error) {
	command.AppCode = appcode.Normalize(command.AppCode)
	command.CommandID = strings.TrimSpace(command.CommandID)
	command.ProductCode = strings.TrimSpace(command.ProductCode)
	command.PackageName = strings.TrimSpace(command.PackageName)
	command.PurchaseToken = strings.TrimSpace(command.PurchaseToken)
	command.OrderID = strings.TrimSpace(command.OrderID)
	if command.CommandID == "" || command.UserID <= 0 || command.RegionID <= 0 || command.PackageName == "" || command.PurchaseToken == "" || (command.ProductID <= 0 && command.ProductCode == "") {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.InvalidArgument, "google payment command is incomplete")
	}
	if len(command.CommandID) > 128 || len(command.PackageName) > 256 || len(command.PurchaseToken) > 4096 || len(command.OrderID) > 128 {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.InvalidArgument, "google payment command is too long")
	}
	if s.repository == nil {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
	}
	if s.googlePlay == nil {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.Unavailable, "google play client is not configured")
	}
	ctx = appcode.WithContext(ctx, command.AppCode)

	product, err := s.googleRechargeProductForPayment(ctx, command)
	if err != nil {
		return ledger.GooglePaymentReceipt{}, err
	}
	if product.Status != ledger.RechargeProductStatusActive || !product.Enabled {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.Conflict, "recharge product is not active")
	}
	if product.Platform != ledger.RechargeProductPlatformAndroid || product.Channel != ledger.RechargeChannelGoogle {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.InvalidArgument, "recharge product is not google play product")
	}
	if command.ProductCode != "" && command.ProductCode != product.ProductName && command.ProductCode != product.ProductCode {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.Conflict, "product_code does not match")
	}
	if !rechargeProductSupportsRegion(product, command.RegionID) {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.Conflict, "recharge product is not available in user region")
	}

	purchase, err := s.googlePlay.GetProductPurchase(ctx, command.PackageName, command.PurchaseToken)
	if err != nil {
		return ledger.GooglePaymentReceipt{}, err
	}
	if purchase.PackageName == "" {
		purchase.PackageName = command.PackageName
	}
	if purchase.PackageName != command.PackageName {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.Conflict, "package_name does not match")
	}
	if purchase.PurchaseState != ledger.GooglePurchaseStatePurchased {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.Conflict, "google purchase is not purchased")
	}
	// 旧 App 首充曾把钱包内部 product_code 当成确认参数；上面的兼容只负责放行历史入参，
	// 真正的 Google 商品事实仍以 purchase.ProductID 对齐 product_name，避免内部编码绕过支付商品校验。
	if purchase.ProductID != "" && purchase.ProductID != product.ProductName {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.Conflict, "google product_id does not match recharge product")
	}
	if command.OrderID != "" && purchase.OrderID != "" && command.OrderID != purchase.OrderID {
		return ledger.GooglePaymentReceipt{}, xerr.New(xerr.Conflict, "google order_id does not match")
	}

	receipt, err := s.repository.ConfirmGooglePayment(ctx, command, product, purchase)
	if err != nil {
		return ledger.GooglePaymentReceipt{}, err
	}
	// 入账已完成，实付明细只影响财务展示：先即时异步拉取；失败会持久化并由 15 秒 worker 补偿。
	s.scheduleGooglePaymentPriceRefresh(command.AppCode, receipt.PaymentOrderID, command.PackageName, purchase.OrderID)
	if receipt.ConsumeState == ledger.PaymentConsumeStateConsumed {
		return receipt, nil
	}
	consumeProductID := product.ProductCode
	if purchase.ProductID != "" {
		consumeProductID = purchase.ProductID
	}
	if err := s.googlePlay.ConsumeProduct(ctx, command.PackageName, consumeProductID, command.PurchaseToken); err != nil {
		return receipt, nil
	}
	if err := s.repository.MarkGooglePaymentConsumed(ctx, command.AppCode, receipt.PaymentOrderID); err != nil {
		return receipt, nil
	}
	receipt.ConsumeState = ledger.PaymentConsumeStateConsumed
	return receipt, nil
}

// scheduleGooglePaymentPriceRefresh 在支付确认返回后异步同步实付明细，不给支付主链路增加延迟或失败面。
func (s *Service) scheduleGooglePaymentPriceRefresh(appCode string, paymentOrderID string, packageName string, orderID string) {
	if s.googlePlay == nil || s.repository == nil ||
		strings.TrimSpace(paymentOrderID) == "" || strings.TrimSpace(orderID) == "" {
		return
	}
	go func() {
		ctx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), appCode), 10*time.Second)
		defer cancel()
		result := s.refreshGooglePaymentPrice(ctx, appCode, ledger.GooglePaymentOrderRef{
			AppCode:         appCode,
			PaymentOrderID:  paymentOrderID,
			PackageName:     packageName,
			ProviderOrderID: orderID,
		})
		if result.Err != "" {
			logx.Warn(ctx, "google_paid_sync_immediate_failed",
				slog.String("app_code", appCode),
				slog.String("payment_order_id", paymentOrderID),
				slog.String("error", result.Err),
			)
		}
	}()
}

func (s *Service) googleRechargeProductForPayment(ctx context.Context, command ledger.GooglePaymentCommand) (ledger.RechargeProduct, error) {
	if command.ProductID > 0 {
		return s.repository.GetRechargeProduct(ctx, command.AppCode, command.ProductID)
	}
	// 首充弹窗只持有 Google 商品 ID，不再依赖 App 充值商品列表返回 product_id；
	// 这里仍然回到钱包内部商品配置取币数、金额和区域，避免客户端自报充值规格。
	return s.repository.GetGoogleRechargeProductByCode(ctx, command.AppCode, command.ProductCode)
}

func validateRechargeProductCommand(command ledger.RechargeProductCommand, requireProductID bool) error {
	if requireProductID && command.ProductID <= 0 {
		return xerr.New(xerr.InvalidArgument, "product_id is required")
	}
	// 隐藏的 Google 首充壳商品可以只承载支付金额和 SKU，不直接给普通金币；
	// 是否发首充资源组由 activity-service 根据 WalletRechargeRecorded 的 google_product_id 决定。
	if command.AmountMicro <= 0 || command.CoinAmount < 0 || command.OperatorUserID <= 0 {
		return xerr.New(xerr.InvalidArgument, "recharge product amount and operator are required")
	}
	if strings.TrimSpace(command.ProductName) == "" || len([]rune(strings.TrimSpace(command.ProductName))) > 128 {
		return xerr.New(xerr.InvalidArgument, "product_name is invalid")
	}
	if len([]rune(strings.TrimSpace(command.Description))) > 512 {
		return xerr.New(xerr.InvalidArgument, "description is too long")
	}
	if ledger.NormalizeRechargeProductPlatform(command.Platform) == "" {
		return xerr.New(xerr.InvalidArgument, "platform is invalid")
	}
	if ledger.NormalizeRechargeAudienceType(command.AudienceType) == "" {
		return xerr.New(xerr.InvalidArgument, "audience_type is invalid")
	}
	if len(command.RegionIDs) == 0 {
		return xerr.New(xerr.InvalidArgument, "region_ids are required")
	}
	seen := make(map[int64]struct{}, len(command.RegionIDs))
	for _, regionID := range command.RegionIDs {
		if regionID <= 0 {
			return xerr.New(xerr.InvalidArgument, "region_id is invalid")
		}
		if _, ok := seen[regionID]; ok {
			return xerr.New(xerr.InvalidArgument, "region_id is duplicated")
		}
		seen[regionID] = struct{}{}
	}
	return nil
}

func rechargeProductSupportsRegion(product ledger.RechargeProduct, regionID int64) bool {
	for _, productRegionID := range product.RegionIDs {
		if productRegionID == regionID {
			return true
		}
	}
	return false
}
