package payment

import (
	"context"
	"database/sql"
	"errors"
	"math"
	"strconv"
	"strings"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/walletclient"
	"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"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const usdtMicroUnit int64 = 1_000_000

type Handler struct {
	wallet walletclient.Client
	userDB *sql.DB
	audit  shared.OperationLogger
}

// New 组装支付后台 handler；wallet 负责账单和商品事实，userDB 只补后台列表需要的用户展示资料。
func New(wallet walletclient.Client, userDB *sql.DB, audit shared.OperationLogger) *Handler {
	return &Handler{wallet: wallet, userDB: userDB, audit: audit}
}

func (h *Handler) ListRechargeBills(c *gin.Context) {
	options := shared.ListOptions(c)
	appCode := appctx.FromContext(c.Request.Context())
	resp, err := h.wallet.ListRechargeBills(c.Request.Context(), &walletv1.ListRechargeBillsRequest{
		RequestId:    middleware.CurrentRequestID(c),
		AppCode:      appCode,
		UserId:       queryInt64(c, "user_id", "userId"),
		SellerUserId: queryInt64(c, "seller_user_id", "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"),
		Page:         int32(options.Page),
		PageSize:     int32(options.PageSize),
	})
	if err != nil {
		response.ServerError(c, "获取账单列表失败")
		return
	}

	items := make([]rechargeBillDTO, 0, len(resp.GetBills()))
	for _, item := range resp.GetBills() {
		items = append(items, rechargeBillFromProto(item))
	}
	// 账单事实仍由 wallet-service 返回；这里只按本页出现的付款用户和币商用户批量补展示资料，避免列表渲染时逐行查用户。
	if err := h.fillRechargeBillUsers(c.Request.Context(), appCode, items); err != nil {
		response.ServerError(c, "获取账单用户资料失败")
		return
	}
	response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}

// fillRechargeBillUsers 将用户资料写回当前页账单 DTO；缺失资料会在 DTO 层保留长 ID，避免影响账单事实展示。
func (h *Handler) fillRechargeBillUsers(ctx context.Context, appCode string, items []rechargeBillDTO) error {
	userIDs := collectRechargeBillUserIDs(items)
	if len(userIDs) == 0 {
		return nil
	}
	profiles, err := h.loadRechargeBillUsers(ctx, appCode, userIDs)
	if err != nil {
		return err
	}
	for index := range items {
		items[index].User = rechargeBillUserOrFallback(profiles[items[index].UserID], items[index].UserID)
		items[index].Seller = rechargeBillUserOrFallback(profiles[items[index].SellerUserID], items[index].SellerUserID)
	}
	return nil
}

// loadRechargeBillUsers 按 app_code 和用户 ID 批量读取展示资料；国家只服务后台账单展示，账单金额和区域事实仍以 wallet-service 为准。
func (h *Handler) loadRechargeBillUsers(ctx context.Context, appCode string, userIDs []int64) (map[int64]rechargeBillUserDTO, error) {
	if h == nil || h.userDB == nil {
		return nil, errors.New("user mysql is not configured")
	}
	// 本查询只读取 users/countries 表里的展示字段，按照 app_code 和 user_id 精确命中；区域和账单筛选仍以 wallet-service 返回为准。
	rows, err := h.userDB.QueryContext(ctx, `
		SELECT u.user_id, COALESCE(u.current_display_user_id, ''), COALESCE(u.username, ''), COALESCE(u.avatar, ''),
		       COALESCE(u.country, ''), COALESCE(c.country_name, ''), COALESCE(c.country_display_name, '')
		FROM users u
		LEFT JOIN countries c ON c.app_code = u.app_code AND c.country_code = u.country
		WHERE u.app_code = ? AND u.user_id IN (`+placeholders(len(userIDs))+`)`,
		append([]any{appCode}, int64Args(userIDs)...)...,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	profiles := make(map[int64]rechargeBillUserDTO, len(userIDs))
	for rows.Next() {
		var profile rechargeBillUserDTO
		var userID int64
		if err := rows.Scan(
			&userID,
			&profile.DisplayUserID,
			&profile.Username,
			&profile.Avatar,
			&profile.CountryCode,
			&profile.CountryName,
			&profile.CountryDisplayName,
		); err != nil {
			return nil, err
		}
		profile.UserID = strconv.FormatInt(userID, 10)
		profiles[userID] = profile
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return profiles, nil
}

func (h *Handler) ListRechargeProducts(c *gin.Context) {
	options := shared.ListOptions(c)
	resp, err := h.wallet.ListAdminRechargeProducts(c.Request.Context(), &walletv1.ListAdminRechargeProductsRequest{
		RequestId:    middleware.CurrentRequestID(c),
		AppCode:      appctx.FromContext(c.Request.Context()),
		Status:       strings.TrimSpace(firstQuery(c, "status")),
		Platform:     strings.TrimSpace(firstQuery(c, "platform")),
		RegionId:     queryInt64(c, "region_id", "regionId"),
		AudienceType: strings.TrimSpace(firstQuery(c, "audience_type", "audienceType")),
		Keyword:      options.Keyword,
		Page:         int32(options.Page),
		PageSize:     int32(options.PageSize),
	})
	if err != nil {
		writeWalletError(c, err, "获取内购配置失败")
		return
	}
	items := make([]rechargeProductDTO, 0, len(resp.GetProducts()))
	for _, item := range resp.GetProducts() {
		items = append(items, rechargeProductFromProto(item))
	}
	response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}

func (h *Handler) CreateRechargeProduct(c *gin.Context) {
	var request rechargeProductRequest
	if err := c.ShouldBindJSON(&request); err != nil {
		response.BadRequest(c, "内购配置参数不正确")
		return
	}
	amountMicro, err := parseUSDTMicro(request.AmountUSDT, request.AmountUSDTMicro)
	if err != nil {
		response.BadRequest(c, "金额USDT不正确")
		return
	}
	resp, err := h.wallet.CreateRechargeProduct(c.Request.Context(), &walletv1.CreateRechargeProductRequest{
		RequestId:      middleware.CurrentRequestID(c),
		AppCode:        appctx.FromContext(c.Request.Context()),
		AmountMicro:    amountMicro,
		CoinAmount:     request.CoinAmount,
		ProductName:    strings.TrimSpace(request.ProductName),
		Description:    strings.TrimSpace(request.Description),
		Platform:       strings.TrimSpace(request.Platform),
		AudienceType:   strings.TrimSpace(request.AudienceType),
		RegionIds:      request.RegionIDs,
		Enabled:        request.Enabled,
		OperatorUserId: actorID(c),
	})
	if err != nil {
		writeWalletError(c, err, "创建内购配置失败")
		return
	}
	item := rechargeProductFromProto(resp.GetProduct())
	shared.OperationLogWithResourceID(c, h.audit, "create-recharge-product", "wallet_recharge_products", strconv.FormatInt(item.ProductID, 10), "success", item.ProductCode)
	response.Created(c, item)
}

func (h *Handler) UpdateRechargeProduct(c *gin.Context) {
	productID, ok := productID(c)
	if !ok {
		return
	}
	var request rechargeProductRequest
	if err := c.ShouldBindJSON(&request); err != nil {
		response.BadRequest(c, "内购配置参数不正确")
		return
	}
	amountMicro, err := parseUSDTMicro(request.AmountUSDT, request.AmountUSDTMicro)
	if err != nil {
		response.BadRequest(c, "金额USDT不正确")
		return
	}
	resp, err := h.wallet.UpdateRechargeProduct(c.Request.Context(), &walletv1.UpdateRechargeProductRequest{
		RequestId:      middleware.CurrentRequestID(c),
		AppCode:        appctx.FromContext(c.Request.Context()),
		ProductId:      productID,
		AmountMicro:    amountMicro,
		CoinAmount:     request.CoinAmount,
		ProductName:    strings.TrimSpace(request.ProductName),
		Description:    strings.TrimSpace(request.Description),
		Platform:       strings.TrimSpace(request.Platform),
		AudienceType:   strings.TrimSpace(request.AudienceType),
		RegionIds:      request.RegionIDs,
		Enabled:        request.Enabled,
		OperatorUserId: actorID(c),
	})
	if err != nil {
		writeWalletError(c, err, "修改内购配置失败")
		return
	}
	item := rechargeProductFromProto(resp.GetProduct())
	shared.OperationLogWithResourceID(c, h.audit, "update-recharge-product", "wallet_recharge_products", strconv.FormatInt(item.ProductID, 10), "success", item.ProductCode)
	response.OK(c, item)
}

func (h *Handler) DeleteRechargeProduct(c *gin.Context) {
	productID, ok := productID(c)
	if !ok {
		return
	}
	_, err := h.wallet.DeleteRechargeProduct(c.Request.Context(), &walletv1.DeleteRechargeProductRequest{
		RequestId:      middleware.CurrentRequestID(c),
		AppCode:        appctx.FromContext(c.Request.Context()),
		ProductId:      productID,
		OperatorUserId: actorID(c),
	})
	if err != nil {
		writeWalletError(c, err, "删除内购配置失败")
		return
	}
	shared.OperationLogWithResourceID(c, h.audit, "delete-recharge-product", "wallet_recharge_products", strconv.FormatInt(productID, 10), "success", "")
	response.OK(c, gin.H{"deleted": true})
}

func (h *Handler) ListThirdPartyPaymentChannels(c *gin.Context) {
	resp, err := h.wallet.ListThirdPartyPaymentChannels(c.Request.Context(), &walletv1.ListThirdPartyPaymentChannelsRequest{
		RequestId:              middleware.CurrentRequestID(c),
		AppCode:                appctx.FromContext(c.Request.Context()),
		ProviderCode:           strings.TrimSpace(firstQuery(c, "provider_code", "providerCode")),
		Status:                 strings.TrimSpace(firstQuery(c, "status")),
		IncludeDisabledMethods: true,
	})
	if err != nil {
		writeWalletError(c, err, "获取三方支付渠道失败")
		return
	}
	items := make([]thirdPartyPaymentChannelDTO, 0, len(resp.GetChannels()))
	for _, item := range resp.GetChannels() {
		items = append(items, thirdPartyPaymentChannelFromProto(item))
	}
	response.OK(c, gin.H{"items": items, "total": len(items)})
}

func (h *Handler) SetThirdPartyPaymentMethodStatus(c *gin.Context) {
	methodID := queryPathInt64(c, "method_id")
	if methodID <= 0 {
		response.BadRequest(c, "支付方式ID不正确")
		return
	}
	var request thirdPartyPaymentMethodStatusRequest
	if err := c.ShouldBindJSON(&request); err != nil {
		response.BadRequest(c, "支付方式状态参数不正确")
		return
	}
	resp, err := h.wallet.SetThirdPartyPaymentMethodStatus(c.Request.Context(), &walletv1.SetThirdPartyPaymentMethodStatusRequest{
		RequestId:      middleware.CurrentRequestID(c),
		AppCode:        appctx.FromContext(c.Request.Context()),
		MethodId:       methodID,
		Enabled:        request.Enabled,
		OperatorUserId: actorID(c),
	})
	if err != nil {
		writeWalletError(c, err, "修改支付方式状态失败")
		return
	}
	item := thirdPartyPaymentMethodFromProto(resp.GetMethod())
	shared.OperationLogWithResourceID(c, h.audit, "update-third-party-payment-method-status", "third_party_payment_methods", strconv.FormatInt(methodID, 10), "success", item.Status)
	response.OK(c, item)
}

func (h *Handler) UpdateThirdPartyPaymentRate(c *gin.Context) {
	methodID := queryPathInt64(c, "method_id")
	if methodID <= 0 {
		response.BadRequest(c, "支付方式ID不正确")
		return
	}
	var request thirdPartyPaymentRateRequest
	if err := c.ShouldBindJSON(&request); err != nil || strings.TrimSpace(request.USDToCurrencyRate) == "" {
		response.BadRequest(c, "支付汇率参数不正确")
		return
	}
	resp, err := h.wallet.UpdateThirdPartyPaymentRate(c.Request.Context(), &walletv1.UpdateThirdPartyPaymentRateRequest{
		RequestId:         middleware.CurrentRequestID(c),
		AppCode:           appctx.FromContext(c.Request.Context()),
		MethodId:          methodID,
		UsdToCurrencyRate: strings.TrimSpace(request.USDToCurrencyRate),
		OperatorUserId:    actorID(c),
	})
	if err != nil {
		writeWalletError(c, err, "修改支付方式汇率失败")
		return
	}
	item := thirdPartyPaymentMethodFromProto(resp.GetMethod())
	shared.OperationLogWithResourceID(c, h.audit, "update-third-party-payment-rate", "third_party_payment_methods", strconv.FormatInt(methodID, 10), "success", item.USDToCurrencyRate)
	response.OK(c, item)
}

type rechargeProductRequest struct {
	AmountUSDT      string  `json:"amountUsdt"`
	AmountUSDTMicro int64   `json:"amountUsdtMicro"`
	CoinAmount      int64   `json:"coinAmount"`
	ProductName     string  `json:"productName"`
	Description     string  `json:"description"`
	AudienceType    string  `json:"audienceType"`
	Platform        string  `json:"platform"`
	RegionIDs       []int64 `json:"regionIds"`
	Enabled         bool    `json:"enabled"`
}

type thirdPartyPaymentMethodStatusRequest struct {
	Enabled bool `json:"enabled"`
}

type thirdPartyPaymentRateRequest struct {
	USDToCurrencyRate string `json:"usdToCurrencyRate"`
}

func queryInt64(c *gin.Context, keys ...string) int64 {
	value := strings.TrimSpace(firstQuery(c, keys...))
	if value == "" {
		return 0
	}
	parsed, err := strconv.ParseInt(value, 10, 64)
	if err != nil || parsed < 0 {
		return 0
	}
	return parsed
}

func firstQuery(c *gin.Context, keys ...string) string {
	for _, key := range keys {
		if value := c.Query(key); value != "" {
			return value
		}
	}
	return ""
}

func queryPathInt64(c *gin.Context, name string) int64 {
	value := strings.TrimSpace(c.Param(name))
	parsed, err := strconv.ParseInt(value, 10, 64)
	if err != nil || parsed <= 0 {
		return 0
	}
	return parsed
}

func productID(c *gin.Context) (int64, bool) {
	value := strings.TrimSpace(c.Param("product_id"))
	parsed, err := strconv.ParseInt(value, 10, 64)
	if err != nil || parsed <= 0 {
		response.BadRequest(c, "内购配置 ID 不正确")
		return 0, false
	}
	return parsed, true
}

func actorID(c *gin.Context) int64 {
	return int64(shared.ActorFromContext(c).UserID)
}

func parseUSDTMicro(amountText string, amountMicro int64) (int64, error) {
	if amountMicro > 0 {
		return amountMicro, nil
	}
	raw := strings.TrimSpace(amountText)
	if raw == "" {
		return 0, errors.New("amount is required")
	}
	if strings.HasPrefix(raw, "+") {
		raw = strings.TrimPrefix(raw, "+")
	}
	parts := strings.Split(raw, ".")
	if len(parts) > 2 || parts[0] == "" {
		return 0, errors.New("amount format is invalid")
	}
	if !allDigits(parts[0]) {
		return 0, errors.New("amount integer part is invalid")
	}
	whole, err := strconv.ParseInt(parts[0], 10, 64)
	if err != nil || whole > math.MaxInt64/usdtMicroUnit {
		return 0, errors.New("amount is too large")
	}
	var fraction int64
	if len(parts) == 2 {
		if parts[1] == "" || len(parts[1]) > 6 || !allDigits(parts[1]) {
			return 0, errors.New("amount fraction part is invalid")
		}
		padded := parts[1] + strings.Repeat("0", 6-len(parts[1]))
		fraction, err = strconv.ParseInt(padded, 10, 64)
		if err != nil {
			return 0, errors.New("amount fraction part is invalid")
		}
	}
	if whole > (math.MaxInt64-fraction)/usdtMicroUnit {
		return 0, errors.New("amount is too large")
	}
	value := whole*usdtMicroUnit + fraction
	if value <= 0 {
		return 0, errors.New("amount must be positive")
	}
	return value, nil
}

func allDigits(value string) bool {
	for _, char := range value {
		if char < '0' || char > '9' {
			return false
		}
	}
	return value != ""
}

func writeWalletError(c *gin.Context, err error, fallback string) {
	message := fallback
	if st, ok := status.FromError(err); ok && strings.TrimSpace(st.Message()) != "" {
		message = st.Message()
	}
	switch status.Code(err) {
	case codes.InvalidArgument, codes.AlreadyExists, codes.FailedPrecondition, codes.Aborted:
		response.BadRequest(c, message)
	case codes.NotFound:
		response.NotFound(c, message)
	default:
		response.ServerError(c, fallback)
	}
}
