package admin

import (
    "net/http"
    "time"
    "encoding/json"

    "bindbox-game/internal/code"
    "bindbox-game/internal/pkg/core"
    "bindbox-game/internal/pkg/validation"
    "bindbox-game/internal/repository/mysql/model"
)

type billTransactionItem struct {
    TransactionID string `json:"transaction_id"`
    OutTradeNo    string `json:"out_trade_no"`
    AmountTotal   int64  `json:"amount_total"`
}

type billRefundItem struct {
    RefundNo     string `json:"refund_no"`
    OutTradeNo   string `json:"out_trade_no"`
    AmountRefund int64  `json:"amount_refund"`
}

type importBillRequest struct {
    BillDate string                 `json:"bill_date"` // YYYY-MM-DD
    Type     string                 `json:"type"`      // transactions | refunds
    Items    []map[string]any       `json:"items"`
}

type importBillResponse struct {
    Imported bool   `json:"imported"`
    Message  string `json:"message"`
}

// ImportPaymentBill 导入支付账单并计算差异
func (h *handler) ImportPaymentBill() core.HandlerFunc {
    return func(ctx core.Context) {
        req := new(importBillRequest)
        rsp := new(importBillResponse)
        if err := ctx.ShouldBindJSON(req); err != nil {
            ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
            return
        }
        t, err := time.Parse("2006-01-02", req.BillDate)
        if err != nil || (req.Type != "transactions" && req.Type != "refunds") {
            ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "invalid bill_date or type"))
            return
        }
        _ = h.writeDB.PaymentBills.WithContext(ctx.RequestContext()).Create(&model.PaymentBills{ BillDate: t, Type: req.Type, Imported: true })
        if req.Type == "transactions" {
            var items []billTransactionItem
            for _, v := range req.Items {
                b, _ := json.Marshal(v)
                var it billTransactionItem
                _ = json.Unmarshal(b, &it)
                if it.TransactionID != "" || it.OutTradeNo != "" { items = append(items, it) }
            }
            h.calcTransactionDiff(ctx, t, items)
        } else {
            var items []billRefundItem
            for _, v := range req.Items {
                b, _ := json.Marshal(v)
                var it billRefundItem
                _ = json.Unmarshal(b, &it)
                if it.RefundNo != "" || it.OutTradeNo != "" { items = append(items, it) }
            }
            h.calcRefundDiff(ctx, t, items)
        }
        rsp.Imported = true
        rsp.Message = "ok"
        ctx.Payload(rsp)
    }
}

func (h *handler) calcTransactionDiff(ctx core.Context, billDate time.Time, items []billTransactionItem) {
    wechatIDs := make(map[string]billTransactionItem)
    orderNos := make(map[string]billTransactionItem)
    for _, it := range items { if it.TransactionID != "" { wechatIDs[it.TransactionID] = it }; if it.OutTradeNo != "" { orderNos[it.OutTradeNo] = it } }
    start := billDate
    end := billDate.Add(24*time.Hour)
    locals, _ := h.readDB.PaymentTransactions.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.PaymentTransactions.SuccessTime.Gte(start), h.readDB.PaymentTransactions.SuccessTime.Lte(end)).Find()
    matchedLocal := make(map[string]bool)
    for _, lt := range locals {
        it, ok := wechatIDs[lt.TransactionID]
        if !ok {
            if lt.OrderNo != "" { it, ok = orderNos[lt.OrderNo] }
        }
        if !ok {
            _ = h.writeDB.PaymentBillDiff.WithContext(ctx.RequestContext()).Create(&model.PaymentBillDiff{ BillDate: billDate, DiffType: "MISSING_WECHAT", LocalTxID: lt.TransactionID, Detail: jsonStr(map[string]any{"order_no": lt.OrderNo}) })
            continue
        }
        matchedLocal[lt.TransactionID] = true
        if it.AmountTotal != lt.AmountTotal {
            _ = h.writeDB.PaymentBillDiff.WithContext(ctx.RequestContext()).Create(&model.PaymentBillDiff{ BillDate: billDate, DiffType: "AMOUNT_MISMATCH", LocalTxID: lt.TransactionID, WechatTxID: it.TransactionID, Detail: jsonStr(map[string]any{"local": lt.AmountTotal, "wechat": it.AmountTotal}) })
        }
    }
    for _, it := range items {
        key := it.TransactionID
        if key == "" { key = it.OutTradeNo }
        if key == "" { continue }
        if it.TransactionID != "" && matchedLocal[it.TransactionID] { continue }
        // 未在本地找到
        _ = h.writeDB.PaymentBillDiff.WithContext(ctx.RequestContext()).Create(&model.PaymentBillDiff{ BillDate: billDate, DiffType: "MISSING_LOCAL", WechatTxID: it.TransactionID, Detail: jsonStr(map[string]any{"out_trade_no": it.OutTradeNo}) })
    }
}

func (h *handler) calcRefundDiff(ctx core.Context, billDate time.Time, items []billRefundItem) {
    refundNos := make(map[string]billRefundItem)
    orderNos := make(map[string]billRefundItem)
    for _, it := range items { if it.RefundNo != "" { refundNos[it.RefundNo] = it }; if it.OutTradeNo != "" { orderNos[it.OutTradeNo] = it } }
    start := billDate
    end := billDate.Add(24*time.Hour)
    locals, _ := h.readDB.PaymentRefunds.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.PaymentRefunds.CreatedAt.Gte(start), h.readDB.PaymentRefunds.CreatedAt.Lte(end)).Find()
    matched := make(map[string]bool)
    for _, lr := range locals {
        it, ok := refundNos[lr.RefundNo]
        if !ok {
            if lr.OrderNo != "" { it, ok = orderNos[lr.OrderNo] }
        }
        if !ok {
            _ = h.writeDB.PaymentBillDiff.WithContext(ctx.RequestContext()).Create(&model.PaymentBillDiff{ BillDate: billDate, DiffType: "MISSING_WECHAT_REFUND", LocalTxID: lr.RefundNo, Detail: jsonStr(map[string]any{"order_no": lr.OrderNo}) })
            continue
        }
        matched[lr.RefundNo] = true
        if it.AmountRefund != lr.AmountRefund {
            _ = h.writeDB.PaymentBillDiff.WithContext(ctx.RequestContext()).Create(&model.PaymentBillDiff{ BillDate: billDate, DiffType: "REFUND_AMOUNT_MISMATCH", LocalTxID: lr.RefundNo, WechatTxID: it.RefundNo, Detail: jsonStr(map[string]any{"local": lr.AmountRefund, "wechat": it.AmountRefund}) })
        }
    }
    for _, it := range items {
        key := it.RefundNo
        if key == "" { key = it.OutTradeNo }
        if key == "" { continue }
        if it.RefundNo != "" && matched[it.RefundNo] { continue }
        _ = h.writeDB.PaymentBillDiff.WithContext(ctx.RequestContext()).Create(&model.PaymentBillDiff{ BillDate: billDate, DiffType: "MISSING_LOCAL_REFUND", WechatTxID: it.RefundNo, Detail: jsonStr(map[string]any{"out_trade_no": it.OutTradeNo}) })
    }
}

func jsonStr(v any) string { b, _ := json.Marshal(v); return string(b) }

type listBillDiffRequest struct {
    BillDate string `form:"bill_date"`
    DiffType string `form:"diff_type"`
    Page     int    `form:"page"`
    Size     int    `form:"size"`
}

type listBillDiffResponse struct {
    Total int64             `json:"total"`
    List  []map[string]any  `json:"list"`
}

// ListPaymentBillDiff 查询账单差异
func (h *handler) ListPaymentBillDiff() core.HandlerFunc {
    return func(ctx core.Context) {
        req := new(listBillDiffRequest)
        if err := ctx.ShouldBindQuery(req); err != nil {
            ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
            return
        }
        if req.Page <= 0 { req.Page = 1 }
        if req.Size <= 0 || req.Size > 100 { req.Size = 20 }
        q := h.readDB.PaymentBillDiff.WithContext(ctx.RequestContext()).ReadDB()
        if req.BillDate != "" { if t, err := time.Parse("2006-01-02", req.BillDate); err==nil { q = q.Where(h.readDB.PaymentBillDiff.BillDate.Eq(t)) } }
        if req.DiffType != "" { q = q.Where(h.readDB.PaymentBillDiff.DiffType.Eq(req.DiffType)) }
        total, _ := q.Count()
        items, _ := q.Order(h.readDB.PaymentBillDiff.ID.Desc()).Limit(req.Size).Offset((req.Page-1)*req.Size).Find()
        var list []map[string]any
        for _, it := range items {
            list = append(list, map[string]any{
                "bill_date": it.BillDate.Format("2006-01-02"),
                "diff_type": it.DiffType,
                "local_tx_id": it.LocalTxID,
                "wechat_tx_id": it.WechatTxID,
                "detail": it.Detail,
                "created_at": it.CreatedAt,
            })
        }
        ctx.Payload(&listBillDiffResponse{ Total: total, List: list })
    }
}