package handlers

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/niangaodev/art-code/config"
	"github.com/niangaodev/art-code/middleware"
	"github.com/niangaodev/art-code/models"
	"github.com/niangaodev/art-code/repositories"
	"github.com/niangaodev/art-code/utils"
	"gorm.io/gorm"
)

const guestCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

func randomGuestSuffix(minLen, maxLen int) string {
	nBig, _ := rand.Int(rand.Reader, big.NewInt(int64(maxLen-minLen+1)))
	n := int(nBig.Int64()) + minLen
	b := make([]byte, n)
	for i := 0; i < n; i++ {
		idx, _ := rand.Int(rand.Reader, big.NewInt(int64(len(guestCharset))))
		b[i] = guestCharset[idx.Int64()]
	}
	return string(b)
}

func uniqueGuestUsername() (string, error) {
	for i := 0; i < 8; i++ {
		name := "游客_" + randomGuestSuffix(6, 10)
		exist, err := repositories.GetUserByUsername(name)
		if err != nil {
			return "", err
		}
		if exist == nil {
			return name, nil
		}
	}
	return "", fmt.Errorf("failed to allocate guest username")
}

// WxLogin 微信小程序静默登录 / 自动建游客账号
func WxLogin(c *gin.Context) {
	var req models.WxLoginRequest
	if err := c.ShouldBindJSON(&req); err != nil || strings.TrimSpace(req.Code) == "" {
		utils.Error(c, 400, "Invalid request")
		return
	}

	session, err := utils.Code2Session(req.Code)
	if err != nil {
		utils.Error(c, 400, err.Error())
		return
	}

	user, err := repositories.GetUserByWxOpenID(session.OpenID)
	if err != nil {
		utils.ServerError(c, err)
		return
	}

	if user == nil {
		username, err := uniqueGuestUsername()
		if err != nil {
			utils.ServerError(c, err)
			return
		}
		hash, err := utils.HashPassword(randomGuestSuffix(16, 24))
		if err != nil {
			utils.ServerError(c, err)
			return
		}
		openidPrefix := session.OpenID
		if len(openidPrefix) > 12 {
			openidPrefix = openidPrefix[:12]
		}
		user = &models.User{
			Username:     username,
			Email:        fmt.Sprintf("wx_%s@guest.local", openidPrefix),
			Avatar:       "",
			PasswordHash: hash,
			WxOpenID:     session.OpenID,
			Role:         "viewer",
			IsActive:     1,
		}
		if role, rerr := repositories.GetRoleByName("viewer"); rerr == nil && role != nil {
			user.RoleID = role.ID
		}
		if err := repositories.CreateUser(user); err != nil {
			utils.ServerError(c, err)
			return
		}
		// reload with role name
		user, err = repositories.GetUserByWxOpenID(session.OpenID)
		if err != nil || user == nil {
			utils.ServerError(c, fmt.Errorf("created user but reload failed"))
			return
		}
	}

	if user.IsActive == 0 {
		utils.Error(c, 403, "Account disabled")
		return
	}

	tokenString, expireUnix, err := middleware.GenerateToken(user.ID, user.Username, user.Role)
	if err != nil {
		utils.ServerError(c, err)
		return
	}

	go func() {
		ip := c.ClientIP()
		location := utils.GetRegion(ip)
		logEntry := &models.UserAccessLog{
			UserID:       user.ID,
			UserIP:       ip,
			UserLocation: location,
		}
		_ = repositories.CreateUserAccessLog(logEntry)
	}()

	utils.Success(c, gin.H{
		"token":  tokenString,
		"expire": expireUnix,
		"user":   repositories.BuildUserResponse(user),
	})
}

// isGuestUser 判断是否为可合并的游客账号（viewer 或 游客_ 前缀）
func isGuestUser(u *models.User) bool {
	if u == nil {
		return false
	}
	role := strings.ToLower(strings.TrimSpace(u.Role))
	if strings.HasPrefix(u.Username, "游客_") {
		return true
	}
	return role == "viewer" || role == "guest"
}

// isStaffUser 判断是否为可绑定的员工账号
func isStaffUser(u *models.User) bool {
	if u == nil {
		return false
	}
	role := strings.ToLower(strings.TrimSpace(u.Role))
	return role == "admin" || role == "editor" || strings.Contains(role, "admin") || strings.Contains(role, "editor")
}

// MergeAccountRequest 游客合并到管理员账号
type MergeAccountRequest struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

// MergeAccount 游客校验管理员密码后，将微信 openid 迁到管理员，并逻辑删除游客
func MergeAccount(c *gin.Context) {
	var req MergeAccountRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		utils.Error(c, 400, "Invalid request")
		return
	}

	guestIDVal, ok := c.Get("userID")
	if !ok {
		utils.Error(c, 401, "Unauthorized")
		return
	}
	guestID, ok := guestIDVal.(uint)
	if !ok {
		utils.Error(c, 401, "Unauthorized")
		return
	}

	guest, err := repositories.GetUserByID(guestID)
	if err != nil {
		utils.ServerError(c, err)
		return
	}
	if guest == nil {
		utils.Error(c, 401, "Unauthorized")
		return
	}
	if !isGuestUser(guest) {
		utils.Error(c, 400, "当前账号不是游客，无法合并")
		return
	}
	openid := strings.TrimSpace(guest.WxOpenID)
	if openid == "" {
		utils.Error(c, 400, "当前游客未绑定微信，无法合并")
		return
	}

	target, err := repositories.GetUserByUsername(strings.TrimSpace(req.Username))
	if err != nil {
		utils.ServerError(c, err)
		return
	}
	if target == nil || !utils.CheckPasswordHash(req.Password, target.PasswordHash) {
		utils.Error(c, 401, "Invalid username or password")
		return
	}
	if target.IsActive == 0 {
		utils.Error(c, 403, "Account disabled")
		return
	}
	if !isStaffUser(target) {
		utils.Error(c, 403, "目标账号不是管理员或编辑者")
		return
	}
	if target.ID == guest.ID {
		utils.Error(c, 400, "不能合并到当前账号")
		return
	}
	existingOpenID := strings.TrimSpace(target.WxOpenID)
	if existingOpenID != "" && existingOpenID != openid {
		utils.Error(c, 409, "该管理员已绑定其他微信账号")
		return
	}

	// 事务：清空游客 openid → 软删游客 → 绑定到管理员
	err = config.DB.Transaction(func(tx *gorm.DB) error {
		now := time.Now().Unix()
		if err := tx.Model(&models.User{}).Where("id = ?", guest.ID).
			Updates(map[string]interface{}{
				"wx_openid":  nil,
				"updated_at": now,
			}).Error; err != nil {
			return err
		}
		if err := tx.Model(&models.User{}).Where("id = ?", guest.ID).
			Update("deleted_at", now).Error; err != nil {
			return err
		}
		if existingOpenID == openid {
			return nil
		}
		return tx.Model(&models.User{}).
			Where("id = ? AND deleted_at = ?", target.ID, 0).
			Updates(map[string]interface{}{
				"wx_openid":  openid,
				"updated_at": now,
			}).Error
	})
	if err != nil {
		utils.ServerError(c, err)
		return
	}

	admin, err := repositories.GetUserByID(target.ID)
	if err != nil || admin == nil {
		utils.ServerError(c, fmt.Errorf("merged but reload admin failed"))
		return
	}

	tokenString, expireUnix, err := middleware.GenerateToken(admin.ID, admin.Username, admin.Role)
	if err != nil {
		utils.ServerError(c, err)
		return
	}

	go func() {
		ip := c.ClientIP()
		location := utils.GetRegion(ip)
		logEntry := &models.UserAccessLog{
			UserID:       admin.ID,
			UserIP:       ip,
			UserLocation: location,
		}
		_ = repositories.CreateUserAccessLog(logEntry)
	}()

	utils.Success(c, gin.H{
		"token":  tokenString,
		"expire": expireUnix,
		"user":   repositories.BuildUserResponse(admin),
	})
}
