package handler

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
	"xboard-go/internal/database"
	"xboard-go/internal/model"
	"xboard-go/internal/service"
	"xboard-go/pkg/utils"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func UserCommConfig(c *gin.Context) {
	data := buildGuestConfig()
	data["is_telegram"] = boolToInt(service.MustGetBool("telegram_bot_enable", false))
	data["telegram_discuss_link"] = service.MustGetString("telegram_discuss_link", "")
	data["stripe_pk"] = service.MustGetString("stripe_pk_live", "")
	data["withdraw_methods"] = service.MustGetString("commission_withdraw_method", "[]")
	data["withdraw_close"] = boolToInt(service.MustGetBool("withdraw_close_enable", false))
	data["currency"] = service.MustGetString("currency", "CNY")
	data["currency_symbol"] = service.MustGetString("currency_symbol", "CNY")
	data["commission_distribution_enable"] = boolToInt(service.MustGetBool("commission_distribution_enable", false))
	data["commission_distribution_l1"] = service.MustGetString("commission_distribution_l1", "0")
	data["commission_distribution_l2"] = service.MustGetString("commission_distribution_l2", "0")
	data["commission_distribution_l3"] = service.MustGetString("commission_distribution_l3", "0")
	Success(c, data)
}

func UserTransfer(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	var req struct {
		TransferAmount int64 `json:"transfer_amount"`
	}
	if err := c.ShouldBindJSON(&req); err != nil || req.TransferAmount <= 0 {
		Fail(c, http.StatusBadRequest, "invalid transfer amount")
		return
	}

	err := database.DB.Transaction(func(tx *gorm.DB) error {
		var locked model.User
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", user.ID).First(&locked).Error; err != nil {
			return err
		}
		if req.TransferAmount > int64(locked.CommissionBalance) {
			return errors.New("insufficient commission balance")
		}
		updates := map[string]any{
			"commission_balance": int64(locked.CommissionBalance) - req.TransferAmount,
			"balance":            int64(locked.Balance) + req.TransferAmount,
			"updated_at":         time.Now().Unix(),
		}
		return tx.Model(&model.User{}).Where("id = ?", locked.ID).Updates(updates).Error
	})
	if err != nil {
		Fail(c, http.StatusBadRequest, err.Error())
		return
	}

	Success(c, true)
}

func UserGetQuickLoginURL(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}
	Success(c, quickLoginURL(c, user.ID, c.PostForm("redirect"), c.Query("redirect")))
}

func PassportGetQuickLoginURL(c *gin.Context) {
	token := strings.TrimSpace(c.GetHeader("Authorization"))
	if token == "" {
		token = strings.TrimSpace(c.PostForm("auth_data"))
	}
	if token == "" {
		var req struct {
			AuthData string `json:"auth_data"`
			Redirect string `json:"redirect"`
		}
		_ = c.ShouldBindJSON(&req)
		token = strings.TrimSpace(req.AuthData)
		if token != "" {
			c.Set("quick_login_redirect", req.Redirect)
		}
	}
	token = strings.TrimSpace(strings.TrimPrefix(token, "Bearer "))
	if token == "" {
		Fail(c, http.StatusUnauthorized, "authorization is required")
		return
	}

	claims, err := utils.VerifyToken(token)
	if err != nil {
		Fail(c, http.StatusUnauthorized, "token expired or invalid")
		return
	}

	redirect := strings.TrimSpace(c.Query("redirect"))
	if redirect == "" {
		redirect = strings.TrimSpace(c.PostForm("redirect"))
	}
	if redirect == "" {
		if value, exists := c.Get("quick_login_redirect"); exists {
			redirect, _ = value.(string)
		}
	}

	Success(c, quickLoginURL(c, claims.UserID, redirect))
}

func PassportLoginWithMailLink(c *gin.Context) {
	var req struct {
		Email    string `json:"email" binding:"required,email"`
		Redirect string `json:"redirect"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, http.StatusBadRequest, "invalid request body")
		return
	}

	var user model.User
	if err := database.DB.Where("email = ?", strings.ToLower(strings.TrimSpace(req.Email))).First(&user).Error; err != nil {
		Fail(c, http.StatusBadRequest, "user not found")
		return
	}

	Success(c, gin.H{
		"url":        quickLoginURL(c, user.ID, req.Redirect),
		"email":      user.Email,
		"expires_in": 900,
	})
}

func PassportPV(c *gin.Context) {
	code := strings.TrimSpace(c.PostForm("invite_code"))
	if code == "" {
		var req struct {
			InviteCode string `json:"invite_code"`
		}
		_ = c.ShouldBindJSON(&req)
		code = strings.TrimSpace(req.InviteCode)
	}
	if code != "" {
		_ = database.DB.Model(&model.InviteCode{}).
			Where("code = ?", code).
			UpdateColumn("pv", gorm.Expr("pv + ?", 1)).Error
	}
	Success(c, true)
}

func UserNoticeFetch(c *gin.Context) {
	current := parsePositiveInt(c.DefaultQuery("current", "1"), 1)
	pageSize := 5

	query := database.DB.Model(&model.Notice{}).Where("`show` = ?", 1).Order("sort ASC").Order("id DESC")
	var total int64
	query.Count(&total)

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

	Success(c, gin.H{
		"data":  notices,
		"total": total,
	})
}

func UserTrafficLog(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	now := time.Now()
	startDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).Unix()

	var records []model.StatUser
	if err := database.DB.Where("user_id = ? AND record_at >= ?", user.ID, startDate).
		Order("record_at DESC").
		Find(&records).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch traffic log")
		return
	}

	items := make([]gin.H, 0, len(records))
	for _, record := range records {
		items = append(items, gin.H{
			"user_id":     record.UserID,
			"u":           record.U,
			"d":           record.D,
			"record_at":   record.RecordAt,
			"server_rate": 1,
		})
	}
	Success(c, items)
}

func UserInviteSave(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	limit := service.MustGetInt("invite_gen_limit", 5)
	var count int64
	database.DB.Model(&model.InviteCode{}).
		Where("user_id = ? AND status = ?", user.ID, 0).
		Count(&count)
	if int(count) >= limit {
		Fail(c, http.StatusBadRequest, "the maximum number of creations has been reached")
		return
	}

	inviteCode := model.InviteCode{
		UserID:    user.ID,
		Code:      randomAlphaNum(8),
		Status:    false,
		CreatedAt: time.Now().Unix(),
		UpdatedAt: time.Now().Unix(),
	}
	if err := database.DB.Create(&inviteCode).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to create invite code")
		return
	}

	Success(c, true)
}

func UserInviteFetch(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	commissionRate := service.MustGetInt("invite_commission", 10)
	if user.CommissionRate != nil {
		commissionRate = *user.CommissionRate
	}

	var codes []model.InviteCode
	_ = database.DB.Where("user_id = ? AND status = ?", user.ID, 0).Order("id DESC").Find(&codes).Error

	var totalInvited int64
	var confirmedCommission int64
	var pendingCommission int64
	database.DB.Model(&model.User{}).Where("invite_user_id = ?", user.ID).Count(&totalInvited)
	database.DB.Model(&model.CommissionLog{}).Where("invite_user_id = ? AND get_amount > 0", user.ID).Select("COALESCE(SUM(get_amount), 0)").Scan(&confirmedCommission)
	database.DB.Model(&model.Order{}).Where("status = ? AND commission_status = ? AND invite_user_id = ?", 3, 0, user.ID).Select("COALESCE(SUM(commission_balance), 0)").Scan(&pendingCommission)

	if service.MustGetBool("commission_distribution_enable", false) {
		pendingCommission = int64(float64(pendingCommission) * float64(service.MustGetInt("commission_distribution_l1", 100)) / 100)
	}

	Success(c, gin.H{
		"codes": codes,
		"stat": []int64{
			totalInvited,
			confirmedCommission,
			pendingCommission,
			int64(commissionRate),
			int64(user.CommissionBalance),
		},
	})
}

func UserInviteDetails(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	current := parsePositiveInt(c.DefaultQuery("current", "1"), 1)
	pageSize := parsePositiveInt(c.DefaultQuery("page_size", "10"), 10)
	if pageSize < 10 {
		pageSize = 10
	}

	query := database.DB.Model(&model.CommissionLog{}).
		Where("invite_user_id = ? AND get_amount > 0", user.ID).
		Order("created_at DESC")

	var total int64
	query.Count(&total)

	var logs []model.CommissionLog
	if err := query.Offset((current - 1) * pageSize).Limit(pageSize).Find(&logs).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch invite details")
		return
	}

	items := make([]gin.H, 0, len(logs))
	for _, item := range logs {
		items = append(items, gin.H{
			"id":           item.ID,
			"order_amount": item.OrderAmount,
			"trade_no":     item.TradeNo,
			"get_amount":   item.GetAmount,
			"created_at":   item.CreatedAt,
		})
	}

	Success(c, gin.H{
		"data":  items,
		"total": total,
	})
}

func UserOrderFetch(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	query := database.DB.Preload("Plan").Preload("Payment").Where("user_id = ?", user.ID).Order("created_at DESC")
	if status := strings.TrimSpace(c.Query("status")); status != "" {
		query = query.Where("status = ?", status)
	}

	var orders []model.Order
	if err := query.Find(&orders).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch orders")
		return
	}
	Success(c, normalizeOrders(orders))
}

func UserOrderDetail(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	tradeNo := strings.TrimSpace(c.Query("trade_no"))
	if tradeNo == "" {
		Fail(c, http.StatusBadRequest, "trade_no is required")
		return
	}

	var order model.Order
	if err := database.DB.Preload("Plan").Preload("Payment").
		Where("user_id = ? AND trade_no = ?", user.ID, tradeNo).
		First(&order).Error; err != nil {
		Fail(c, http.StatusBadRequest, "order does not exist or has been paid")
		return
	}
	if order.Plan == nil && order.PlanID != nil {
		Fail(c, http.StatusBadRequest, "subscription plan does not exist")
		return
	}

	data := normalizeOrder(order)
	data["try_out_plan_id"] = service.MustGetInt("try_out_plan_id", 0)
	if ids := parseIntSlice(order.SurplusOrderIDs); len(ids) > 0 {
		var surplusOrders []model.Order
		_ = database.DB.Where("id IN ?", ids).Order("id DESC").Find(&surplusOrders).Error
		data["surplus_orders"] = normalizeOrders(surplusOrders)
	}
	Success(c, data)
}

func UserOrderCheck(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	tradeNo := strings.TrimSpace(c.Query("trade_no"))
	if tradeNo == "" {
		Fail(c, http.StatusBadRequest, "trade_no is required")
		return
	}

	var order model.Order
	if err := database.DB.Select("status").Where("user_id = ? AND trade_no = ?", user.ID, tradeNo).First(&order).Error; err != nil {
		Fail(c, http.StatusBadRequest, "order does not exist")
		return
	}

	Success(c, order.Status)
}

func UserOrderGetPaymentMethod(c *gin.Context) {
	var methods []model.Payment
	if err := database.DB.Select("id", "name", "payment", "icon", "handling_fee_fixed", "handling_fee_percent").
		Where("enable = ?", 1).
		Order("sort ASC").
		Find(&methods).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch payment methods")
		return
	}
	Success(c, methods)
}

func UserOrderCancel(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	var req struct {
		TradeNo string `json:"trade_no"`
	}
	if err := c.ShouldBindJSON(&req); err != nil || strings.TrimSpace(req.TradeNo) == "" {
		Fail(c, http.StatusUnprocessableEntity, "invalid parameter")
		return
	}

	var order model.Order
	if err := database.DB.Where("trade_no = ? AND user_id = ?", req.TradeNo, user.ID).First(&order).Error; err != nil {
		Fail(c, http.StatusBadRequest, "order does not exist")
		return
	}
	if order.Status != 0 {
		Fail(c, http.StatusBadRequest, "you can only cancel pending orders")
		return
	}

	if err := database.DB.Model(&model.Order{}).Where("id = ?", order.ID).
		Updates(map[string]any{"status": 2, "updated_at": time.Now().Unix()}).Error; err != nil {
		Fail(c, http.StatusBadRequest, "cancel failed")
		return
	}
	Success(c, true)
}

func UserOrderSave(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	var req struct {
		PlanID     int    `json:"plan_id" binding:"required"`
		Period     string `json:"period" binding:"required"`
		CouponCode string `json:"coupon_code"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, http.StatusBadRequest, "invalid request body")
		return
	}

	var pendingCount int64
	database.DB.Model(&model.Order{}).Where("user_id = ? AND status IN ?", user.ID, []int{0, 1}).Count(&pendingCount)
	if pendingCount > 0 {
		Fail(c, http.StatusBadRequest, "you have an unpaid or pending order, please try again later or cancel it")
		return
	}

	var plan model.Plan
	if err := database.DB.First(&plan, req.PlanID).Error; err != nil {
		Fail(c, http.StatusBadRequest, "subscription plan does not exist")
		return
	}

	totalAmount, ok := resolvePlanPrice(&plan, req.Period)
	if !ok {
		Fail(c, http.StatusBadRequest, "the period is not available for this subscription")
		return
	}

	orderType := 1
	if user.PlanID != nil {
		orderType = 2
		if *user.PlanID != req.PlanID {
			orderType = 3
		}
	}

	order := model.Order{
		UserID:       user.ID,
		PlanID:       &req.PlanID,
		Period:       req.Period,
		TradeNo:      generateTradeNo(),
		TotalAmount:  totalAmount,
		Type:         orderType,
		Status:       0,
		InviteUserID: user.InviteUserID,
		CreatedAt:    time.Now().Unix(),
		UpdatedAt:    time.Now().Unix(),
	}

	if couponCode := strings.TrimSpace(req.CouponCode); couponCode != "" {
		coupon, discount, err := validateCoupon(couponCode, req.PlanID, user.ID, req.Period)
		if err != nil {
			Fail(c, http.StatusBadRequest, err.Error())
			return
		}
		order.CouponID = &coupon.ID
		order.DiscountAmount = &discount
		order.TotalAmount -= discount
		if order.TotalAmount < 0 {
			order.TotalAmount = 0
		}
	}

	if err := database.DB.Create(&order).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to create order")
		return
	}
	Success(c, order.TradeNo)
}

func UserOrderCheckout(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	var req struct {
		TradeNo string `json:"trade_no" binding:"required"`
		Method  int    `json:"method"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, http.StatusBadRequest, "invalid request body")
		return
	}

	var order model.Order
	if err := database.DB.Where("trade_no = ? AND user_id = ? AND status = ?", req.TradeNo, user.ID, 0).First(&order).Error; err != nil {
		Fail(c, http.StatusBadRequest, "order does not exist or has been paid")
		return
	}

	if order.TotalAmount <= 0 {
		now := time.Now().Unix()
		if err := database.DB.Model(&model.Order{}).Where("id = ?", order.ID).
			Updates(map[string]any{"status": 3, "paid_at": now, "updated_at": now}).Error; err != nil {
			Fail(c, http.StatusBadRequest, "payment failed")
			return
		}
		c.JSON(http.StatusOK, gin.H{"type": -1, "data": true})
		return
	}

	var payment model.Payment
	if err := database.DB.Where("id = ? AND enable = ?", req.Method, 1).First(&payment).Error; err != nil {
		Fail(c, http.StatusBadRequest, "payment method is not available")
		return
	}

	var handlingAmount int64
	if payment.HandlingFeePercent != nil {
		handlingAmount += (order.TotalAmount * *payment.HandlingFeePercent) / 100
	}
	if payment.HandlingFeeFixed != nil {
		handlingAmount += *payment.HandlingFeeFixed
	}

	updates := map[string]any{
		"payment_id":      payment.ID,
		"updated_at":      time.Now().Unix(),
		"handling_amount": handlingAmount,
	}
	if err := database.DB.Model(&model.Order{}).Where("id = ?", order.ID).Updates(updates).Error; err != nil {
		Fail(c, http.StatusBadRequest, "request failed, please try again later")
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"type": 0,
		"data": gin.H{
			"trade_no":      order.TradeNo,
			"payment_id":    payment.ID,
			"payment":       payment.Payment,
			"total_amount":  order.TotalAmount + handlingAmount,
			"message":       "payment method recorded; external gateway callback is not required for local validation flows",
			"handling_fee":  handlingAmount,
			"requires_poll": true,
		},
	})
}

func UserCouponCheck(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusUnauthorized, "user not found")
		return
	}

	var req struct {
		Code   string `json:"code"`
		PlanID int    `json:"plan_id"`
		Period string `json:"period"`
	}
	if err := c.ShouldBindJSON(&req); err != nil || strings.TrimSpace(req.Code) == "" {
		Fail(c, http.StatusUnprocessableEntity, "coupon cannot be empty")
		return
	}

	coupon, _, err := validateCoupon(req.Code, req.PlanID, user.ID, req.Period)
	if err != nil {
		Fail(c, http.StatusBadRequest, err.Error())
		return
	}

	Success(c, 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),
		"started_at":     coupon.StartedAt,
		"ended_at":       coupon.EndedAt,
		"show":           coupon.Show,
	})
}

func UserGiftCardCheck(c *gin.Context) {
	Fail(c, http.StatusBadRequest, "gift card integration is not enabled in the current Go backend")
}

func UserGiftCardRedeem(c *gin.Context) {
	Fail(c, http.StatusBadRequest, "gift card integration is not enabled in the current Go backend")
}

func UserGiftCardHistory(c *gin.Context) {
	page := parsePositiveInt(c.DefaultQuery("page", "1"), 1)
	perPage := parsePositiveInt(c.DefaultQuery("per_page", "15"), 15)
	Success(c, gin.H{
		"data": []any{},
		"pagination": gin.H{
			"current_page": page,
			"last_page":    1,
			"per_page":     perPage,
			"total":        0,
		},
	})
}

func UserGiftCardDetail(c *gin.Context) {
	Fail(c, http.StatusNotFound, "record does not exist")
}

func UserGiftCardTypes(c *gin.Context) {
	Success(c, gin.H{
		"types": map[int]string{
			1: "general",
			2: "plan",
			3: "mystery",
		},
	})
}

func UserTelegramBotInfo(c *gin.Context) {
	link := service.MustGetString("telegram_discuss_link", "")
	username := extractTelegramUsername(link)
	Success(c, gin.H{
		"username": username,
		"link":     link,
		"enabled":  service.MustGetBool("telegram_bot_enable", false),
	})
}

func UserGetStripePublicKey(c *gin.Context) {
	var req struct {
		ID int `json:"id"`
	}
	_ = c.ShouldBindJSON(&req)

	if req.ID > 0 {
		var payment model.Payment
		if err := database.DB.Where("id = ? AND payment = ?", req.ID, "StripeCredit").First(&payment).Error; err == nil {
			if value := parseConfigString(payment.Config, "stripe_pk_live"); value != "" {
				Success(c, value)
				return
			}
		}
	}

	if pk := service.MustGetString("stripe_pk_live", ""); pk != "" {
		Success(c, pk)
		return
	}
	Fail(c, http.StatusBadRequest, "payment is not found")
}

func quickLoginURL(c *gin.Context, userID int, redirectValues ...string) string {
	redirect := ""
	for _, candidate := range redirectValues {
		candidate = strings.TrimSpace(candidate)
		if candidate != "" {
			redirect = candidate
			break
		}
	}

	verify := service.StoreQuickLoginToken(userID, 15*time.Minute)
	query := url.Values{}
	query.Set("verify", verify)
	if redirect != "" {
		query.Set("redirect", redirect)
	}
	return baseURL(c) + "/api/v1/passport/auth/token2Login?" + query.Encode()
}

func baseURL(c *gin.Context) string {
	if baseURL := requestBaseURL(c); baseURL != "" {
		return baseURL
	}
	return ""
}

func generateTradeNo() string {
	buf := make([]byte, 8)
	if _, err := rand.Read(buf); err != nil {
		return strconv.FormatInt(time.Now().UnixNano(), 10)
	}
	return fmt.Sprintf("%d%s", time.Now().Unix(), strings.ToUpper(hex.EncodeToString(buf)))
}

func randomAlphaNum(length int) string {
	if length <= 0 {
		return ""
	}
	const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
	buf := make([]byte, length)
	random := make([]byte, length)
	if _, err := rand.Read(random); err != nil {
		return generateTradeNo()[:length]
	}
	for i := range buf {
		buf[i] = alphabet[int(random[i])%len(alphabet)]
	}
	return string(buf)
}

func resolvePlanPrice(plan *model.Plan, period string) (int64, bool) {
	if plan == nil || plan.Prices == nil || strings.TrimSpace(*plan.Prices) == "" {
		return 0, false
	}

	var raw map[string]any
	if err := json.Unmarshal([]byte(*plan.Prices), &raw); err != nil {
		return 0, false
	}

	value, ok := raw[period]
	if !ok {
		return 0, false
	}

	switch typed := value.(type) {
	case float64:
		return int64(typed), true
	case int:
		return int64(typed), true
	case int64:
		return typed, true
	case string:
		parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64)
		if err != nil {
			return 0, false
		}
		return parsed, true
	default:
		return 0, false
	}
}

func validateCoupon(code string, planID, userID int, period string) (*model.Coupon, int64, error) {
	var coupon model.Coupon
	if err := database.DB.Where("code = ?", strings.TrimSpace(code)).First(&coupon).Error; err != nil {
		return nil, 0, errors.New("invalid coupon")
	}
	if !coupon.Show {
		return nil, 0, errors.New("invalid coupon")
	}
	now := time.Now().Unix()
	if coupon.LimitUse != nil && *coupon.LimitUse <= 0 {
		return nil, 0, errors.New("this coupon is no longer available")
	}
	if coupon.StartedAt > 0 && now < coupon.StartedAt {
		return nil, 0, errors.New("this coupon has not yet started")
	}
	if coupon.EndedAt > 0 && now > coupon.EndedAt {
		return nil, 0, errors.New("this coupon has expired")
	}
	if planID > 0 && len(parseIntSlice(coupon.LimitPlanIDs)) > 0 && !containsInt(parseIntSlice(coupon.LimitPlanIDs), planID) {
		return nil, 0, errors.New("the coupon code cannot be used for this subscription")
	}
	if period != "" && len(parseStringSlice(coupon.LimitPeriod)) > 0 && !containsString(parseStringSlice(coupon.LimitPeriod), period) {
		return nil, 0, errors.New("the coupon code cannot be used for this period")
	}
	if coupon.LimitUseWithUser != nil && userID > 0 {
		var usedCount int64
		database.DB.Model(&model.Order{}).
			Where("coupon_id = ? AND user_id = ? AND status NOT IN ?", coupon.ID, userID, []int{0, 2}).
			Count(&usedCount)
		if usedCount >= int64(*coupon.LimitUseWithUser) {
			return nil, 0, fmt.Errorf("the coupon can only be used %d times per person", *coupon.LimitUseWithUser)
		}
	}

	var discount int64
	switch coupon.Type {
	case 1:
		discount = coupon.Value
	case 2:
		var plan model.Plan
		if err := database.DB.First(&plan, planID).Error; err != nil {
			return nil, 0, errors.New("subscription plan does not exist")
		}
		total, ok := resolvePlanPrice(&plan, period)
		if !ok {
			return nil, 0, errors.New("the period is not available for this subscription")
		}
		discount = (total * coupon.Value) / 100
	default:
		discount = 0
	}

	return &coupon, discount, nil
}

func normalizeOrders(orders []model.Order) []gin.H {
	items := make([]gin.H, 0, len(orders))
	for _, order := range orders {
		items = append(items, normalizeOrder(order))
	}
	return items
}

func normalizeOrder(order model.Order) gin.H {
	data := gin.H{
		"id":                        order.ID,
		"user_id":                   order.UserID,
		"plan_id":                   order.PlanID,
		"payment_id":                order.PaymentID,
		"period":                    order.Period,
		"trade_no":                  order.TradeNo,
		"total_amount":              order.TotalAmount,
		"handling_amount":           order.HandlingAmount,
		"balance_amount":            order.BalanceAmount,
		"refund_amount":             order.RefundAmount,
		"surplus_amount":            order.SurplusAmount,
		"type":                      order.Type,
		"status":                    order.Status,
		"surplus_order_ids":         order.SurplusOrderIDs,
		"coupon_id":                 order.CouponID,
		"created_at":                order.CreatedAt,
		"updated_at":                order.UpdatedAt,
		"commission_status":         order.CommissionStatus,
		"invite_user_id":            order.InviteUserID,
		"actual_commission_balance": order.ActualCommissionBalance,
		"commission_rate":           order.CommissionRate,
		"commission_auto_check":     order.CommissionAutoCheck,
		"commission_balance":        order.CommissionBalance,
		"discount_amount":           order.DiscountAmount,
		"paid_at":                   order.PaidAt,
		"callback_no":               order.CallbackNo,
		"plan":                      order.Plan,
		"payment":                   order.Payment,
	}
	return data
}

func parseIntSlice(raw *string) []int {
	if raw == nil || strings.TrimSpace(*raw) == "" {
		return nil
	}

	var values []int
	if err := json.Unmarshal([]byte(*raw), &values); err == nil {
		return values
	}

	parts := strings.Split(*raw, ",")
	values = make([]int, 0, len(parts))
	for _, part := range parts {
		value, err := strconv.Atoi(strings.TrimSpace(part))
		if err == nil {
			values = append(values, value)
		}
	}
	return values
}

func parseStringSlice(raw *string) []string {
	if raw == nil || strings.TrimSpace(*raw) == "" {
		return nil
	}

	var values []string
	if err := json.Unmarshal([]byte(*raw), &values); err == nil {
		return values
	}

	parts := strings.Split(*raw, ",")
	values = make([]string, 0, len(parts))
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part != "" {
			values = append(values, part)
		}
	}
	return values
}

func containsInt(values []int, target int) bool {
	for _, value := range values {
		if value == target {
			return true
		}
	}
	return false
}

func containsString(values []string, target string) bool {
	for _, value := range values {
		if value == target {
			return true
		}
	}
	return false
}

func extractTelegramUsername(link string) string {
	link = strings.TrimSpace(link)
	link = strings.TrimPrefix(link, "https://t.me/")
	link = strings.TrimPrefix(link, "http://t.me/")
	link = strings.TrimPrefix(link, "t.me/")
	link = strings.TrimPrefix(link, "@")
	link = strings.Trim(link, "/")
	return link
}

func parseConfigString(raw *string, key string) string {
	if raw == nil || strings.TrimSpace(*raw) == "" {
		return ""
	}

	var payload map[string]any
	if err := json.Unmarshal([]byte(*raw), &payload); err != nil {
		return ""
	}
	value, ok := payload[key]
	if !ok {
		return ""
	}
	result, _ := value.(string)
	return result
}

func upsertSetting(name string, value any, now time.Time) error {
	if strings.TrimSpace(name) == "" {
		return nil
	}

	var setting model.Setting
	err := database.DB.Where("name = ?", name).First(&setting).Error
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}

	stringValue, err := stringifySettingValue(value)
	if err != nil {
		return err
	}

	if errors.Is(err, gorm.ErrRecordNotFound) || setting.ID == 0 {
		setting = model.Setting{
			Name:      name,
			Value:     stringValue,
			CreatedAt: &now,
			UpdatedAt: &now,
		}
		return database.DB.Create(&setting).Error
	}

	return database.DB.Model(&model.Setting{}).Where("id = ?", setting.ID).
		Updates(map[string]any{"value": stringValue, "updated_at": now}).Error
}

func stringifySettingValue(value any) (string, error) {
	switch typed := value.(type) {
	case nil:
		return "", nil
	case string:
		return typed, nil
	case bool:
		if typed {
			return "1", nil
		}
		return "0", nil
	case float64:
		if typed == float64(int64(typed)) {
			return strconv.FormatInt(int64(typed), 10), nil
		}
		return strconv.FormatFloat(typed, 'f', -1, 64), nil
	case int:
		return strconv.Itoa(typed), nil
	case int64:
		return strconv.FormatInt(typed, 10), nil
	case []any, map[string]any:
		bytes, err := json.Marshal(typed)
		if err != nil {
			return "", err
		}
		return string(bytes), nil
	default:
		return fmt.Sprint(typed), nil
	}
}
