package handler

import (
	"crypto/md5"
	"fmt"
	"net/http"
	"strings"
	"time"
	"xboard-go/internal/database"
	"xboard-go/internal/model"
	"xboard-go/internal/service"
	"xboard-go/pkg/utils"

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

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

	Success(c, gin.H{
		"email":              user.Email,
		"transfer_enable":    user.TransferEnable,
		"last_login_at":      user.LastLoginAt,
		"created_at":         user.CreatedAt,
		"banned":             user.Banned,
		"remind_expire":      user.RemindExpire,
		"remind_traffic":     user.RemindTraffic,
		"expired_at":         user.ExpiredAt,
		"balance":            user.Balance,
		"commission_balance": user.CommissionBalance,
		"plan_id":            user.PlanID,
		"discount":           user.Discount,
		"commission_rate":    user.CommissionRate,
		"telegram_id":        user.TelegramID,
		"uuid":               user.UUID,
		"avatar_url":         "https://cdn.v2ex.com/gravatar/" + fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(user.Email)))) + "?s=64&d=identicon",
	})
}

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

	var pendingOrders int64
	var openTickets int64
	var invitedUsers int64

	database.DB.Model(&model.Order{}).Where("status = ? AND user_id = ?", 0, user.ID).Count(&pendingOrders)
	database.DB.Model(&model.Ticket{}).Where("status = ? AND user_id = ?", 0, user.ID).Count(&openTickets)
	database.DB.Model(&model.User{}).Where("invite_user_id = ?", user.ID).Count(&invitedUsers)

	Success(c, []int64{pendingOrders, openTickets, invitedUsers})
}

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

	if user.PlanID != nil {
		var plan model.Plan
		if err := database.DB.First(&plan, *user.PlanID).Error; err == nil {
			user.Plan = &plan
		}
	}

	baseURL := requestBaseURL(c)
	subscribePath := service.GetSubscribePath()

	Success(c, gin.H{
		"plan_id":         user.PlanID,
		"token":           user.Token,
		"expired_at":      user.ExpiredAt,
		"u":               user.U,
		"d":               user.D,
		"transfer_enable": user.TransferEnable,
		"email":           user.Email,
		"uuid":            user.UUID,
		"device_limit":    user.DeviceLimit,
		"speed_limit":     user.SpeedLimit,
		"next_reset_at":   user.NextResetAt,
		"plan":            service.FormatPlan(user.Plan),
		"subscribe_url":   strings.TrimRight(baseURL, "/") + "/" + subscribePath + "/" + user.Token,
		"reset_day":       service.GetResetDay(user),
	})
}

func UserCheckLogin(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Success(c, gin.H{"is_login": false})
		return
	}
	Success(c, gin.H{
		"is_login": true,
		"is_admin": user.IsAdmin,
	})
}

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

	newUUID := uuid.New().String()
	newToken := service.GenerateSubscriptionToken()
	if err := database.DB.Model(&model.User{}).
		Where("id = ?", user.ID).
		Updates(map[string]any{"uuid": newUUID, "token": newToken, "updated_at": time.Now().Unix()}).Error; err != nil {
		Fail(c, 500, "reset failed")
		return
	}

	baseURL := requestBaseURL(c)
	Success(c, strings.TrimRight(baseURL, "/")+"/"+service.GetSubscribePath()+"/"+newToken)
}

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

	var req struct {
		RemindExpire  *int `json:"remind_expire"`
		RemindTraffic *int `json:"remind_traffic"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, 400, "invalid request body")
		return
	}

	updates := map[string]any{"updated_at": time.Now().Unix()}
	if req.RemindExpire != nil {
		updates["remind_expire"] = *req.RemindExpire
	}
	if req.RemindTraffic != nil {
		updates["remind_traffic"] = *req.RemindTraffic
	}

	if err := database.DB.Model(&model.User{}).Where("id = ?", user.ID).Updates(updates).Error; err != nil {
		Fail(c, 500, "save failed")
		return
	}

	Success(c, true)
}

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

	var req struct {
		OldPassword string `json:"old_password" binding:"required"`
		NewPassword string `json:"new_password" binding:"required,min=8"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, 400, "invalid request body")
		return
	}

	if !utils.CheckPassword(req.OldPassword, user.Password, user.PasswordAlgo, user.PasswordSalt) {
		Fail(c, 400, "the old password is wrong")
		return
	}

	hashed, err := utils.HashPassword(req.NewPassword)
	if err != nil {
		Fail(c, 500, "failed to hash password")
		return
	}

	if err := database.DB.Model(&model.User{}).
		Where("id = ?", user.ID).
		Updates(map[string]any{
			"password":      hashed,
			"password_algo": nil,
			"password_salt": nil,
			"updated_at":    time.Now().Unix(),
		}).Error; err != nil {
		Fail(c, 500, "save failed")
		return
	}

	Success(c, true)
}

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

	servers, err := service.AvailableServersForUser(user)
	if err != nil {
		Fail(c, 500, "failed to fetch servers")
		return
	}

	// Fetch all servers for parent lookup
	var allServers []model.Server
	if err := database.DB.Find(&allServers).Error; err != nil {
		Fail(c, 500, "failed to fetch all servers")
		return
	}
	serverMap := make(map[int]model.Server, len(allServers))
	for _, s := range allServers {
		serverMap[s.ID] = s
	}

	now := time.Now().Unix()
	result := make([]gin.H, 0, len(servers))
	for _, server := range servers {
		lastCheckAt, _ := database.CacheGetJSON[int64](nodeLastCheckKey(&server))
		isOnline := 0
		if lastCheckAt > 0 && now-lastCheckAt <= 300 {
			isOnline = 1
		}

		// Inherit online status from parent if current is offline
		if isOnline == 0 && server.ParentID != nil {
			if parent, ok := serverMap[*server.ParentID]; ok {
				pLastCheckAt, _ := database.CacheGetJSON[int64](nodeLastCheckKey(&parent))
				if pLastCheckAt > 0 && now-pLastCheckAt <= 300 {
					isOnline = 1
					lastCheckAt = pLastCheckAt
				}
			}
		}

		result = append(result, gin.H{
			"id":            server.ID,
			"type":          server.Type,
			"name":          server.Name,
			"rate":          server.Rate,
			"tags":          server.Tags,
			"is_online":     isOnline,
			"last_check_at": lastCheckAt,
			"cache_key":     fmt.Sprintf("%s_%d_%d", server.Type, server.ID, server.UpdatedAt.Unix()),
		})
	}

	Success(c, result)
}

func currentUser(c *gin.Context) (*model.User, bool) {
	if value, exists := c.Get("user"); exists {
		if user, ok := value.(*model.User); ok {
			return user, true
		}
	}

	userIDValue, exists := c.Get("user_id")
	if !exists {
		return nil, false
	}

	userID, ok := userIDValue.(int)
	if !ok {
		return nil, false
	}

	var user model.User
	if err := database.DB.First(&user, userID).Error; err != nil {
		return nil, false
	}

	c.Set("user", &user)
	return &user, true
}
