//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2025-03-04 16:08:06
// Distributed under terms of the MIT license.
//

package auth

import (
	"net/http"
	"time"

	"github.com/veypi/vbase/auth"
	"github.com/veypi/vbase/cfg"
	"github.com/veypi/vbase/libs/crypto"
	"github.com/veypi/vbase/libs/jwt"
	"github.com/veypi/vbase/models"
	"github.com/veypi/vigo"
)

// LoginRequest 登录请求
type LoginRequest struct {
	Username    string `json:"username" src:"json" desc:"用户名/邮箱/手机号"`
	Password    string `json:"password" src:"json" desc:"密码"`
	CaptchaID   string `json:"captcha_id,omitempty" src:"json" desc:"验证码ID"`
	CaptchaCode string `json:"captcha_code,omitempty" src:"json" desc:"验证码"`
	Remember    bool   `json:"remember,omitempty" src:"json" desc:"记住登录"`
}

// AuthResponse 认证响应（token 仅通过 HttpOnly Cookie 下发，body 只含用户信息）
type AuthResponse struct {
	AccessToken  string    `json:"access_token,omitempty"`
	RefreshToken string    `json:"refresh_token,omitempty"`
	TokenType    string    `json:"token_type,omitempty"`
	ExpiresIn    int       `json:"expires_in,omitempty"`
	User         *UserInfo `json:"user"`
}

// UserInfo 用户信息
type UserInfo struct {
	ID       string  `json:"id"`
	Username string  `json:"username"`
	Nickname string  `json:"nickname"`
	Email    *string `json:"email"`
	Avatar   string  `json:"avatar"`
}

// LoginWithCodeRequest 验证码登录请求
type LoginWithCodeRequest struct {
	Type   string `json:"type" src:"json" desc:"类型: email/phone"`
	Target string `json:"target" src:"json" desc:"邮箱或手机号"`
	Code   string `json:"code" src:"json" desc:"验证码"`
}

// loginWithCode 验证码登录
func loginWithCode(x *vigo.X, req *LoginWithCodeRequest) (*AuthResponse, error) {
	// 参数校验
	if req.Type != "email" && req.Type != "phone" {
		return nil, vigo.ErrInvalidArg.WithString("invalid type, must be email or phone")
	}
	if req.Target == "" || req.Code == "" {
		return nil, vigo.ErrInvalidArg.WithString("target and code are required")
	}

	db := cfg.DB()

	// 查找最新验证码
	var verification models.VerificationCode
	if err := db.Where("target = ? AND type = ? AND purpose = ?",
		req.Target, req.Type, models.CodePurposeLogin).
		Order("created_at DESC").First(&verification).Error; err != nil {
		return nil, vigo.ErrInvalidArg.WithString("invalid verification code")
	}

	// 检查验证码状态
	maxAttempts, _ := models.GetSettingInt(models.SettingCodeMaxAttempt)
	if maxAttempts == 0 {
		maxAttempts = 3
	}

	if !verification.CanRetry(maxAttempts) {
		return nil, vigo.ErrInvalidArg.WithString("verification code expired or max attempts exceeded")
	}

	// 验证code
	if verification.Code != req.Code {
		// 增加尝试次数
		db.Model(&verification).UpdateColumn("attempts", verification.Attempts+1)
		return nil, vigo.ErrInvalidArg.WithString("invalid verification code")
	}

	// 标记为已使用
	now := time.Now()
	verification.Status = models.CodeStatusUsed
	verification.UsedAt = &now
	db.Save(&verification)

	// 查找用户
	var user models.User
	var queryErr error
	if req.Type == "email" {
		queryErr = db.Where("email = ?", req.Target).First(&user).Error
	} else {
		queryErr = db.Where("phone = ?", req.Target).First(&user).Error
	}

	if queryErr != nil {
		return nil, vigo.ErrUnauthorized.WithString("user not found")
	}

	// 检查用户状态
	if user.Status != models.UserStatusActive {
		return nil, vigo.ErrForbidden.WithString("user is disabled")
	}

	// 生成token
	return generateAuthResponseForUser(x, &user)
}

// generateAuthResponseForUser 为用户生成认证响应（登录时递增版本，踢掉旧会话）
func generateAuthResponseForUser(x *vigo.X, user *models.User) (*AuthResponse, error) {
	version, err := auth.IncrTokenVersion(user.ID)
	if err != nil {
		return nil, vigo.ErrInternalServer.WithError(err)
	}

	tokenPair, err := jwt.GenerateTokenPair(user.ID, version)
	if err != nil {
		return nil, vigo.ErrInternalServer.WithError(err)
	}

	// 设置 HttpOnly Cookie（浏览器自动携带），body 仅返回用户信息
	setTokenCookies(x, tokenPair)

	return &AuthResponse{
		User: &UserInfo{
			ID:       user.ID,
			Username: user.Username,
			Nickname: user.Nickname,
			Email:    user.Email,
			Avatar:   user.Avatar,
		},
	}, nil
}

// login 用户登录
func login(x *vigo.X, req *LoginRequest) (*AuthResponse, error) {
	// 查找用户
	var user models.User
	query := cfg.DB().Where("username = ? OR email = ? OR phone = ?", req.Username, req.Username, req.Username)
	if err := query.First(&user).Error; err != nil {
		return nil, vigo.ErrUnauthorized.WithString("invalid username or password")
	}

	// 检查用户状态
	if user.Status != models.UserStatusActive {
		return nil, vigo.ErrForbidden.WithString("user is disabled")
	}

	// 验证密码
	if !crypto.VerifyPassword(req.Password, user.Password) {
		return nil, vigo.ErrUnauthorized.WithString("invalid username or password")
	}

	// 更新最后登录时间
	now := time.Now()
	cfg.DB().Model(&user).Update("last_login_at", now)

	// 生成 token + Set-Cookie
	return generateAuthResponseForUser(x, &user)
}

// RefreshRequest 刷新请求
type RefreshRequest struct {
	RefreshToken string `json:"refresh_token" src:"json" desc:"刷新令牌"`
}

// refresh 刷新Token（Token Rotation：严格验证版本并轮换）
func refresh(x *vigo.X, req *RefreshRequest) (*AuthResponse, error) {
	refreshToken := req.RefreshToken
	// Body 为空时从 Cookie 读取
	if refreshToken == "" {
		if c, err := x.Request.Cookie(refreshCookieName); err == nil {
			refreshToken = c.Value
		}
	}
	if refreshToken == "" {
		return nil, vigo.ErrTokenInvalid
	}

	claims, err := jwt.ParseToken(refreshToken)
	if err != nil {
		if err == jwt.ErrExpiredToken {
			return nil, vigo.ErrTokenExpired
		}
		return nil, vigo.ErrTokenInvalid
	}

	if !jwt.IsRefreshToken(claims) {
		return nil, vigo.ErrTokenInvalid
	}

	// 查找用户
	var user models.User
	if err := cfg.DB().First(&user, "id = ?", claims.UserID).Error; err != nil {
		return nil, vigo.ErrTokenInvalid
	}

	if user.Status != models.UserStatusActive {
		return nil, vigo.ErrForbidden.WithString("user is disabled")
	}

	// 严格验证 refresh_token 版本并递增（版本不匹配=已泄露/已用）
	newVersion, err := auth.ValidateRefreshAndRotate(claims.UserID, claims.Version)
	if err != nil {
		return nil, vigo.ErrTokenInvalid.WithString("refresh token is stale or already used")
	}

	// 生成新 token pair（新版本号）
	tokenPair, err := jwt.GenerateTokenPair(user.ID, newVersion)
	if err != nil {
		return nil, vigo.ErrInternalServer.WithError(err)
	}

	// 更新 Cookie，body 仅返回用户信息
	setTokenCookies(x, tokenPair)

	return &AuthResponse{
		User: &UserInfo{
			ID:       user.ID,
			Username: user.Username,
			Nickname: user.Nickname,
			Email:    user.Email,
			Avatar:   user.Avatar,
		},
	}, nil
}

// logout 用户登出（版本递增，全部 token 失效）
func logout(x *vigo.X) error {
	userID := cfg.Auth.UserID(x)
	if userID == "" {
		return nil
	}
	// 递增 token 版本 → 已签发的所有 token 全部失效
	if err := auth.RevokeAllTokens(userID); err != nil {
		return vigo.ErrInternalServer.WithError(err)
	}
	// 清除 Cookie
	clearTokenCookies(x)
	return nil
}

// ========== Cookie 辅助函数 ==========

const (
	accessCookieName  = "vb_access"
	refreshCookieName = "vb_refresh"
)

// setTokenCookies 设置 HttpOnly Cookie
func setTokenCookies(x *vigo.X, tokenPair *jwt.TokenPair) {
	refreshTokenPath := Router.String() + "/refresh"
	cp := cfg.Global.JWT.CookiePath
	http.SetCookie(x.ResponseWriter(), &http.Cookie{
		Name:     accessCookieName,
		Value:    tokenPair.AccessToken,
		Path:     cp,
		MaxAge:   int(cfg.Global.JWT.AccessExpiry.Seconds()),
		HttpOnly: true,
		Secure:   x.Request.TLS != nil,
		SameSite: http.SameSiteStrictMode,
	})
	http.SetCookie(x.ResponseWriter(), &http.Cookie{
		Name:     refreshCookieName,
		Value:    tokenPair.RefreshToken,
		Path:     refreshTokenPath,
		MaxAge:   int(cfg.Global.JWT.RefreshExpiry.Seconds()),
		HttpOnly: true,
		Secure:   x.Request.TLS != nil,
		SameSite: http.SameSiteStrictMode,
	})
}

// clearTokenCookies 清除 Cookie（登出时调用）
func clearTokenCookies(x *vigo.X) {
	cp := cfg.Global.JWT.CookiePath
	refreshTokenPath := Router.String() + "/refresh"
	http.SetCookie(x.ResponseWriter(), &http.Cookie{
		Name:     accessCookieName,
		Value:    "",
		Path:     cp,
		MaxAge:   -1,
		HttpOnly: true,
	})
	http.SetCookie(x.ResponseWriter(), &http.Cookie{
		Name:     refreshCookieName,
		Value:    "",
		Path:     refreshTokenPath,
		MaxAge:   -1,
		HttpOnly: true,
	})
}
