package payment

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"sync"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/response"
	walletv1 "hyapp.local/api/proto/wallet/v1"

	"github.com/gin-gonic/gin"
)

type rechargeBillDailyBucketDTO struct {
	Date                 string `json:"date"`
	GoogleUsdMinor       int64  `json:"googleUsdMinor"`
	ThirdPartyUsdMinor   int64  `json:"thirdPartyUsdMinor"`
	CoinSellerUsdMinor   int64  `json:"coinSellerUsdMinor"`
	GoogleCoinAmount     int64  `json:"googleCoinAmount"`
	ThirdPartyCoinAmount int64  `json:"thirdPartyCoinAmount"`
	CoinSellerCoinAmount int64  `json:"coinSellerCoinAmount"`
}

type rechargeBillRegionBucketDTO struct {
	RegionID       int64  `json:"regionId"`
	Name           string `json:"name"`
	BillCount      int64  `json:"billCount"`
	UsdMinorAmount int64  `json:"usdMinorAmount"`
}

type rechargeBillGooglePaidStatsDTO struct {
	GoogleBillCount int64 `json:"googleBillCount"`
	SyncedCount     int64 `json:"syncedCount"`
	UnsyncedCount   int64 `json:"unsyncedCount"`
	CoveredUsdMinor int64 `json:"coveredUsdMinor"`
	EstFeeUsdMinor  int64 `json:"estFeeUsdMinor"`
	EstTaxUsdMinor  int64 `json:"estTaxUsdMinor"`
	EstNetUsdMinor  int64 `json:"estNetUsdMinor"`
}

// rechargeBillWithdrawalStatsDTO 是“用户提现”合并口径：工资转币商（钱包账本）+ 审核通过的提现申请（admin 库）。
type rechargeBillWithdrawalStatsDTO struct {
	TransferCount    int64 `json:"transferCount"`
	TransferUsdMinor int64 `json:"transferUsdMinor"`
	ApprovedCount    int64 `json:"approvedCount"`
	ApprovedUsdMinor int64 `json:"approvedUsdMinor"`
	TotalUsdMinor    int64 `json:"totalUsdMinor"`
}

type rechargeBillOverviewDTO struct {
	Daily      []rechargeBillDailyBucketDTO   `json:"daily"`
	Regions    []rechargeBillRegionBucketDTO  `json:"regions"`
	GooglePaid rechargeBillGooglePaidStatsDTO `json:"googlePaid"`
	Withdrawal rechargeBillWithdrawalStatsDTO `json:"withdrawal"`
}

// FinanceRechargeOverviewQuery 是供其他后台模块复用财务充值口径的只读查询。
// ScopeRegionIDs 非空时只汇总这些区域，避免数据范围受限的 BI 查询先读全量再裁剪。
type FinanceRechargeOverviewQuery struct {
	AppCode         string
	StartAtMS       int64
	EndAtMS         int64
	ScopeRegionIDs  []int64
	IncludeRegions  bool
	TZOffsetMinutes int32
}

// FinanceRechargeDailyBucket 与 Finance 页面趋势使用同一组渠道字段和切日口径。
type FinanceRechargeDailyBucket struct {
	Date               string
	GoogleUSDMinor     int64
	ThirdPartyUSDMinor int64
	CoinSellerUSDMinor int64
}

// FinanceRechargeRegionOverview 在全量查询时只返回 Finance 页面已有的区域总额；
// 仅显式区域范围查询才补齐逐日渠道，避免默认概览对所有区域做 N+1 聚合。
type FinanceRechargeRegionOverview struct {
	RegionID       int64
	TotalUSDMinor  int64
	DailyAvailable bool
	Daily          []FinanceRechargeDailyBucket
}

type FinanceRechargeOverview struct {
	Daily   []FinanceRechargeDailyBucket
	Regions []FinanceRechargeRegionOverview
}

// FinanceRechargeOverviewSource 让 Social BI 依赖 Finance 的业务口径，而不是依赖 payment HTTP handler。
type FinanceRechargeOverviewSource interface {
	FinanceRechargeOverview(context.Context, FinanceRechargeOverviewQuery) (FinanceRechargeOverview, error)
}

// GetRechargeBillOverview 返回财务概览聚合：按日趋势、区域分布与谷歌实付覆盖度；筛选口径与账单列表一致。
func (h *Handler) GetRechargeBillOverview(c *gin.Context) {
	options := shared.ListOptions(c)
	appCode := appctx.FromContext(c.Request.Context())
	tzOffsetMinutes := int32(queryInt64(c, "tz_offset_minutes", "tzOffsetMinutes"))
	if tzOffsetMinutes == 0 {
		// 财务系统统一按中国时区切日边界。
		tzOffsetMinutes = 480
	}
	startAtMS := queryInt64(c, "start_at_ms", "startAtMs")
	endAtMS := queryInt64(c, "end_at_ms", "endAtMs")
	userFilter := shared.UserIdentityFilterFromQuery(c, "")
	userID, userMatched, ok := h.resolveRechargeBillUserFilter(c, appCode, userFilter, "user_id 参数不正确", "user_id", "userId")
	if !ok {
		return
	}
	sellerFilter := shared.UserIdentityFilterFromQuery(c, "seller")
	sellerUserID, sellerMatched, ok := h.resolveRechargeBillUserFilter(c, appCode, sellerFilter, "seller_user_id 参数不正确", "seller_user_id", "sellerUserId")
	if !ok {
		return
	}
	if (!userFilter.IsEmpty() && !userMatched) || (!sellerFilter.IsEmpty() && !sellerMatched) {
		response.OK(c, rechargeBillOverviewDTO{Daily: []rechargeBillDailyBucketDTO{}, Regions: []rechargeBillRegionBucketDTO{}})
		return
	}
	financeQuery := financeCoinSellerRechargeQuery{
		RechargeType:      strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
		Status:            options.Status,
		PaidState:         strings.TrimSpace(firstQuery(c, "paid_state", "paidState")),
		Keyword:           options.Keyword,
		RegionID:          queryInt64(c, "region_id", "regionId"),
		StartAtMS:         startAtMS,
		EndAtMS:           endAtMS,
		TzOffsetMinutes:   tzOffsetMinutes,
		TargetUserID:      sellerUserID,
		HasUserOnlyFilter: userID > 0 && sellerUserID == 0,
		RequestID:         middleware.CurrentRequestID(c),
		UserID:            userID,
	}
	// HTTP 页面和 Social BI 都走同一个装配函数；后续口径调整只允许在这里发生一次。
	overview, err := h.loadFinanceRechargeOverview(c.Request.Context(), appCode, financeQuery, userFilter.IsEmpty() && sellerFilter.IsEmpty(), true)
	if err != nil {
		writeWalletError(c, err, "获取财务概览失败")
		return
	}
	h.fillApprovedWithdrawalStats(&overview, appCode, startAtMS, endAtMS)
	response.OK(c, overview)
}

// FinanceRechargeOverview 暴露 Finance 页面自身使用的充值聚合，供 Social BI 复用。
// 默认只复用 Finance 已经返回的全局逐日与区域总额；显式区域筛选才按区域补逐日渠道。
func (h *Handler) FinanceRechargeOverview(ctx context.Context, query FinanceRechargeOverviewQuery) (FinanceRechargeOverview, error) {
	appCode := appctx.Normalize(query.AppCode)
	if appCode == "" {
		return FinanceRechargeOverview{}, fmt.Errorf("app_code is required")
	}
	tzOffsetMinutes := query.TZOffsetMinutes
	if tzOffsetMinutes == 0 {
		tzOffsetMinutes = 480
	}
	regionIDs := uniquePositiveInt64s(query.ScopeRegionIDs)
	baseQuery := financeCoinSellerRechargeQuery{
		StartAtMS:       query.StartAtMS,
		EndAtMS:         query.EndAtMS,
		TzOffsetMinutes: tzOffsetMinutes,
	}

	if len(regionIDs) > 0 {
		regions, err := h.loadFinanceRechargeRegions(ctx, appCode, baseQuery, regionIDs)
		if err != nil {
			return FinanceRechargeOverview{}, err
		}
		return FinanceRechargeOverview{Daily: mergeFinanceRechargeRegionDaily(regions), Regions: regions}, nil
	}

	overview, err := h.loadFinanceRechargeOverview(ctx, appCode, baseQuery, true, query.IncludeRegions)
	if err != nil {
		return FinanceRechargeOverview{}, err
	}
	result := FinanceRechargeOverview{Daily: financeRechargeDailyBuckets(overview.Daily)}
	if !query.IncludeRegions {
		return result, nil
	}
	for _, region := range overview.Regions {
		if region.RegionID <= 0 {
			continue
		}
		result.Regions = append(result.Regions, FinanceRechargeRegionOverview{
			RegionID:      region.RegionID,
			TotalUSDMinor: region.UsdMinorAmount,
		})
	}
	sort.Slice(result.Regions, func(i, j int) bool { return result.Regions[i].RegionID < result.Regions[j].RegionID })
	return result, nil
}

func (h *Handler) loadFinanceRechargeRegions(ctx context.Context, appCode string, baseQuery financeCoinSellerRechargeQuery, regionIDs []int64) ([]FinanceRechargeRegionOverview, error) {
	type regionResult struct {
		region FinanceRechargeRegionOverview
		err    error
	}
	results := make(chan regionResult, len(regionIDs))
	semaphore := make(chan struct{}, 4)
	var wg sync.WaitGroup
	for _, regionID := range uniquePositiveInt64s(regionIDs) {
		regionID := regionID
		wg.Add(1)
		go func() {
			defer wg.Done()
			semaphore <- struct{}{}
			defer func() { <-semaphore }()
			regionQuery := baseQuery
			regionQuery.RegionID = regionID
			// 单区域默认趋势已经同时返回 Google/三方渠道；这里不再额外查询两次渠道区域分布。
			overview, err := h.loadFinanceRechargeOverview(ctx, appCode, regionQuery, true, false)
			daily := financeRechargeDailyBuckets(overview.Daily)
			results <- regionResult{
				region: FinanceRechargeRegionOverview{
					RegionID:       regionID,
					TotalUSDMinor:  financeRechargeDailyTotal(daily),
					DailyAvailable: true,
					Daily:          daily,
				},
				err: err,
			}
		}()
	}
	wg.Wait()
	close(results)

	regions := make([]FinanceRechargeRegionOverview, 0, len(regionIDs))
	for result := range results {
		if result.err != nil {
			return nil, result.err
		}
		regions = append(regions, result.region)
	}
	sort.Slice(regions, func(i, j int) bool { return regions[i].RegionID < regions[j].RegionID })
	return regions, nil
}

func financeRechargeDailyTotal(rows []FinanceRechargeDailyBucket) int64 {
	var total int64
	for _, row := range rows {
		total += row.GoogleUSDMinor + row.ThirdPartyUSDMinor + row.CoinSellerUSDMinor
	}
	return total
}

// loadFinanceRechargeOverview 是 Finance HTTP 页面与模块内复用入口的唯一装配路径。
// ordinary 与币商统计仍分别由 wallet/legacy source 和 financeCoinSellerRechargeStats 提供，禁止回退旧库存币商口径。
func (h *Handler) loadFinanceRechargeOverview(ctx context.Context, appCode string, query financeCoinSellerRechargeQuery, includeOrdinary bool, includeRegions bool) (rechargeBillOverviewDTO, error) {
	overview := rechargeBillOverviewDTO{Daily: []rechargeBillDailyBucketDTO{}, Regions: []rechargeBillRegionBucketDTO{}}
	if isFinanceCoinSellerRechargeType(query.RechargeType) {
		// coin_seller 及其三方/USDT 子来源都由 admin 台账、币商 H5 支付单和 legacy FREIGHT_GOLD 事实重建；
		// 不再把细分值透传给普通充值源，否则 legacy 未识别的新值会退化成“全部普通充值”。
		includeOrdinary = false
	}
	if source, ok := h.billSources[appCode]; ok {
		if includeOrdinary {
			var err error
			overview, err = source.Overview(ctx, legacyRechargeBillQuery{
				Keyword:      query.Keyword,
				RechargeType: query.RechargeType,
				PaidState:    query.PaidState,
				RegionID:     query.RegionID,
				StartAtMS:    query.StartAtMS,
				EndAtMS:      query.EndAtMS,
			}, query.TzOffsetMinutes)
			if err != nil {
				return rechargeBillOverviewDTO{}, err
			}
		}
		if err := h.applyFinanceCoinSellerOverview(ctx, appCode, query, &overview); err != nil {
			return rechargeBillOverviewDTO{}, err
		}
		return overview, nil
	}

	if includeOrdinary && financeWalletOverviewSupportsOrdinaryFilters(query) {
		if h.wallet == nil {
			return rechargeBillOverviewDTO{}, fmt.Errorf("wallet recharge overview is not configured")
		}
		resp, err := h.wallet.GetRechargeBillOverview(ctx, &walletv1.GetRechargeBillOverviewRequest{
			RequestId:       query.RequestID,
			AppCode:         appCode,
			RegionId:        query.RegionID,
			RechargeType:    query.RechargeType,
			Status:          query.Status,
			StartAtMs:       query.StartAtMS,
			EndAtMs:         query.EndAtMS,
			TzOffsetMinutes: query.TzOffsetMinutes,
		})
		if err != nil {
			return rechargeBillOverviewDTO{}, err
		}
		overview.Daily = make([]rechargeBillDailyBucketDTO, 0, len(resp.GetDaily()))
		for _, bucket := range resp.GetDaily() {
			overview.Daily = append(overview.Daily, rechargeBillDailyBucketDTO{
				Date:                 bucket.GetDate(),
				GoogleUsdMinor:       bucket.GetGoogleUsdMinor(),
				ThirdPartyUsdMinor:   bucket.GetThirdPartyUsdMinor(),
				CoinSellerUsdMinor:   bucket.GetCoinSellerUsdMinor(),
				GoogleCoinAmount:     bucket.GetGoogleCoinAmount(),
				ThirdPartyCoinAmount: bucket.GetThirdPartyCoinAmount(),
				CoinSellerCoinAmount: bucket.GetCoinSellerCoinAmount(),
			})
		}
		if stats := resp.GetGooglePaid(); stats != nil {
			overview.GooglePaid = rechargeBillGooglePaidStatsDTO{
				GoogleBillCount: stats.GetGoogleBillCount(),
				SyncedCount:     stats.GetSyncedCount(),
				UnsyncedCount:   stats.GetUnsyncedCount(),
				CoveredUsdMinor: stats.GetCoveredUsdMinor(),
				EstFeeUsdMinor:  stats.GetEstFeeUsdMinor(),
				EstTaxUsdMinor:  stats.GetEstTaxUsdMinor(),
				EstNetUsdMinor:  stats.GetEstNetUsdMinor(),
			}
		}
		if transfer := resp.GetSalaryTransfer(); transfer != nil {
			overview.Withdrawal.TransferCount = transfer.GetTransferCount()
			overview.Withdrawal.TransferUsdMinor = transfer.GetTransferUsdMinor()
		}
	}
	if includeOrdinary && includeRegions && normalizedRechargeType(query.RechargeType) == "" {
		ordinaryRegions, err := h.walletOrdinaryRechargeOverviewRegions(ctx, appCode, query)
		if err != nil {
			return rechargeBillOverviewDTO{}, err
		}
		overview.Regions = h.fillFinanceCoinSellerRegionNames(ctx, appCode, ordinaryRegions)
	}
	if err := h.applyFinanceCoinSellerOverview(ctx, appCode, query, &overview); err != nil {
		return rechargeBillOverviewDTO{}, err
	}
	return overview, nil
}

func financeRechargeDailyBuckets(rows []rechargeBillDailyBucketDTO) []FinanceRechargeDailyBucket {
	out := make([]FinanceRechargeDailyBucket, 0, len(rows))
	for _, row := range rows {
		if strings.TrimSpace(row.Date) == "" {
			continue
		}
		out = append(out, FinanceRechargeDailyBucket{
			Date:               row.Date,
			GoogleUSDMinor:     row.GoogleUsdMinor,
			ThirdPartyUSDMinor: row.ThirdPartyUsdMinor,
			CoinSellerUSDMinor: row.CoinSellerUsdMinor,
		})
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Date < out[j].Date })
	return out
}

func mergeFinanceRechargeRegionDaily(regions []FinanceRechargeRegionOverview) []FinanceRechargeDailyBucket {
	byDay := map[string]FinanceRechargeDailyBucket{}
	for _, region := range regions {
		for _, row := range region.Daily {
			current := byDay[row.Date]
			current.Date = row.Date
			current.GoogleUSDMinor += row.GoogleUSDMinor
			current.ThirdPartyUSDMinor += row.ThirdPartyUSDMinor
			current.CoinSellerUSDMinor += row.CoinSellerUSDMinor
			byDay[row.Date] = current
		}
	}
	out := make([]FinanceRechargeDailyBucket, 0, len(byDay))
	for _, row := range byDay {
		out = append(out, row)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Date < out[j].Date })
	return out
}

func uniquePositiveInt64s(values []int64) []int64 {
	seen := map[int64]struct{}{}
	out := make([]int64, 0, len(values))
	for _, value := range values {
		if value <= 0 {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		out = append(out, value)
	}
	sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
	return out
}

func (h *Handler) requireLegacyCoinSellerDayRange(c *gin.Context, query legacyRechargeBillQuery, tzOffsetMinutes int32) bool {
	if !legacyCoinSellerAggregateRequested(query) || !legacyCoinSellerAggregateFilterSupported(query) {
		return true
	}
	if _, _, _, err := legacyDashboardDayRange(query, tzOffsetMinutes); err != nil {
		response.BadRequest(c, "币商统计仅支持明确整日时间范围")
		return false
	}
	return true
}

// fillApprovedWithdrawalStats 把 admin 库的“审核通过提现申请”并入用户提现口径；查询失败只降级该卡片，不阻断概览。
func (h *Handler) fillApprovedWithdrawalStats(overview *rechargeBillOverviewDTO, appCode string, startAtMS int64, endAtMS int64) {
	if h.store != nil {
		if stats, err := h.store.ApprovedWithdrawalStats(appCode, startAtMS, endAtMS); err == nil {
			overview.Withdrawal.ApprovedCount = stats.ApprovedCount
			overview.Withdrawal.ApprovedUsdMinor = stats.ApprovedUSDMinor
		}
	}
	overview.Withdrawal.TotalUsdMinor = overview.Withdrawal.TransferUsdMinor + overview.Withdrawal.ApprovedUsdMinor
}
