package handler

import (
	"context"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/sundynix/sundynix-gateway/internal/store"
)

// 充值（P5.1：兑换码渠道；设计见 PAYMENT_DESIGN.md）。
// 入账目标一律是「计费租户」（ResolveBillingTenantID）——和消耗记账同一本账，
// 谁的池子扣钱就往谁的池子充，别让用户充进一个花不到的池。

// BillingPacks: GET /api/v1/billing/packs —— 在售积分包 + 可用渠道（wechat 配了 env 才亮）。
func (h *Handler) BillingPacks(c *gin.Context) {
	packs, err := h.db.ActivePacks(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
		return
	}
	channels := []string{store.ChannelRedeem}
	if h.pay.Current() != nil {
		channels = append(channels, store.ChannelWechat)
	}
	c.JSON(http.StatusOK, gin.H{"packs": packs, "channels": channels})
}

// orderTTL 待支付订单的有效期：过期后前端轮询会把它置 expired，不再确认到账。
// 微信 Native 的 code_url 本身约 2 小时有效，这里收紧到 30 分钟——挂太久的单
// 价格可能已经改过，不让旧价格的单无限期可付。
const orderTTL = 30 * time.Minute

// BillingCreateOrder: POST /api/v1/billing/orders {pack_id} —— 微信 Native 下单，返回 code_url。
// 金额/积分由服务端按在售包锁定进订单行，前端只传包 id，不信任任何客户端金额。
func (h *Handler) BillingCreateOrder(c *gin.Context) {
	wc := h.pay.Current()
	if wc == nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "微信支付未配置，请用兑换码充值"})
		return
	}
	var b struct {
		PackID string `json:"pack_id"`
	}
	if err := c.ShouldBindJSON(&b); err != nil || strings.TrimSpace(b.PackID) == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "pack_id 必填"})
		return
	}
	ctx := c.Request.Context()
	uid := userID(c)
	billing := h.db.ResolveBillingTenantID(ctx, uid, tenantID(c))
	if billing == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无计费租户上下文"})
		return
	}
	pk, err := h.db.GetPack(ctx, b.PackID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "积分包不存在或已下架"})
		return
	}
	o := &store.PaymentOrder{
		TenantID: billing, UserID: uid, PackID: pk.ID,
		AmountFen: pk.PriceFen, CreditsMicro: pk.CreditsMicro,
		Channel: store.ChannelWechat, Status: store.OrderPending,
	}
	if err := h.db.CreateOrder(ctx, o); err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
		return
	}
	codeURL, err := wc.CreatePay(ctx, o.ID, "sundynix 积分充值 · "+pk.Name, pk.PriceFen)
	if err != nil {
		// 渠道下单失败的单直接作废，不留一堆永远付不了的 pending。
		_ = h.db.ExpireOrder(ctx, o.ID)
		c.JSON(http.StatusBadGateway, gin.H{"error": "微信下单失败: " + err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"order_id": o.ID, "code_url": codeURL, "amount_fen": o.AmountFen})
}

// BillingOrderStatus: GET /api/v1/billing/orders/:id —— 前端轮询订单态。
// pending 时顺路主动查单确认（本地/内网收不到公网回调也能到账——回调只是生产更快的通道，
// 两条路汇入同一个 MarkOrderPaid 幂等闸）；超过 TTL 置 expired。
func (h *Handler) BillingOrderStatus(c *gin.Context) {
	ctx := c.Request.Context()
	o, err := h.db.GetOrder(ctx, c.Param("id"))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "订单不存在"})
		return
	}
	// 只允许看自己计费租户的单（订单表未挂租户插件，这里显式校验）。
	if o.TenantID != h.db.ResolveBillingTenantID(ctx, userID(c), tenantID(c)) {
		c.JSON(http.StatusNotFound, gin.H{"error": "订单不存在"})
		return
	}
	updated, mismatch := h.reconcileOrder(ctx, o)
	if mismatch {
		c.JSON(http.StatusOK, gin.H{"order": updated, "warn": "支付金额与订单不符，已挂起待人工核对"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"order": updated})
}

// reconcileOrder 对一张 pending 微信单主动查单并落态：已付且金额相符→入账（幂等闸），
// 渠道关单/超 TTL→过期。返回最新订单 + 是否金额不符（不符则不入账、留人工对账）。
// 前端轮询与掉单补偿定时器共用这一份，避免两处「查单→落态」逻辑漂移。
func (h *Handler) reconcileOrder(ctx context.Context, o *store.PaymentOrder) (*store.PaymentOrder, bool) {
	wc := h.pay.Current()
	if o.Status != store.OrderPending || wc == nil {
		return o, false
	}
	if r, err := wc.QueryOrder(ctx, o.ID); err == nil {
		switch {
		case r.Paid && r.AmountFen == o.AmountFen:
			if _, err := h.db.MarkOrderPaid(ctx, o.ID, r.ChannelTxn); err == nil {
				o, _ = h.db.GetOrder(ctx, o.ID)
			}
		case r.Paid: // 金额对不上：不入账，人工对账（比错账便宜）
			return o, true
		case r.Closed:
			_ = h.db.ExpireOrder(ctx, o.ID)
			o, _ = h.db.GetOrder(ctx, o.ID)
		}
	}
	if o.Status == store.OrderPending && time.Since(o.CreatedAt) > orderTTL {
		_ = h.db.ExpireOrder(ctx, o.ID)
		o, _ = h.db.GetOrder(ctx, o.ID)
	}
	return o, false
}

// WechatCallback: POST /api/v1/billing/callback/wechat —— 微信支付回调（公开路由，验签是唯一的门）。
// 应答契约：入账成功/重复推送都回 200 {code:SUCCESS}；验签失败 4xx；处理失败 5xx 让微信重试。
func (h *Handler) WechatCallback(c *gin.Context) {
	wc := h.pay.Current()
	if wc == nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"code": "FAIL", "message": "渠道未配置"})
		return
	}
	r, err := wc.VerifyCallback(c.Request)
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"code": "FAIL", "message": "验签失败"})
		return
	}
	if !r.Paid {
		c.JSON(http.StatusOK, gin.H{"code": "SUCCESS"}) // 非成功态通知：确认收到即可
		return
	}
	ctx := c.Request.Context()
	o, err := h.db.GetOrder(ctx, r.OrderID)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{"code": "SUCCESS"}) // 不认识的单：可能是别的环境，别让微信无限重试
		return
	}
	if r.AmountFen != o.AmountFen {
		// 金额不符：不入账、不让重试（重试也不会变对），落审计人工处理。
		c.JSON(http.StatusOK, gin.H{"code": "SUCCESS"})
		return
	}
	if _, err := h.db.MarkOrderPaid(ctx, o.ID, r.ChannelTxn); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"code": "FAIL", "message": "入账失败"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"code": "SUCCESS"})
}

// BillingRedeem: POST /api/v1/billing/redeem {code} —— 核销兑换码，积分入计费租户。
func (h *Handler) BillingRedeem(c *gin.Context) {
	var b struct {
		Code string `json:"code"`
	}
	if err := c.ShouldBindJSON(&b); err != nil || strings.TrimSpace(b.Code) == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "code 必填"})
		return
	}
	ctx := c.Request.Context()
	uid := userID(c)
	billing := h.db.ResolveBillingTenantID(ctx, uid, tenantID(c))
	if billing == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无计费租户上下文"})
		return
	}
	order, err := h.db.Redeem(ctx, b.Code, billing, uid)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"order":         order,
		"balance_micro": h.db.TenantBalance(ctx, billing),
	})
}

// BillingOrders: GET /api/v1/billing/orders —— 计费租户最近充值记录（账单页展示）。
func (h *Handler) BillingOrders(c *gin.Context) {
	ctx := c.Request.Context()
	billing := h.db.ResolveBillingTenantID(ctx, userID(c), tenantID(c))
	rows, err := h.db.TenantOrders(ctx, billing, 20)
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"orders": rows})
}

// AdminGenRedeemCodes: POST /api/v1/admin/redeem-codes {credits, count, memo} —— 批量生成兑换码。
// credits 单位：积分（面向人，非 micro）。
func (h *Handler) AdminGenRedeemCodes(c *gin.Context) {
	var b struct {
		Credits float64 `json:"credits"`
		Count   int     `json:"count"`
		Memo    string  `json:"memo"`
	}
	if err := c.ShouldBindJSON(&b); err != nil || b.Credits <= 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "credits 必填且为正"})
		return
	}
	if b.Count <= 0 {
		b.Count = 1
	}
	codes, err := h.db.GenerateRedeemCodes(c.Request.Context(), b.Count, int64(b.Credits*1e6), b.Memo)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"codes": codes})
}

// AdminRedeemCodes: GET /api/v1/admin/redeem-codes —— 兑换码台账（含核销状态）。
// 码在台账里脱敏只露首尾：完整明文只在生成响应里给一次。兑换码等同现金，
// 常驻可查的列表接口不该是第二个明文出口（丢了码就重新生成一张，不提供找回）。
func (h *Handler) AdminRedeemCodes(c *gin.Context) {
	rows, err := h.db.ListRedeemCodes(c.Request.Context(), 200)
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
		return
	}
	for i := range rows {
		if n := len(rows[i].Code); n > 12 {
			rows[i].Code = rows[i].Code[:8] + "…" + rows[i].Code[n-4:]
		}
	}
	c.JSON(http.StatusOK, gin.H{"codes": rows})
}

// AdminSavePack: PUT /api/v1/admin/packs {id?, name, credits, price_fen, active, sort} —— 配积分包。
func (h *Handler) AdminSavePack(c *gin.Context) {
	var b struct {
		ID       string  `json:"id"`
		Name     string  `json:"name"`
		Credits  float64 `json:"credits"` // 积分（面向人）
		PriceFen int64   `json:"price_fen"`
		Active   bool    `json:"active"`
		Sort     int     `json:"sort"`
	}
	if err := c.ShouldBindJSON(&b); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
		return
	}
	pk := &store.CreditPack{Name: strings.TrimSpace(b.Name), CreditsMicro: int64(b.Credits * 1e6), PriceFen: b.PriceFen, Active: b.Active, Sort: b.Sort}
	pk.ID = b.ID
	if err := h.db.SavePack(c.Request.Context(), pk); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"pack": pk})
}

// AdminPacks: GET /api/v1/admin/packs —— 全部积分包（含下架）。
func (h *Handler) AdminPacks(c *gin.Context) {
	rows, err := h.db.ListPacks(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"packs": rows})
}

// AdminOrders: GET /api/v1/admin/orders?status= —— 全平台充值订单流 + 状态计数（P5.3 观测）。
func (h *Handler) AdminOrders(c *gin.Context) {
	ctx := c.Request.Context()
	rows, err := h.db.AllOrders(ctx, c.Query("status"), 50)
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"orders": rows, "stats": h.db.OrderStats(ctx)})
}

// AdminReconcile: GET /api/v1/admin/orders/reconcile —— 日终对账（P5.3）。
// paid 订单 ↔ 账本 grant 分录逐单比对，列出对不上的（正常应为空）。
func (h *Handler) AdminReconcile(c *gin.Context) {
	rows, err := h.db.ReconcileOrders(c.Request.Context(), 200)
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"diffs": rows, "ok": len(rows) == 0})
}
