package payment

import (
	"context"
	"sort"
	"strings"
	"time"

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

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

type rechargeBillSummaryBucketDTO struct {
	BillCount      int64 `json:"billCount"`
	CoinAmount     int64 `json:"coinAmount"`
	USDMinorAmount int64 `json:"usdMinorAmount"`
}

type rechargeBillSummaryDTO struct {
	Total      rechargeBillSummaryBucketDTO `json:"total"`
	GooglePlay rechargeBillSummaryBucketDTO `json:"googlePlay"`
	ThirdParty rechargeBillSummaryBucketDTO `json:"thirdParty"`
	CoinSeller rechargeBillSummaryBucketDTO `json:"coinSeller"`
}

type rechargeBillRegionDTO struct {
	RegionID   int64  `json:"regionId"`
	RegionCode string `json:"regionCode"`
	Name       string `json:"name"`
}

type googleRechargePaidRefreshRequest struct {
	TransactionIDs []string `json:"transactionIds"`
}

type googleRechargePaidDTO struct {
	TransactionID   string `json:"transactionId"`
	CurrencyCode    string `json:"currencyCode"`
	PaidAmountMicro int64  `json:"paidAmountMicro"`
	TaxMicro        int64  `json:"taxMicro"`
	NetMicro        int64  `json:"netMicro"`
	SyncedAtMS      int64  `json:"syncedAtMs"`
	Error           string `json:"error,omitempty"`
}

// listLegacyRechargeBills 处理 legacy App 的充值明细；分页与展示口径对齐 wallet-service 账单列表。
func (h *Handler) listLegacyRechargeBills(c *gin.Context, source RechargeBillSource) {
	options := shared.ListOptions(c)
	tzOffsetMinutes := int32(queryInt64(c, "tz_offset_minutes", "tzOffsetMinutes"))
	if tzOffsetMinutes == 0 {
		// legacy 币商充值列表可能来自 dashboard 自然日聚合，默认沿用财务页中国时区。
		tzOffsetMinutes = defaultLegacyFinanceTZOffsetMinutes
	}
	items, total, err := source.ListRechargeBills(c.Request.Context(), legacyRechargeBillQuery{
		Keyword:         options.Keyword,
		RechargeType:    strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
		PaidState:       strings.TrimSpace(firstQuery(c, "paid_state", "paidState")),
		RegionID:        queryInt64(c, "region_id", "regionId"),
		StartAtMS:       queryInt64(c, "start_at_ms", "startAtMs"),
		EndAtMS:         queryInt64(c, "end_at_ms", "endAtMs"),
		TzOffsetMinutes: tzOffsetMinutes,
		Page:            options.Page,
		PageSize:        options.PageSize,
	})
	if err != nil {
		response.ServerError(c, "获取账单列表失败")
		return
	}
	response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: total})
}

// GetRechargeBillSummary 返回与账单列表同一筛选口径的充值总和、Google 充值与三方充值聚合。
func (h *Handler) GetRechargeBillSummary(c *gin.Context) {
	options := shared.ListOptions(c)
	appCode := appctx.FromContext(c.Request.Context())
	tzOffsetMinutes := int32(queryInt64(c, "tz_offset_minutes", "tzOffsetMinutes"))
	if tzOffsetMinutes == 0 {
		// legacy finance 的币商补充来自 dashboard 自然日聚合，默认沿用财务页中国时区。
		tzOffsetMinutes = defaultLegacyFinanceTZOffsetMinutes
	}
	if source, ok := h.billSources[appCode]; ok {
		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, rechargeBillSummaryDTO{})
			return
		}
		query := legacyRechargeBillQuery{
			Keyword:         options.Keyword,
			RechargeType:    strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
			PaidState:       strings.TrimSpace(firstQuery(c, "paid_state", "paidState")),
			RegionID:        queryInt64(c, "region_id", "regionId"),
			StartAtMS:       queryInt64(c, "start_at_ms", "startAtMs"),
			EndAtMS:         queryInt64(c, "end_at_ms", "endAtMs"),
			TzOffsetMinutes: tzOffsetMinutes,
		}
		summary := rechargeBillSummaryDTO{}
		if !userFilter.IsEmpty() || !sellerFilter.IsEmpty() {
			// legacy 普通充值汇总没有 user/seller 过滤能力；有用户筛选时只保留后续可精确计算的财务币商桶。
		} else if !isFinanceCoinSellerRechargeType(query.RechargeType) {
			// 币商及其细分来源由下方财务币商聚合唯一负责；不让 legacy 源把未知子类型退化成普通充值全量查询。
			var err error
			summary, err = source.SummarizeRechargeBills(c.Request.Context(), query)
			if err != nil {
				response.ServerError(c, "获取充值汇总失败")
				return
			}
		}
		if err := h.applyFinanceCoinSellerSummary(c.Request.Context(), appCode, financeCoinSellerRechargeQuery{
			RechargeType:      strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
			Status:            options.Status,
			PaidState:         query.PaidState,
			Keyword:           options.Keyword,
			RegionID:          query.RegionID,
			StartAtMS:         query.StartAtMS,
			EndAtMS:           query.EndAtMS,
			TzOffsetMinutes:   tzOffsetMinutes,
			TargetUserID:      sellerUserID,
			HasUserOnlyFilter: userID > 0 && sellerUserID == 0,
		}, &summary); err != nil {
			response.ServerError(c, "获取充值汇总失败")
			return
		}
		response.OK(c, summary)
		return
	}
	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, rechargeBillSummaryDTO{})
		return
	}
	paidState := strings.TrimSpace(firstQuery(c, "paid_state", "paidState"))
	var summary rechargeBillSummaryDTO
	if paidState == "" && !isFinanceCoinSellerRechargeType(strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType"))) {
		resp, err := h.wallet.GetRechargeBillSummary(c.Request.Context(), &walletv1.GetRechargeBillSummaryRequest{
			RequestId:    middleware.CurrentRequestID(c),
			AppCode:      appCode,
			UserId:       userID,
			SellerUserId: sellerUserID,
			RegionId:     queryInt64(c, "region_id", "regionId"),
			RechargeType: strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
			Status:       options.Status,
			Keyword:      options.Keyword,
			StartAtMs:    queryInt64(c, "start_at_ms", "startAtMs"),
			EndAtMs:      queryInt64(c, "end_at_ms", "endAtMs"),
		})
		if err != nil {
			writeWalletError(c, err, "获取充值汇总失败")
			return
		}
		summary = rechargeBillSummaryDTO{
			Total:      rechargeBillSummaryBucketFromProto(resp.GetTotal()),
			GooglePlay: rechargeBillSummaryBucketFromProto(resp.GetGooglePlay()),
			ThirdParty: rechargeBillSummaryBucketFromProto(resp.GetThirdParty()),
			CoinSeller: rechargeBillSummaryBucketFromProto(resp.GetCoinSeller()),
		}
	}
	if err := h.applyFinanceCoinSellerSummary(c.Request.Context(), appCode, financeCoinSellerRechargeQuery{
		RechargeType:      strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
		Status:            options.Status,
		PaidState:         paidState,
		Keyword:           options.Keyword,
		RegionID:          queryInt64(c, "region_id", "regionId"),
		StartAtMS:         queryInt64(c, "start_at_ms", "startAtMs"),
		EndAtMS:           queryInt64(c, "end_at_ms", "endAtMs"),
		TzOffsetMinutes:   tzOffsetMinutes,
		TargetUserID:      sellerUserID,
		HasUserOnlyFilter: userID > 0 && sellerUserID == 0,
	}, &summary); err != nil {
		response.ServerError(c, "获取充值汇总失败")
		return
	}
	response.OK(c, summary)
}

func rechargeBillSummaryBucketFromProto(bucket *walletv1.RechargeBillSummaryBucket) rechargeBillSummaryBucketDTO {
	if bucket == nil {
		return rechargeBillSummaryBucketDTO{}
	}
	return rechargeBillSummaryBucketDTO{
		BillCount:      bucket.GetBillCount(),
		CoinAmount:     bucket.GetCoinAmount(),
		USDMinorAmount: bucket.GetUsdMinorAmount(),
	}
}

func normalizedRechargeType(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}

func nonNegativeRechargeBillBucket(bucket rechargeBillSummaryBucketDTO) rechargeBillSummaryBucketDTO {
	return rechargeBillSummaryBucketDTO{
		BillCount:      nonNegativeInt64(bucket.BillCount),
		CoinAmount:     nonNegativeInt64(bucket.CoinAmount),
		USDMinorAmount: nonNegativeInt64(bucket.USDMinorAmount),
	}
}

func nonNegativeInt64(value int64) int64 {
	if value < 0 {
		return 0
	}
	return value
}

// RefreshGoogleRechargePaidDetails 对指定账单触发 Google Orders API 实付明细同步，供财务补齐历史订单实付。
func (h *Handler) RefreshGoogleRechargePaidDetails(c *gin.Context) {
	var request googleRechargePaidRefreshRequest
	if err := c.ShouldBindJSON(&request); err != nil || len(request.TransactionIDs) == 0 {
		response.BadRequest(c, "交易号参数不正确")
		return
	}
	if len(request.TransactionIDs) > 100 {
		response.BadRequest(c, "单次最多刷新 100 笔账单")
		return
	}
	appCode := appctx.FromContext(c.Request.Context())
	if source, ok := h.billSources[appCode]; ok {
		if !source.SupportsGooglePaidSync() {
			response.BadRequest(c, "该 App 未配置 Google Play 包名和服务账号，无法同步谷歌实付")
			return
		}
		results, err := source.RefreshGooglePaidDetails(c.Request.Context(), request.TransactionIDs)
		if err != nil {
			response.ServerError(c, "查询谷歌实付明细失败")
			return
		}
		shared.OperationLog(c, h.audit, "refresh-google-recharge-paid", "admin_legacy_google_paid_details", "success", appCode)
		response.OK(c, gin.H{"items": results})
		return
	}
	resp, err := h.wallet.RefreshGooglePaymentPrices(c.Request.Context(), &walletv1.RefreshGooglePaymentPricesRequest{
		RequestId:      middleware.CurrentRequestID(c),
		AppCode:        appCode,
		TransactionIds: request.TransactionIDs,
	})
	if err != nil {
		writeWalletError(c, err, "查询谷歌实付明细失败")
		return
	}
	items := make([]googleRechargePaidDTO, 0, len(resp.GetPrices()))
	for _, price := range resp.GetPrices() {
		items = append(items, googleRechargePaidDTO{
			TransactionID:   price.GetTransactionId(),
			CurrencyCode:    price.GetCurrencyCode(),
			PaidAmountMicro: price.GetPaidAmountMicro(),
			TaxMicro:        price.GetTaxMicro(),
			NetMicro:        price.GetNetMicro(),
			SyncedAtMS:      price.GetSyncedAtMs(),
			Error:           price.GetError(),
		})
	}
	shared.OperationLog(c, h.audit, "refresh-google-recharge-paid", "payment_orders", "success", appCode)
	response.OK(c, gin.H{"items": items})
}

type rechargeBillAppDTO struct {
	AppCode string `json:"appCode"`
	AppName string `json:"appName"`
	LogoURL string `json:"logoUrl"`
}

// rechargeBillSourceLogo 是兼容扩展：历史或测试账单源不实现时仍可列出，新的 Mongo 源则补充 Logo。
type rechargeBillSourceLogo interface {
	LogoURL() string
}

// ListRechargeBillApps 返回充值详情可选的 App 目录：hyapp 在册应用 + 配置的 legacy 账单源（Yumi 等）。
func (h *Handler) ListRechargeBillApps(c *gin.Context) {
	items := []rechargeBillAppDTO{}
	seen := map[string]struct{}{}
	if h.userDB != nil {
		rows, err := h.userDB.QueryContext(c.Request.Context(), `
			SELECT app_code, app_name, logo_url
			FROM apps
			WHERE status = 'active'
			ORDER BY app_name ASC, app_code ASC`)
		if err != nil {
			response.ServerError(c, "获取 App 列表失败")
			return
		}
		defer rows.Close()
		for rows.Next() {
			var item rechargeBillAppDTO
			if err := rows.Scan(&item.AppCode, &item.AppName, &item.LogoURL); err != nil {
				response.ServerError(c, "获取 App 列表失败")
				return
			}
			item.AppCode = appctx.Normalize(item.AppCode)
			seen[item.AppCode] = struct{}{}
			items = append(items, item)
		}
		if err := rows.Err(); err != nil {
			response.ServerError(c, "获取 App 列表失败")
			return
		}
	}
	legacyApps := make([]rechargeBillAppDTO, 0, len(h.billSources))
	for _, source := range h.billSources {
		appCode := appctx.Normalize(source.AppCode())
		if _, ok := seen[appCode]; ok {
			continue
		}
		logoURL := ""
		if logoSource, ok := source.(rechargeBillSourceLogo); ok {
			logoURL = strings.TrimSpace(logoSource.LogoURL())
		}
		legacyApps = append(legacyApps, rechargeBillAppDTO{AppCode: appCode, AppName: source.AppName(), LogoURL: logoURL})
	}
	sort.Slice(legacyApps, func(i, j int) bool { return legacyApps[i].AppName < legacyApps[j].AppName })
	items = append(items, legacyApps...)
	response.OK(c, gin.H{"items": items, "total": len(items)})
}

// ListRechargeBillRegions 返回当前 App 的区域目录，用于充值详情按区域筛选；不受财务范围授权限制。
func (h *Handler) ListRechargeBillRegions(c *gin.Context) {
	appCode := appctx.FromContext(c.Request.Context())
	regions, err := h.listRechargeBillRegions(c.Request.Context(), appCode)
	if err != nil {
		response.ServerError(c, "获取区域列表失败")
		return
	}
	response.OK(c, gin.H{"items": regions, "total": len(regions)})
}

func (h *Handler) listRechargeBillRegions(ctx context.Context, appCode string) ([]rechargeBillRegionDTO, error) {
	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	items := []rechargeBillRegionDTO{}
	seen := map[int64]struct{}{}
	if h.userDB != nil {
		rows, err := h.userDB.QueryContext(ctx, `
			SELECT region_id, region_code, name
			FROM regions
			WHERE app_code = ? AND status = 'active'
			ORDER BY sort_order DESC, name ASC`, appCode)
		if err != nil {
			return nil, err
		}
		defer rows.Close()
		for rows.Next() {
			var item rechargeBillRegionDTO
			if err := rows.Scan(&item.RegionID, &item.RegionCode, &item.Name); err != nil {
				return nil, err
			}
			seen[item.RegionID] = struct{}{}
			items = append(items, item)
		}
		if err := rows.Err(); err != nil {
			return nil, err
		}
	}
	// Yumi/Aslan 这类 legacy App 的区域目录在线上 likei Mongo；这里只取目录展示，账单筛选仍以 wallet 的 target_region_id 为准。
	for _, source := range h.moneyRegionSources {
		_, sourceRegions, _, err := source.ListMoneyMasterData(ctx, repository.MoneyAccess{All: true})
		if err != nil {
			return nil, err
		}
		for _, region := range sourceRegions {
			if appctx.Normalize(region.AppCode) != appCode {
				continue
			}
			if _, ok := seen[region.RegionID]; ok {
				continue
			}
			seen[region.RegionID] = struct{}{}
			items = append(items, rechargeBillRegionDTO{RegionID: region.RegionID, RegionCode: region.RegionCode, Name: region.Name})
		}
	}
	return items, nil
}
