package admin

import (
	"bindbox-game/internal/code"
	"bindbox-game/internal/pkg/core"
	"bindbox-game/internal/pkg/validation"
	"bindbox-game/internal/repository/mysql/model"
	"fmt"
	"net/http"
	"sort"
	"time"
)

type spendingLeaderboardRequest struct {
	Page      int    `form:"page"`
	PageSize  int    `form:"page_size"`
	RangeType string `form:"rangeType"` // today, 7d, 30d, custom
	StartDate string `form:"start"`
	EndDate   string `form:"end"`
	SortBy    string `form:"sort_by"` // spending, profit
}

type spendingLeaderboardItem struct {
	UserID          int64  `json:"user_id"`
	Nickname        string `json:"nickname"`
	Avatar          string `json:"avatar"`
	OrderCount      int64  `json:"-"`               // Hidden
	TotalSpending   int64  `json:"-"`               // Hidden
	TotalPrizeValue int64  `json:"-"`               // Hidden
	TotalDiscount   int64  `json:"total_discount"`  // Total Coupon Discount (Fen)
	TotalPoints     int64  `json:"total_points"`    // Total Points Discount (Fen)
	GamePassCount   int64  `json:"game_pass_count"` // Count of SourceType=4
	ItemCardCount   int64  `json:"item_card_count"` // Count where ItemCardID > 0
	// Breakdown by game type
	IchibanSpending  int64 `json:"ichiban_spending"`
	IchibanPrize     int64 `json:"ichiban_prize"`
	IchibanProfit    int64 `json:"ichiban_profit"`
	IchibanCount     int64 `json:"ichiban_count"`
	InfiniteSpending int64 `json:"infinite_spending"`
	InfinitePrize    int64 `json:"infinite_prize"`
	InfiniteProfit   int64 `json:"infinite_profit"`
	InfiniteCount    int64 `json:"infinite_count"`
	MatchingSpending int64 `json:"matching_spending"`
	MatchingPrize    int64 `json:"matching_prize"`
	MatchingProfit   int64 `json:"matching_profit"`
	MatchingCount    int64 `json:"matching_count"`
	// 直播间统计 (source_type=5)
	LivestreamSpending int64 `json:"livestream_spending"`
	LivestreamPrize    int64 `json:"livestream_prize"`
	LivestreamProfit   int64 `json:"livestream_profit"`
	LivestreamCount    int64 `json:"livestream_count"`

	Profit     int64   `json:"profit"`      // Spending - PrizeValue
	ProfitRate float64 `json:"profit_rate"` // Profit / Spending
}

type spendingLeaderboardResponse struct {
	Page     int                       `json:"page"`
	PageSize int                       `json:"page_size"`
	Total    int64                     `json:"total"`
	List     []spendingLeaderboardItem `json:"list"`
}

func (h *handler) DashboardPlayerSpendingLeaderboard() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(spendingLeaderboardRequest)
		if err := ctx.ShouldBindForm(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		if req.Page <= 0 {
			req.Page = 1
		}
		if req.PageSize <= 0 {
			req.PageSize = 20
		}

		var start, end time.Time
		if req.RangeType != "all" {
			start, end = parseRange(req.RangeType, req.StartDate, req.EndDate)
			h.logger.Info(fmt.Sprintf("SpendingLeaderboard range: start=%v, end=%v, type=%s", start, end, req.RangeType))
		} else {
			h.logger.Info("SpendingLeaderboard range: ALL TIME")
		}

		db := h.repo.GetDbR().WithContext(ctx.RequestContext())

		// 1. Get Top Spenders from Orders
		type orderStat struct {
			UserID             int64
			TotalAmount        int64 // ActualAmount
			OrderCount         int64
			TotalDiscount      int64
			TotalPoints        int64
			GamePassCount      int64
			ItemCardCount      int64
			IchibanSpending    int64
			IchibanCount       int64
			InfiniteSpending   int64
			InfiniteCount      int64
			MatchingSpending   int64
			MatchingCount      int64
			LivestreamSpending int64
			LivestreamCount    int64
		}
		var stats []orderStat

		query := db.Table(model.TableNameOrders).
			Joins("LEFT JOIN (SELECT l.order_id, MAX(a.activity_category_id) as category_id FROM activity_draw_logs l JOIN activity_issues i ON i.id = l.issue_id JOIN activities a ON a.id = i.activity_id GROUP BY l.order_id) oa ON oa.order_id = orders.id").
			Where("orders.status = ?", 2)

		if req.RangeType != "all" {
			query = query.Where("orders.created_at >= ?", start).Where("orders.created_at <= ?", end)
		}

		if err := query.Select(`
				orders.user_id, 
				SUM(orders.total_amount) as total_amount, 
				COUNT(orders.id) as order_count,
				SUM(orders.discount_amount) as total_discount,
				SUM(orders.points_amount) as total_points,
				SUM(CASE WHEN orders.source_type = 4 THEN 1 ELSE 0 END) as game_pass_count,
				SUM(CASE WHEN orders.item_card_id > 0 THEN 1 ELSE 0 END) as item_card_count,
				SUM(CASE WHEN oa.category_id = 1 THEN orders.total_amount ELSE 0 END) as ichiban_spending,
				SUM(CASE WHEN oa.category_id = 1 THEN 1 ELSE 0 END) as ichiban_count,
				SUM(CASE WHEN oa.category_id = 2 THEN orders.total_amount ELSE 0 END) as infinite_spending,
				SUM(CASE WHEN oa.category_id = 2 THEN 1 ELSE 0 END) as infinite_count,
				SUM(CASE WHEN oa.category_id = 3 THEN orders.total_amount ELSE 0 END) as matching_spending,
				SUM(CASE WHEN oa.category_id = 3 THEN 1 ELSE 0 END) as matching_count,
				SUM(CASE WHEN orders.source_type = 5 THEN orders.total_amount ELSE 0 END) as livestream_spending,
				SUM(CASE WHEN orders.source_type = 5 THEN 1 ELSE 0 END) as livestream_count
			`).
			Group("orders.user_id").
			Order("total_amount DESC").
			Limit(100).
			Scan(&stats).Error; err != nil {
			h.logger.Error(fmt.Sprintf("SpendingLeaderboard SQL error: %v", err))
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21020, err.Error()))
			return
		}
		h.logger.Info(fmt.Sprintf("SpendingLeaderboard SQL done: count=%d", len(stats)))

		// 2. Collect User IDs
		userIDs := make([]int64, 0, len(stats))
		statMap := make(map[int64]*spendingLeaderboardItem)
		for _, s := range stats {
			userIDs = append(userIDs, s.UserID)
			statMap[s.UserID] = &spendingLeaderboardItem{
				UserID:             s.UserID,
				TotalSpending:      s.TotalAmount,
				OrderCount:         s.OrderCount,
				TotalDiscount:      s.TotalDiscount,
				TotalPoints:        s.TotalPoints,
				GamePassCount:      s.GamePassCount,
				ItemCardCount:      s.ItemCardCount,
				IchibanSpending:    s.IchibanSpending,
				IchibanCount:       s.IchibanCount,
				InfiniteSpending:   s.InfiniteSpending,
				InfiniteCount:      s.InfiniteCount,
				MatchingSpending:   s.MatchingSpending,
				MatchingCount:      s.MatchingCount,
				LivestreamSpending: 0, // Will be updated from douyin_orders
				LivestreamCount:    s.LivestreamCount,
			}
		}

		// 2.1 Fetch Real Douyin Spending
		if len(userIDs) > 0 {
			type dyStat struct {
				UserID int64
				Amount int64
				Count  int64
			}
			var dyStats []dyStat
			dyQuery := h.repo.GetDbR().Table("douyin_orders").
				Select("CAST(local_user_id AS SIGNED) as user_id, SUM(actual_pay_amount) as amount, COUNT(*) as count").
				Where("local_user_id IN ?", userIDs).
				Where("local_user_id != '' AND local_user_id != '0'")

			if req.RangeType != "all" {
				dyQuery = dyQuery.Where("created_at >= ?", start).Where("created_at <= ?", end)
			}

			if err := dyQuery.Group("local_user_id").Scan(&dyStats).Error; err == nil {
				for _, ds := range dyStats {
					if item, ok := statMap[ds.UserID]; ok {
						item.LivestreamSpending = ds.Amount
						item.LivestreamCount = ds.Count // Use real paid order count
						item.TotalSpending += ds.Amount // Add to total since orders.total_amount was 0 for these
					}
				}
			}
		}

		if len(userIDs) > 0 {
			// 3. Get User Info
			// Use h.readDB.Users (GEN) as it's simple
			users, _ := h.readDB.Users.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Users.ID.In(userIDs...)).Find()
			for _, u := range users {
				if item, ok := statMap[u.ID]; ok {
					item.Nickname = u.Nickname
					item.Avatar = u.Avatar
				}
			}

			// 4. Calculate Prize Value (Inventory)
			type invStat struct {
				UserID          int64
				TotalValue      int64
				IchibanPrize    int64
				InfinitePrize   int64
				MatchingPrize   int64
				LivestreamPrize int64
			}
			var invStats []invStat

			// Join with Products, Activities, and Orders (for livestream detection)
			query := db.Table(model.TableNameUserInventory).
				Joins("JOIN products ON products.id = user_inventory.product_id").
				Joins("LEFT JOIN activities ON activities.id = user_inventory.activity_id").
				Joins("LEFT JOIN orders ON orders.id = user_inventory.order_id").
				Where("user_inventory.user_id IN ?", userIDs)

			if req.RangeType != "all" {
				query = query.Where("user_inventory.created_at >= ?", start).
					Where("user_inventory.created_at <= ?", end)
			}
			// Only include Holding (1) and Shipped/Used (3) items. Exclude Void/Decomposed (2).
			query = query.Where("user_inventory.status IN ?", []int{1, 3}).
				Where("user_inventory.remark NOT LIKE ?", "%void%")

			err := query.Select(`
				user_inventory.user_id, 
				SUM(products.price) as total_value,
				SUM(CASE WHEN activities.activity_category_id = 1 THEN products.price ELSE 0 END) as ichiban_prize,
				SUM(CASE WHEN activities.activity_category_id = 2 THEN products.price ELSE 0 END) as infinite_prize,
				SUM(CASE WHEN activities.activity_category_id = 3 THEN products.price ELSE 0 END) as matching_prize
			`).
				Group("user_inventory.user_id").
				Scan(&invStats).Error

			if err == nil {
				for _, is := range invStats {
					if item, ok := statMap[is.UserID]; ok {
						item.TotalPrizeValue = is.TotalValue
						item.IchibanPrize = is.IchibanPrize
						item.InfinitePrize = is.InfinitePrize
						item.MatchingPrize = is.MatchingPrize
					}
				}
			}

			// 4.1 Calculate Livestream Prize Value (From Draw Logs)
			type lsStat struct {
				UserID int64
				Amount int64
			}
			var lsStats []lsStat
			lsQuery := db.Table(model.TableNameLivestreamDrawLogs).
				Joins("JOIN livestream_prizes ON livestream_prizes.id = livestream_draw_logs.prize_id").
				Joins("JOIN products ON products.id = livestream_prizes.product_id").
				Select("livestream_draw_logs.local_user_id as user_id, SUM(products.price) as amount").
				Where("livestream_draw_logs.local_user_id IN ?", userIDs).
				Where("livestream_draw_logs.is_refunded = 0")

			if req.RangeType != "all" {
				lsQuery = lsQuery.Where("livestream_draw_logs.created_at >= ?", start).
					Where("livestream_draw_logs.created_at <= ?", end)
			}

			if err := lsQuery.Group("livestream_draw_logs.local_user_id").Scan(&lsStats).Error; err == nil {
				for _, ls := range lsStats {
					if item, ok := statMap[ls.UserID]; ok {
						item.LivestreamPrize = ls.Amount
						// item.TotalPrizeValue += ls.Amount // Already included in user_inventory
					}
				}
			}

			// 4.2 Calculate Profit for each category
			for _, item := range statMap {
				item.IchibanProfit = item.IchibanSpending - item.IchibanPrize
				item.InfiniteProfit = item.InfiniteSpending - item.InfinitePrize
				item.MatchingProfit = item.MatchingSpending - item.MatchingPrize
				item.LivestreamProfit = item.LivestreamSpending - item.LivestreamPrize
			}
		}

		// 5. Calculate Profit and Final List
		list := make([]spendingLeaderboardItem, 0, len(statMap))
		for _, item := range statMap {
			// Calculate totals based on the 4 displayed categories to ensure UI consistency
			calculatedSpending := item.IchibanSpending + item.InfiniteSpending + item.MatchingSpending + item.LivestreamSpending
			calculatedProfit := item.IchibanProfit + item.InfiniteProfit + item.MatchingProfit + item.LivestreamProfit

			item.Profit = calculatedProfit
			if calculatedSpending > 0 {
				item.ProfitRate = float64(item.Profit) / float64(calculatedSpending)
			} else {
				item.ProfitRate = 0
			}
			list = append(list, *item)
		}

		// 6. Sort (in memory since we only have top N spenders)
		sortBy := req.SortBy
		if sortBy == "" {
			sortBy = "spending"
		}

		sort.Slice(list, func(i, j int) bool {
			switch sortBy {
			case "profit":
				return list[i].Profit > list[j].Profit // Higher profit first
			case "profit_asc":
				return list[i].Profit < list[j].Profit // Lower profit (loss) first
			default:
				return list[i].TotalSpending > list[j].TotalSpending
			}
		})

		// Pagination on the result list
		startIdx := (req.Page - 1) * req.PageSize
		if startIdx >= len(list) {
			startIdx = len(list)
		}
		endIdx := startIdx + req.PageSize
		if endIdx > len(list) {
			endIdx = len(list)
		}

		finalList := list[startIdx:endIdx]
		if finalList == nil {
			finalList = []spendingLeaderboardItem{}
		}

		ctx.Payload(&spendingLeaderboardResponse{
			Page:     req.Page,
			PageSize: req.PageSize,
			Total:    int64(len(list)), // Total of the fetched top batch
			List:     finalList,
		})
	}
}
