package handler

import (
	"net/http"
	"strings"
	"time"
	"xboard-go/internal/database"
	"xboard-go/internal/model"
	"xboard-go/internal/service"

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

func AdminDashboardSummary(c *gin.Context) {
	now := time.Now().Unix()
	todayStart := time.Now().Truncate(24 * time.Hour).Unix()
	yesterdayStart := todayStart - 86400
	currentMonthStart := time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.Local).Unix()
	lastMonthStart := time.Date(time.Now().Year(), time.Now().Month()-1, 1, 0, 0, 0, 0, time.Local).Unix()

	// 1. Basic Counts
	var totalUsers int64
	var totalOrders int64
	var pendingOrders int64
	var pendingTickets int64
	var commissionPendingTotal int64
	var onlineUsers int64
	var onlineNodes int64
	var onlineDevices int64

	database.DB.Model(&model.User{}).Count(&totalUsers)
	database.DB.Model(&model.Order{}).Count(&totalOrders)
	database.DB.Model(&model.Order{}).Where("status = ?", 0).Count(&pendingOrders)
	database.DB.Model(&model.Order{}).
		Where("status = ? AND commission_status = ?", 3, 0).
		Select("COALESCE(SUM(commission_balance), 0)").
		Scan(&commissionPendingTotal)
	database.DB.Model(&model.Ticket{}).Where("status = ?", 0).Count(&pendingTickets)
	database.DB.Model(&model.Server{}).Where("show = ?", true).Count(&onlineNodes) // Simplified online check

	// Online status (last 10 mins)
	tenMinsAgo := now - 600
	database.DB.Model(&model.User{}).Where("t >= ?", tenMinsAgo).Count(&onlineUsers)
	database.DB.Model(&model.User{}).Where("t >= ?", tenMinsAgo).Select("COALESCE(SUM(online_count), 0)").Scan(&onlineDevices)

	// 2. Income Statistics
	var todayIncome int64
	var yesterdayIncome int64
	var currentMonthIncome int64
	var lastMonthIncome int64

	database.DB.Model(&model.Order{}).Where("status NOT IN ? AND created_at >= ?", []int{0, 2}, todayStart).Select("COALESCE(SUM(total_amount), 0)").Scan(&todayIncome)
	database.DB.Model(&model.Order{}).Where("status NOT IN ? AND created_at >= ? AND created_at < ?", []int{0, 2}, yesterdayStart, todayStart).Select("COALESCE(SUM(total_amount), 0)").Scan(&yesterdayIncome)
	database.DB.Model(&model.Order{}).Where("status NOT IN ? AND created_at >= ?", []int{0, 2}, currentMonthStart).Select("COALESCE(SUM(total_amount), 0)").Scan(&currentMonthIncome)
	database.DB.Model(&model.Order{}).Where("status NOT IN ? AND created_at >= ? AND created_at < ?", []int{0, 2}, lastMonthStart, currentMonthStart).Select("COALESCE(SUM(total_amount), 0)").Scan(&lastMonthIncome)

	// 3. Traffic Statistics (using StatServer)
	type trafficSum struct {
		U     int64 `json:"upload"`
		D     int64 `json:"download"`
		Total int64 `json:"total"`
	}
	var todayTraffic trafficSum
	var monthTraffic trafficSum
	var totalTraffic trafficSum

	database.DB.Model(&model.StatServer{}).Where("record_at >= ?", todayStart).Select("COALESCE(SUM(u), 0) as u, COALESCE(SUM(d), 0) as d, COALESCE(SUM(u+d), 0) as total").Scan(&todayTraffic)
	database.DB.Model(&model.StatServer{}).Where("record_at >= ?", currentMonthStart).Select("COALESCE(SUM(u), 0) as u, COALESCE(SUM(d), 0) as d, COALESCE(SUM(u+d), 0) as total").Scan(&monthTraffic)
	database.DB.Model(&model.StatServer{}).Select("COALESCE(SUM(u), 0) as u, COALESCE(SUM(d), 0) as d, COALESCE(SUM(u+d), 0) as total").Scan(&totalTraffic)

	// 4. User Growth
	var currentMonthNewUsers int64
	var lastMonthNewUsers int64
	database.DB.Model(&model.User{}).Where("created_at >= ?", currentMonthStart).Count(&currentMonthNewUsers)
	database.DB.Model(&model.User{}).Where("created_at >= ? AND created_at < ?", lastMonthStart, currentMonthStart).Count(&lastMonthNewUsers)

	// Calculate Growth Rates
	dayIncomeGrowth := calculateGrowth(todayIncome, yesterdayIncome)
	monthIncomeGrowth := calculateGrowth(currentMonthIncome, lastMonthIncome)
	userGrowth := calculateGrowth(currentMonthNewUsers, lastMonthNewUsers)

	Success(c, gin.H{
		"server_time":            now,
		"todayIncome":            todayIncome,
		"dayIncomeGrowth":        dayIncomeGrowth,
		"currentMonthIncome":     currentMonthIncome,
		"lastMonthIncome":        lastMonthIncome,
		"monthIncomeGrowth":      monthIncomeGrowth,
		"totalOrders":            totalOrders,
		"pendingOrders":          pendingOrders,
		"currentMonthNewUsers":   currentMonthNewUsers,
		"totalUsers":             totalUsers,
		"activeUsers":            totalUsers, // Placeholder for valid subscription count
		"userGrowth":             userGrowth,
		"commissionPendingTotal": commissionPendingTotal,
		"onlineUsers":            onlineUsers,
		"onlineDevices":          onlineDevices,
		"ticketPendingTotal":     pendingTickets,
		"onlineNodes":            onlineNodes,
		"todayTraffic":           todayTraffic,
		"monthTraffic":           monthTraffic,
		"totalTraffic":           totalTraffic,
		"secure_path":            service.GetAdminSecurePath(),
		"app_name":               service.MustGetString("app_name", "XBoard"),
	})
}

func calculateGrowth(current, previous int64) float64 {
	if previous <= 0 {
		if current > 0 {
			return 100.0
		}
		return 0.0
	}
	return float64(current-previous) / float64(previous) * 100.0
}

func AdminPlansFetch(c *gin.Context) {
	var plans []model.Plan
	if err := database.DB.Order("sort ASC, id DESC").Find(&plans).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch plans")
		return
	}

	groupNames := loadServerGroupNameMap()
	items := make([]gin.H, 0, len(plans))
	for _, plan := range plans {
		items = append(items, gin.H{
			"id":                   plan.ID,
			"name":                 plan.Name,
			"group_id":             intValue(plan.GroupID),
			"group_name":           groupNames[intFromPointer(plan.GroupID)],
			"transfer_enable":      intValue(plan.TransferEnable),
			"speed_limit":          intValue(plan.SpeedLimit),
			"show":                 plan.Show,
			"sort":                 intValue(plan.Sort),
			"renew":                plan.Renew,
			"content":              stringValue(plan.Content),
			"reset_traffic_method": intValue(plan.ResetTrafficMethod),
			"capacity_limit":       intValue(plan.CapacityLimit),
			"prices":               stringValue(plan.Prices),
			"sell":                 plan.Sell,
			"device_limit":         intValue(plan.DeviceLimit),
			"tags":                 parseStringSlice(plan.Tags),
			"created_at":           plan.CreatedAt,
			"updated_at":           plan.UpdatedAt,
		})
	}

	Success(c, items)
}

func AdminPlanSave(c *gin.Context) {
	var payload map[string]any
	if err := c.ShouldBindJSON(&payload); err != nil {
		Fail(c, http.StatusBadRequest, "invalid request body")
		return
	}

	id := intFromAny(payload["id"])
	name := strings.TrimSpace(stringFromAny(payload["name"]))
	if name == "" && id == 0 {
		Fail(c, http.StatusUnprocessableEntity, "plan name is required")
		return
	}

	// For simplicity in this Go implementation, we'll map payload to model.Plan
	// In a real app, we'd use a dedicated struct for binding.
	now := time.Now().Unix()
	tags, _ := marshalJSON(payload["tags"], true)
	values := map[string]any{
		"name":                 payload["name"],
		"group_id":             payload["group_id"],
		"transfer_enable":      payload["transfer_enable"],
		"speed_limit":          payload["speed_limit"],
		"show":                 payload["show"],
		"sort":                 payload["sort"],
		"renew":                payload["renew"],
		"content":              payload["content"],
		"reset_traffic_method": payload["reset_traffic_method"],
		"capacity_limit":       payload["capacity_limit"],
		"prices":               payload["prices"],
		"sell":                 payload["sell"],
		"device_limit":         payload["device_limit"],
		"tags":                 tags,
		"updated_at":           now,
	}

	if id > 0 {
		if err := database.DB.Model(&model.Plan{}).Where("id = ?", id).Updates(values).Error; err != nil {
			Fail(c, http.StatusInternalServerError, "failed to save plan")
			return
		}
		Success(c, true)
		return
	}

	values["created_at"] = now
	if err := database.DB.Model(&model.Plan{}).Create(values).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to create plan")
		return
	}
	Success(c, true)
}

func AdminPlanDrop(c *gin.Context) {
	var payload struct {
		ID int `json:"id"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil || payload.ID <= 0 {
		Fail(c, http.StatusBadRequest, "plan id is required")
		return
	}

	// Check usage
	var orderCount int64
	database.DB.Model(&model.Order{}).Where("plan_id = ?", payload.ID).Count(&orderCount)
	if orderCount > 0 {
		Fail(c, http.StatusBadRequest, "this plan is still used by orders")
		return
	}

	var userCount int64
	database.DB.Model(&model.User{}).Where("plan_id = ?", payload.ID).Count(&userCount)
	if userCount > 0 {
		Fail(c, http.StatusBadRequest, "this plan is still used by users")
		return
	}

	if err := database.DB.Where("id = ?", payload.ID).Delete(&model.Plan{}).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to delete plan")
		return
	}
	Success(c, true)
}

func AdminPlanSort(c *gin.Context) {
	var payload struct {
		PlanIDs []int `json:"plan_ids"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil {
		Fail(c, http.StatusBadRequest, "invalid request body")
		return
	}

	tx := database.DB.Begin()
	for i, id := range payload.PlanIDs {
		if err := tx.Model(&model.Plan{}).Where("id = ?", id).Update("sort", i+1).Error; err != nil {
			tx.Rollback()
			Fail(c, http.StatusInternalServerError, "failed to sort plans")
			return
		}
	}
	tx.Commit()
	Success(c, true)
}

func AdminOrdersFetch(c *gin.Context) {
	params := getFetchParams(c)
	page := parsePositiveInt(firstString(params["page"], params["current"]), 1)
	perPage := parsePositiveInt(firstString(params["per_page"], params["pageSize"]), 50)
	keyword := strings.TrimSpace(params["keyword"])
	statusFilter := strings.TrimSpace(params["status"])

	query := database.DB.Model(&model.Order{}).Preload("Plan").Preload("Payment").Order("id DESC")
	if keyword != "" {
		query = query.Where("trade_no LIKE ? OR CAST(user_id AS CHAR) = ?", "%"+keyword+"%", keyword)
	}
	if statusFilter != "" {
		query = query.Where("status = ?", statusFilter)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to count orders")
		return
	}

	var orders []model.Order
	if err := query.Offset((page - 1) * perPage).Limit(perPage).Find(&orders).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch orders")
		return
	}

	userEmails := loadUserEmailMap(extractOrderUserIDs(orders))
	inviteEmails := loadUserEmailMap(extractOrderInviteUserIDs(orders))
	items := make([]gin.H, 0, len(orders))
	for _, order := range orders {
		item := normalizeOrder(order)
		item["user_email"] = userEmails[order.UserID]
		item["user"] = gin.H{
			"id":    order.UserID,
			"email": userEmails[order.UserID],
		}
		if order.InviteUserID != nil {
			item["invite_user"] = gin.H{
				"id":    *order.InviteUserID,
				"email": inviteEmails[*order.InviteUserID],
			}
		}
		items = append(items, item)
	}

	Success(c, gin.H{
		"list":  items,
		"data":  items,
		"total": total,
		"filters": gin.H{
			"keyword": keyword,
			"status":  statusFilter,
		},
		"pagination": gin.H{
			"current":   page,
			"last_page": calculateLastPage(total, perPage),
			"per_page":  perPage,
			"total":     total,
		},
	})
}

func AdminCouponsFetch(c *gin.Context) {
	var coupons []model.Coupon
	if err := database.DB.Order("id DESC").Find(&coupons).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch coupons")
		return
	}

	items := make([]gin.H, 0, len(coupons))
	for _, coupon := range coupons {
		items = append(items, gin.H{
			"id":                  coupon.ID,
			"name":                coupon.Name,
			"code":                coupon.Code,
			"type":                coupon.Type,
			"value":               coupon.Value,
			"limit_plan_ids":      parseStringSlice(coupon.LimitPlanIDs),
			"limit_period":        parseStringSlice(coupon.LimitPeriod),
			"limit_use":           intValue(coupon.LimitUse),
			"limit_use_with_user": intValue(coupon.LimitUseWithUser),
			"started_at":          coupon.StartedAt,
			"ended_at":            coupon.EndedAt,
			"show":                coupon.Show,
			"created_at":          coupon.CreatedAt,
			"updated_at":          coupon.UpdatedAt,
		})
	}

	Success(c, items)
}

func AdminCouponSave(c *gin.Context) {
	var payload map[string]any
	if err := c.ShouldBindJSON(&payload); err != nil {
		Fail(c, http.StatusBadRequest, "invalid request body")
		return
	}

	id := intFromAny(payload["id"])
	now := time.Now().Unix()
	limitPlanIDs, _ := marshalJSON(payload["limit_plan_ids"], true)
	limitPeriod, _ := marshalJSON(payload["limit_period"], true)
	values := map[string]any{
		"name":                payload["name"],
		"code":                payload["code"],
		"type":                payload["type"],
		"value":               payload["value"],
		"limit_plan_ids":      limitPlanIDs,
		"limit_period":        limitPeriod,
		"limit_use":           payload["limit_use"],
		"limit_use_with_user": payload["limit_use_with_user"],
		"started_at":          payload["started_at"],
		"ended_at":            payload["ended_at"],
		"show":                payload["show"],
		"updated_at":          now,
	}

	if id > 0 {
		if err := database.DB.Model(&model.Coupon{}).Where("id = ?", id).Updates(values).Error; err != nil {
			Fail(c, http.StatusInternalServerError, "failed to save coupon")
			return
		}
		Success(c, true)
		return
	}

	values["created_at"] = now
	if err := database.DB.Model(&model.Coupon{}).Create(values).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to create coupon")
		return
	}
	Success(c, true)
}

func AdminCouponDrop(c *gin.Context) {
	var payload struct {
		ID int `json:"id"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil || payload.ID <= 0 {
		Fail(c, http.StatusBadRequest, "coupon id is required")
		return
	}

	if err := database.DB.Where("id = ?", payload.ID).Delete(&model.Coupon{}).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to delete coupon")
		return
	}
	Success(c, true)
}

func AdminOrderPaid(c *gin.Context) {
	var payload struct {
		TradeNo string `json:"trade_no"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil || payload.TradeNo == "" {
		Fail(c, http.StatusBadRequest, "trade_no is required")
		return
	}

	var order model.Order
	if err := database.DB.Preload("Plan").Where("trade_no = ?", payload.TradeNo).First(&order).Error; err != nil {
		Fail(c, http.StatusBadRequest, "order does not exist")
		return
	}
	if order.Status != 0 {
		Fail(c, http.StatusBadRequest, "order status is not pending")
		return
	}

	now := time.Now().Unix()
	tx := database.DB.Begin()

	// Update order
	if err := tx.Model(&model.Order{}).Where("id = ?", order.ID).Updates(map[string]any{
		"status":  3,
		"paid_at": now,
	}).Error; err != nil {
		tx.Rollback()
		Fail(c, http.StatusInternalServerError, "failed to update order")
		return
	}

	updates := map[string]any{
		"plan_id":    order.PlanID,
		"updated_at": now,
	}
	if order.Plan != nil {
		updates["transfer_enable"] = order.Plan.TransferEnable
	}
	if err := tx.Model(&model.User{}).Where("id = ?", order.UserID).Updates(updates).Error; err != nil {
		tx.Rollback()
		Fail(c, http.StatusInternalServerError, "failed to update user")
		return
	}

	tx.Commit()
	Success(c, true)
}

func AdminOrderCancel(c *gin.Context) {
	var payload struct {
		TradeNo string `json:"trade_no"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil || payload.TradeNo == "" {
		Fail(c, http.StatusBadRequest, "trade_no is required")
		return
	}

	if err := database.DB.Model(&model.Order{}).Where("trade_no = ?", payload.TradeNo).Update("status", 2).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to cancel order")
		return
	}
	Success(c, true)
}

func AdminUserResetTraffic(c *gin.Context) {
	var payload struct {
		ID int `json:"id"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil || payload.ID <= 0 {
		Fail(c, http.StatusBadRequest, "user id is required")
		return
	}

	if err := database.DB.Model(&model.User{}).Where("id = ?", payload.ID).Updates(map[string]any{
		"u": 0,
		"d": 0,
	}).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to reset traffic")
		return
	}
	Success(c, true)
}

func AdminUserBan(c *gin.Context) {
	var payload struct {
		ID     int  `json:"id"`
		Banned bool `json:"banned"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil || payload.ID <= 0 {
		Fail(c, http.StatusBadRequest, "user id is required")
		return
	}

	if err := database.DB.Model(&model.User{}).Where("id = ?", payload.ID).Update("banned", payload.Banned).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to update user status")
		return
	}
	Success(c, true)
}

func AdminUserDelete(c *gin.Context) {
	var payload struct {
		ID int `json:"id"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil || payload.ID <= 0 {
		Fail(c, http.StatusBadRequest, "user id is required")
		return
	}

	if err := database.DB.Where("id = ?", payload.ID).Delete(&model.User{}).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to delete user")
		return
	}
	Success(c, true)
}

func AdminUserUpdate(c *gin.Context) {
	var payload map[string]any
	if err := c.ShouldBindJSON(&payload); err != nil {
		Fail(c, http.StatusBadRequest, "invalid request body")
		return
	}

	id := intFromAny(payload["id"])
	if id <= 0 {
		Fail(c, http.StatusBadRequest, "user id is required")
		return
	}

	now := time.Now().Unix()
	values := map[string]any{
		"email":              payload["email"],
		"password":           payload["password"],
		"balance":            payload["balance"],
		"commission_type":    payload["commission_type"],
		"commission_rate":    payload["commission_rate"],
		"commission_balance": payload["commission_balance"],
		"group_id":           payload["group_id"],
		"plan_id":            payload["plan_id"],
		"speed_limit":        payload["speed_limit"],
		"device_limit":       payload["device_limit"],
		"expired_at":         payload["expired_at"],
		"remarks":            payload["remarks"],
		"updated_at":         now,
	}

	// Remove nil values to avoid overwriting with defaults if not provided
	for k, v := range values {
		if v == nil {
			delete(values, k)
		}
	}

	if err := database.DB.Model(&model.User{}).Where("id = ?", id).Updates(values).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to update user")
		return
	}

	// Sync password to children (IPv6 sub-users) if changed
	if pwd, ok := values["password"].(string); ok && pwd != "" {
		database.DB.Model(&model.User{}).Where("parent_id = ?", id).Update("password", pwd)
	}

	Success(c, true)
}

func AdminUsersFetch(c *gin.Context) {
	params := getFetchParams(c)
	page := parsePositiveInt(firstString(params["page"], params["current"]), 1)
	perPage := parsePositiveInt(firstString(params["per_page"], params["pageSize"]), 50)
	keyword := strings.TrimSpace(params["keyword"])
	suffix := service.GetPluginConfigString(service.PluginUserAddIPv6, "email_suffix", "-ipv6")
	shadowPattern := "%" + suffix + "@%"

	query := database.DB.Model(&model.User{}).
		Preload("Plan").
		Where("email NOT LIKE ?", shadowPattern).
		Order("id DESC")
	if keyword != "" {
		query = query.Where("email LIKE ? OR CAST(id AS CHAR) = ?", "%"+keyword+"%", keyword)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to count users")
		return
	}

	var users []model.User
	if err := query.Offset((page - 1) * perPage).Limit(perPage).Find(&users).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch users")
		return
	}

	userIDs := make([]int, 0, len(users))
	for _, user := range users {
		userIDs = append(userIDs, user.ID)
	}
	deviceMap := service.GetUsersDevices(userIDs)

	realnameStatusByUserID := make(map[int]string, len(userIDs))
	if len(userIDs) > 0 {
		var records []model.RealNameAuth
		if err := database.DB.Select("user_id", "status").Where("user_id IN ?", userIDs).Find(&records).Error; err == nil {
			for _, record := range records {
				realnameStatusByUserID[int(record.UserID)] = record.Status
			}
		}
	}

	shadowByParentID := make(map[int]model.User, len(userIDs))
	if len(userIDs) > 0 {
		var shadowUsers []model.User
		if err := database.DB.Select("id", "parent_id", "email").Where("parent_id IN ?", userIDs).Find(&shadowUsers).Error; err == nil {
			for _, shadow := range shadowUsers {
				if shadow.ParentID != nil {
					shadowByParentID[*shadow.ParentID] = shadow
				}
			}
		}
	}

	groupNames := loadServerGroupNameMap()
	items := make([]gin.H, 0, len(users))
	for _, user := range users {
		onlineIP := ""
		if ips, ok := deviceMap[user.ID]; ok && len(ips) > 0 {
			onlineIP = strings.Join(ips, ", ")
		}

		realnameStatus := realnameStatusByUserID[user.ID]
		if realnameStatus == "" {
			realnameStatus = "unverified"
		}

		ipv6Shadow, hasIPv6Shadow := shadowByParentID[user.ID]
		ipv6ShadowID := 0
		if hasIPv6Shadow {
			ipv6ShadowID = ipv6Shadow.ID
		}

		items = append(items, gin.H{
			"id":             user.ID,
			"email":          user.Email,
			"parent_id":      intValue(user.ParentID),
			"is_shadow_user": user.ParentID != nil,
			"balance":        user.Balance,
			"uuid":           user.UUID,
			"token":          user.Token,
			"group_id":   intValue(user.GroupID),
			"group_name": groupNames[intFromPointer(user.GroupID)],
			"group": gin.H{
				"id":   intValue(user.GroupID),
				"name": groupNames[intFromPointer(user.GroupID)],
			},
			"plan_id":   intValue(user.PlanID),
			"plan_name": planName(user.Plan),
			"plan": gin.H{
				"id":   intValue(user.PlanID),
				"name": planName(user.Plan),
			},
			"transfer_enable":    user.TransferEnable,
			"u":                  user.U,
			"d":                  user.D,
			"total_used":         user.U + user.D,
			"banned":             user.Banned,
			"is_admin":           user.IsAdmin,
			"is_staff":           user.IsStaff,
			"device_limit":       intValue(user.DeviceLimit),
			"online_count":       intValue(user.OnlineCount),
			"expired_at":         int64Value(user.ExpiredAt),
			"next_reset_at":      int64Value(user.NextResetAt),
			"last_login_at":      int64Value(user.LastLoginAt),
			"last_login_ip":      user.LastLoginIP,
			"online_ip":          onlineIP,
			"online_ip_count":    len(deviceMap[user.ID]),
			"realname_status":    realnameStatus,
			"realname_label":     realNameStatusLabel(realnameStatus),
			"ipv6_shadow_id":     ipv6ShadowID,
			"ipv6_shadow_email":  firstString(ipv6Shadow.Email, service.IPv6ShadowEmail(user.Email)),
			"ipv6_enabled":       hasIPv6Shadow,
			"created_at":         user.CreatedAt,
			"updated_at":         user.UpdatedAt,
			"remarks":            stringValue(user.Remarks),
			"commission_type":    user.CommissionType,
			"commission_rate":    intValue(user.CommissionRate),
			"commission_balance": user.CommissionBalance,
		})
	}

	c.JSON(http.StatusOK, gin.H{
		"list":  items,
		"data":  items,
		"total": total,
		"filters": gin.H{
			"keyword": keyword,
		},
		"pagination": gin.H{
			"current":   page,
			"last_page": calculateLastPage(total, perPage),
			"per_page":  perPage,
			"total":     total,
		},
	})
}

func AdminTicketsFetch(c *gin.Context) {
	params := getFetchParams(c)
	if ticketID := strings.TrimSpace(params["id"]); ticketID != "" {
		adminTicketDetail(c, ticketID)
		return
	}

	page := parsePositiveInt(params["page"], 1)
	perPage := parsePositiveInt(params["per_page"], 50)
	keyword := strings.TrimSpace(params["keyword"])

	query := database.DB.Model(&model.Ticket{}).Order("updated_at DESC, id DESC")
	if keyword != "" {
		query = query.Where("subject LIKE ? OR CAST(user_id AS CHAR) = ? OR CAST(id AS CHAR) = ?", "%"+keyword+"%", keyword, keyword)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to count tickets")
		return
	}

	var tickets []model.Ticket
	if err := query.Offset((page - 1) * perPage).Limit(perPage).Find(&tickets).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch tickets")
		return
	}

	userEmails := loadUserEmailMap(extractTicketUserIDs(tickets))
	messageCounts := loadTicketMessageCountMap(extractTicketIDs(tickets))
	items := make([]gin.H, 0, len(tickets))
	for _, ticket := range tickets {
		items = append(items, gin.H{
			"id":            ticket.ID,
			"user_id":       ticket.UserID,
			"user_email":    userEmails[ticket.UserID],
			"subject":       ticket.Subject,
			"level":         ticket.Level,
			"status":        ticket.Status,
			"reply_status":  ticket.ReplyStatus,
			"message_count": messageCounts[ticket.ID],
			"created_at":    ticket.CreatedAt,
			"updated_at":    ticket.UpdatedAt,
		})
	}

	Success(c, gin.H{
		"list":  items,
		"data":  items,
		"total": total,
		"filters": gin.H{
			"keyword": keyword,
		},
		"pagination": gin.H{
			"current":   page,
			"last_page": calculateLastPage(total, perPage),
			"per_page":  perPage,
			"total":     total,
		},
	})
}

func AdminGiftCardStatus(c *gin.Context) {
	Success(c, gin.H{
		"supported": false,
		"status":    "not_integrated",
		"message":   "Gift card management is not fully integrated in the current Go backend yet.",
	})
}

func adminTicketDetail(c *gin.Context, rawID string) {
	var ticket model.Ticket
	if err := database.DB.Where("id = ?", rawID).First(&ticket).Error; err != nil {
		Fail(c, http.StatusNotFound, "ticket not found")
		return
	}

	var messages []model.TicketMessage
	if err := database.DB.Where("ticket_id = ?", ticket.ID).Order("id ASC").Find(&messages).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch ticket messages")
		return
	}

	userEmails := loadUserEmailMap([]int{ticket.UserID})
	Success(c, gin.H{
		"id":           ticket.ID,
		"user_id":      ticket.UserID,
		"user_email":   userEmails[ticket.UserID],
		"subject":      ticket.Subject,
		"level":        ticket.Level,
		"status":       ticket.Status,
		"reply_status": ticket.ReplyStatus,
		"created_at":   ticket.CreatedAt,
		"updated_at":   ticket.UpdatedAt,
		"messages":     buildTicketMessages(messages, 0),
	})
}

func extractOrderUserIDs(orders []model.Order) []int {
	ids := make([]int, 0, len(orders))
	for _, order := range orders {
		ids = append(ids, order.UserID)
	}
	return ids
}

func extractOrderInviteUserIDs(orders []model.Order) []int {
	ids := make([]int, 0, len(orders))
	for _, order := range orders {
		if order.InviteUserID != nil {
			ids = append(ids, *order.InviteUserID)
		}
	}
	return ids
}

func extractTicketUserIDs(tickets []model.Ticket) []int {
	ids := make([]int, 0, len(tickets))
	for _, ticket := range tickets {
		ids = append(ids, ticket.UserID)
	}
	return ids
}

func extractTicketIDs(tickets []model.Ticket) []int {
	ids := make([]int, 0, len(tickets))
	for _, ticket := range tickets {
		ids = append(ids, ticket.ID)
	}
	return ids
}

func intFromPointer(value *int) int {
	if value == nil {
		return 0
	}
	return *value
}

func planName(plan *model.Plan) string {
	if plan == nil {
		return ""
	}
	return plan.Name
}
