package payment

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

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/config"
	"hyapp-admin-server/internal/repository"

	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

// financePaidCoinSellerRechargeSource 只暴露支付系统已经成功落单的币商充值事实。
// WebConsole 手工填写的 USDT/金币和 legacy 钱包发货流水不实现这个接口，避免运营输入进入财务口径。
type financePaidCoinSellerRechargeSource interface {
	financePaidCoinSellerRechargeStats(context.Context, financeCoinSellerRechargeQuery) (financeCoinSellerRechargeStats, error)
	listFinancePaidCoinSellerRechargeBills(context.Context, financeCoinSellerRechargeQuery, int) ([]rechargeBillDTO, int64, error)
}

func (h *Handler) financePaidCoinSellerRechargeSource(appCode string) financePaidCoinSellerRechargeSource {
	if h == nil || appctx.Normalize(appCode) != "aslan" {
		return nil
	}
	// 目前只有 Aslan legacy H5 的 FREIGHT_GOLD 真实支付单需要从 Mongo 补齐；
	// 其他 App 继续使用自己的已校验后台订单或新 wallet H5 订单，避免无依据扩大旧平台口径。
	source, ok := h.billSources["aslan"]
	if !ok || source == nil {
		return nil
	}
	paidSource, _ := source.(financePaidCoinSellerRechargeSource)
	return paidSource
}

// financePaidCoinSellerFilter 只匹配有支付订单号、支付渠道、正数美元金额且已经成功的 FREIGHT_GOLD 单。
// 这组字段来自 in_app_purchase_details 支付事实，不读取 user_freight_balance_running_water 的发货金额。
func (s *MongoRechargeBillSource) financePaidCoinSellerFilter(ctx context.Context, query financeCoinSellerRechargeQuery) (bson.M, []string, bool, error) {
	if s == nil || s.collection == nil || appctx.Normalize(s.config.AppCode) != "aslan" {
		return nil, nil, false, nil
	}
	filter := bson.M{
		"sysOrigin":           s.config.SysOrigin,
		"status":              "SUCCESS",
		"trialPeriod":         bson.M{"$ne": true},
		"amountUsd":           bson.M{"$gt": 0},
		"orderId":             bson.M{"$type": "string", "$ne": ""},
		"factory.factoryCode": bson.M{"$type": "string", "$ne": ""},
	}
	// Legacy 支付厂商码带下划线；按已确认的两个历史写法等值匹配，避免正则扫描，也保证厂商筛选不会混入其他三方。
	if rechargeSource, ok := financeCoinSellerRechargeSource(query.RechargeType); ok {
		switch rechargeSource {
		case repository.CoinSellerRechargeSourceMiFaPay:
			filter["factory.factoryCode"] = bson.M{"$in": bson.A{"MIFA_PAY", "MIFAPAY"}}
		case repository.CoinSellerRechargeSourceV5Pay:
			filter["factory.factoryCode"] = bson.M{"$in": bson.A{"V5_PAY", "V5PAY"}}
		}
	}
	conditions := bson.A{bson.M{"$or": legacyFreightCommodityMatchConditions()}}
	if query.TargetUserID > 0 {
		filter["acceptUserId"] = query.TargetUserID
	}
	if keyword := strings.TrimSpace(query.Keyword); keyword != "" {
		keywordConditions := bson.A{
			bson.M{"_id": keyword},
			bson.M{"orderId": keyword},
		}
		if userID, err := strconv.ParseInt(keyword, 10, 64); err == nil && userID > 0 {
			keywordConditions = append(keywordConditions, bson.M{"_id": userID}, bson.M{"acceptUserId": userID})
		}
		conditions = append(conditions, bson.M{"$or": keywordConditions})
	}
	paidAt := legacyPaidCoinSellerAtExpression()
	timeConditions := bson.A{}
	if query.StartAtMS > 0 {
		timeConditions = append(timeConditions, bson.M{"$gte": bson.A{paidAt, time.UnixMilli(query.StartAtMS)}})
	}
	if query.EndAtMS > 0 {
		timeConditions = append(timeConditions, bson.M{"$lt": bson.A{paidAt, time.UnixMilli(query.EndAtMS)}})
	}
	if len(timeConditions) == 1 {
		conditions = append(conditions, bson.M{"$expr": timeConditions[0]})
	} else if len(timeConditions) > 1 {
		conditions = append(conditions, bson.M{"$expr": bson.M{"$and": timeConditions}})
	}
	filter["$and"] = conditions

	if query.RegionID <= 0 {
		return filter, nil, true, nil
	}
	codes, handled, err := s.regionCountryCodes(ctx, query.RegionID)
	if err != nil {
		return nil, nil, false, err
	}
	if !handled || len(codes) == 0 {
		return nil, nil, false, nil
	}
	return filter, codes, true, nil
}

func legacyFreightCommodityMatchConditions() bson.A {
	conditions := make(bson.A, 0, len(legacyFreightCommodityFields))
	for _, field := range legacyFreightCommodityFields {
		conditions = append(conditions, bson.M{field: bson.M{"$in": legacyFreightCommodityTypes}})
	}
	return conditions
}

// updateTime 是支付回调把订单推进 SUCCESS 的时间；旧单缺失时才依次回退 purchaseDateMs/createTime。
func legacyPaidCoinSellerAtExpression() bson.M {
	return bson.M{"$ifNull": bson.A{
		"$updateTime",
		bson.M{"$ifNull": bson.A{"$purchaseDateMs", "$createTime"}},
	}}
}

func legacyFreightCoinAggregateExpression() bson.M {
	typedTotal := bson.M{"$reduce": bson.M{
		"input":        bson.M{"$ifNull": bson.A{"$products", bson.A{}}},
		"initialValue": int64(0),
		"in": bson.M{"$add": bson.A{
			"$$value",
			bson.M{"$cond": bson.A{
				bson.M{"$or": bson.A{
					bson.M{"$in": bson.A{"$$this.code", legacyFreightCommodityTypes}},
					bson.M{"$in": bson.A{"$$this.name", legacyFreightCommodityTypes}},
				}},
				bson.M{"$convert": bson.M{"input": "$$this.content", "to": "long", "onError": int64(0), "onNull": int64(0)}},
				int64(0),
			}},
		}},
	}}
	allNumericTotal := bson.M{"$reduce": bson.M{
		"input":        bson.M{"$ifNull": bson.A{"$products", bson.A{}}},
		"initialValue": int64(0),
		"in": bson.M{"$add": bson.A{
			"$$value",
			bson.M{"$convert": bson.M{"input": "$$this.content", "to": "long", "onError": int64(0), "onNull": int64(0)}},
		}},
	}}
	return bson.M{"$let": bson.M{
		"vars": bson.M{"typedTotal": typedTotal, "allNumericTotal": allNumericTotal},
		"in": bson.M{"$cond": bson.A{
			bson.M{"$gt": bson.A{"$$typedTotal", 0}},
			"$$typedTotal",
			bson.M{"$cond": bson.A{
				bson.M{"$in": bson.A{"$trackCommodityType", legacyFreightCommodityTypes}},
				"$$allNumericTotal",
				int64(0),
			}},
		}},
	}}
}

func (s *MongoRechargeBillSource) financePaidCoinSellerRechargeStats(ctx context.Context, query financeCoinSellerRechargeQuery) (financeCoinSellerRechargeStats, error) {
	stats := financeCoinSellerRechargeStats{Daily: map[string]rechargeBillSummaryBucketDTO{}}
	filter, regionCodes, matchable, err := s.financePaidCoinSellerFilter(ctx, query)
	if err != nil || !matchable {
		return stats, err
	}
	base := mongo.Pipeline{bson.D{{Key: "$match", Value: filter}}}
	if len(regionCodes) > 0 {
		base = append(base, legacyRegionLookupStages(regionCodes)...)
	} else {
		base = append(base, legacyCountryResolveStages()...)
	}
	tzOffsetMinutes := query.TzOffsetMinutes
	if tzOffsetMinutes == 0 {
		tzOffsetMinutes = defaultLegacyFinanceTZOffsetMinutes
	}
	pipeline := append(base,
		bson.D{{Key: "$addFields", Value: bson.M{
			"financePaidAt":     legacyPaidCoinSellerAtExpression(),
			"financeCoinAmount": legacyFreightCoinAggregateExpression(),
		}}},
		bson.D{{Key: "$facet", Value: bson.M{
			"total": mongo.Pipeline{
				bson.D{{Key: "$group", Value: bson.M{
					"_id":       nil,
					"billCount": bson.M{"$sum": 1},
					"usd":       bson.M{"$sum": bson.M{"$ifNull": bson.A{"$amountUsd", 0}}},
					"coin":      bson.M{"$sum": "$financeCoinAmount"},
				}}},
			},
			"daily": mongo.Pipeline{
				bson.D{{Key: "$group", Value: bson.M{
					"_id": bson.M{"$dateToString": bson.M{
						"format":   "%Y-%m-%d",
						"date":     "$financePaidAt",
						"timezone": legacyTimezoneOffset(tzOffsetMinutes),
					}},
					"billCount": bson.M{"$sum": 1},
					"usd":       bson.M{"$sum": bson.M{"$ifNull": bson.A{"$amountUsd", 0}}},
					"coin":      bson.M{"$sum": "$financeCoinAmount"},
				}}},
			},
			"regions": mongo.Pipeline{
				bson.D{{Key: "$group", Value: bson.M{
					"_id":       "$legacyBillCountry",
					"billCount": bson.M{"$sum": 1},
					"usd":       bson.M{"$sum": bson.M{"$ifNull": bson.A{"$amountUsd", 0}}},
				}}},
			},
		}}},
	)
	cursor, err := s.collection.Aggregate(ctx, pipeline)
	if err != nil {
		return stats, fmt.Errorf("aggregate Aslan paid coin seller bills: %w", err)
	}
	defer cursor.Close(ctx)
	type bucketRow struct {
		Date      string `bson:"_id"`
		BillCount int64  `bson:"billCount"`
		USD       any    `bson:"usd"`
		Coin      int64  `bson:"coin"`
	}
	type regionRow struct {
		Country   string `bson:"_id"`
		BillCount int64  `bson:"billCount"`
		USD       any    `bson:"usd"`
	}
	var result struct {
		Total   []bucketRow `bson:"total"`
		Daily   []bucketRow `bson:"daily"`
		Regions []regionRow `bson:"regions"`
	}
	if cursor.Next(ctx) {
		if err := cursor.Decode(&result); err != nil {
			return stats, fmt.Errorf("decode Aslan paid coin seller stats: %w", err)
		}
	}
	if err := cursor.Err(); err != nil {
		return stats, fmt.Errorf("iterate Aslan paid coin seller stats: %w", err)
	}
	if len(result.Total) > 0 {
		stats.addBucket(rechargeBillSummaryBucketDTO{
			BillCount:      result.Total[0].BillCount,
			CoinAmount:     result.Total[0].Coin,
			USDMinorAmount: legacyDecimalToMinor(result.Total[0].USD),
		})
	}
	for _, row := range result.Daily {
		stats.addDaily(row.Date, rechargeBillSummaryBucketDTO{
			BillCount:      row.BillCount,
			CoinAmount:     row.Coin,
			USDMinorAmount: legacyDecimalToMinor(row.USD),
		})
	}
	countryToRegion := s.legacyCountryRegionIndex(ctx)
	for _, row := range result.Regions {
		region, ok := countryToRegion[strings.ToUpper(strings.TrimSpace(row.Country))]
		if !ok {
			continue
		}
		stats.addRegion(region.RegionID, row.BillCount, legacyDecimalToMinor(row.USD))
	}
	return stats, nil
}

func (s *MongoRechargeBillSource) listFinancePaidCoinSellerRechargeBills(ctx context.Context, query financeCoinSellerRechargeQuery, limit int) ([]rechargeBillDTO, int64, error) {
	filter, regionCodes, matchable, err := s.financePaidCoinSellerFilter(ctx, query)
	if err != nil || !matchable {
		return []rechargeBillDTO{}, 0, err
	}
	base := mongo.Pipeline{bson.D{{Key: "$match", Value: filter}}}
	var total int64
	if len(regionCodes) == 0 {
		total, err = s.collection.CountDocuments(ctx, filter)
	} else {
		base = append(base, legacyRegionLookupStages(regionCodes)...)
		countCursor, countErr := s.collection.Aggregate(ctx, append(clonePipeline(base), bson.D{{Key: "$count", Value: "n"}}))
		if countErr != nil {
			return nil, 0, fmt.Errorf("count Aslan paid coin seller bills by region: %w", countErr)
		}
		if countCursor.Next(ctx) {
			var row struct {
				N int64 `bson:"n"`
			}
			if decodeErr := countCursor.Decode(&row); decodeErr != nil {
				countCursor.Close(ctx)
				return nil, 0, fmt.Errorf("decode Aslan paid coin seller bill count: %w", decodeErr)
			}
			total = row.N
		}
		if countErr := countCursor.Err(); countErr != nil {
			countCursor.Close(ctx)
			return nil, 0, fmt.Errorf("iterate Aslan paid coin seller bill count: %w", countErr)
		}
		countCursor.Close(ctx)
	}
	if err != nil {
		return nil, 0, fmt.Errorf("count Aslan paid coin seller bills: %w", err)
	}
	if total == 0 || limit < 1 {
		return []rechargeBillDTO{}, total, nil
	}
	base = append(base,
		bson.D{{Key: "$addFields", Value: bson.M{"financePaidAt": legacyPaidCoinSellerAtExpression()}}},
		bson.D{{Key: "$sort", Value: bson.D{{Key: "financePaidAt", Value: -1}, {Key: "_id", Value: -1}}}},
		bson.D{{Key: "$limit", Value: int64(limit)}},
	)
	if len(regionCodes) == 0 {
		// 无区域筛选时先分页再查用户国家，避免为整段历史订单执行 $lookup。
		base = append(base, legacyCountryResolveStages()...)
	}
	cursor, err := s.collection.Aggregate(ctx, base, options.Aggregate().SetAllowDiskUse(false))
	if err != nil {
		return nil, 0, fmt.Errorf("query Aslan paid coin seller bills: %w", err)
	}
	defer cursor.Close(ctx)
	countryToRegion := s.legacyCountryRegionIndex(ctx)
	items := make([]rechargeBillDTO, 0, limit)
	for cursor.Next(ctx) {
		var document legacyPurchaseDocument
		if err := cursor.Decode(&document); err != nil {
			return nil, 0, fmt.Errorf("decode Aslan paid coin seller bill: %w", err)
		}
		item := legacyPaidCoinSellerRechargeBillDTO(s.config, document)
		if region, ok := countryToRegion[strings.ToUpper(strings.TrimSpace(document.LegacyBillCountry))]; ok {
			item.SellerRegionID = region.RegionID
			item.TargetRegionID = region.RegionID
		}
		items = append(items, item)
	}
	if err := cursor.Err(); err != nil {
		return nil, 0, fmt.Errorf("iterate Aslan paid coin seller bills: %w", err)
	}
	return items, total, nil
}

func legacyPaidCoinSellerRechargeBillDTO(sourceConfig config.FinanceBillSourceConfig, document legacyPurchaseDocument) rechargeBillDTO {
	paidAtMS := legacyPaidCoinSellerAtMS(document)
	factoryCode := strings.ToUpper(strings.TrimSpace(document.Factory.FactoryCode))
	paidCurrency := strings.ToUpper(strings.TrimSpace(document.Currency))
	if paidCurrency == "" {
		paidCurrency = "USD"
	}
	return rechargeBillDTO{
		AppCode:       sourceConfig.AppCode,
		TransactionID: legacyDocumentID(document.ID),
		// Legacy H5 以支付单 factoryCode 还原具体厂商；未知历史值保留“币商充值-三方”聚合标签。
		RechargeType:         financeCoinSellerBillType(factoryCode),
		Status:               "succeeded",
		ExternalRef:          strings.TrimSpace(document.OrderID),
		SellerUserID:         document.AcceptUserID,
		CurrencyCode:         "USD",
		CoinAmount:           legacyFreightCoinAmount(document),
		USDMinorAmount:       legacyDecimalToMinor(document.AmountUSD),
		ProviderAmountMinor:  legacyDecimalToMinor(document.Amount),
		CreatedAtMS:          paidAtMS,
		ProviderCode:         legacyProviderCode(factoryCode),
		UserPaidCurrencyCode: paidCurrency,
		UserPaidAmountMicro:  legacyDecimalToMicro(document.Amount),
		PaidSyncedAtMS:       paidAtMS,
		PolicyVersion:        strings.ToUpper(strings.TrimSpace(document.TrackCommodityType)),
	}
}

func legacyPaidCoinSellerAtMS(document legacyPurchaseDocument) int64 {
	for _, value := range []time.Time{document.UpdateTime, document.PurchaseDateMS, document.CreateTime} {
		if !value.IsZero() {
			return value.UnixMilli()
		}
	}
	return 0
}

func legacyFreightCoinAmount(document legacyPurchaseDocument) int64 {
	var typedTotal int64
	var allNumericTotal int64
	for _, product := range document.Products {
		amount, err := strconv.ParseInt(strings.TrimSpace(product.Content), 10, 64)
		if err != nil || amount <= 0 {
			continue
		}
		allNumericTotal += amount
		if legacyFreightCommodityType(product.Code) || legacyFreightCommodityType(product.Name) {
			typedTotal += amount
		}
	}
	if typedTotal > 0 {
		return typedTotal
	}
	// 极老订单只在顶层 trackCommodityType 写类型；此时产品中的正数 content 才能作为该账单的发货金币。
	if legacyFreightCommodityType(document.TrackCommodityType) {
		return allNumericTotal
	}
	return 0
}

func legacyFreightCommodityType(value string) bool {
	switch strings.ToUpper(strings.TrimSpace(value)) {
	case "FREIGHT_GOLD", "FREIGHT_GOLD_SUPER":
		return true
	default:
		return false
	}
}
