package api

import (
	"errors"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"oci-portal/internal/model"
	"oci-portal/internal/service"
)

type authHandler struct {
	svc  *service.AuthService
	logs *service.SystemLogService
}

type loginRequest struct {
	// 字段长度上限防超长输入撑爆登录守卫与 bcrypt(S-03)
	Username string `json:"username" binding:"required,max=64"`
	Password string `json:"password" binding:"required,max=128"`
	// 两步验证码;账号已启用 TOTP 时必填,缺失返回 428
	TotpCode string `json:"totpCode" binding:"max=8"`
}

// login 校验用户名密码(与可选 TOTP)后签发 JWT。
//
// @Summary  登录
// @Tags     认证
// @Accept   json
// @Produce  json
// @Param    body body loginRequest true "登录凭据"
// @Success  200 {object} tokenResponse "token 与 expiresAt"
// @Failure  401 {object} errorResponse "凭据错误"
// @Failure  428 {object} totpRequiredResponse "需要两步验证码(totpRequired=true)"
// @Router   /api/v1/auth/login [post]
func (h *authHandler) login(c *gin.Context) {
	var req loginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	start := time.Now()
	token, expires, err := h.svc.Login(c.Request.Context(), req.Username, req.Password, requestIP(c), req.TotpCode)
	if errors.Is(err, service.ErrTotpRequired) {
		// 密码已通过,引导前端弹出二次验证输入;不算失败不留痕
		c.JSON(http.StatusPreconditionRequired, gin.H{"error": err.Error(), "totpRequired": true})
		return
	}
	if errors.Is(err, service.ErrPasswordLoginDisabled) {
		h.recordLogin(c, req.Username, http.StatusForbidden, start)
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	if errors.Is(err, service.ErrLoginLocked) {
		h.recordLogin(c, req.Username, http.StatusTooManyRequests, start)
		c.JSON(http.StatusTooManyRequests, gin.H{"error": err.Error()})
		return
	}
	if errors.Is(err, service.ErrInvalidCredentials) {
		h.recordLogin(c, req.Username, http.StatusUnauthorized, start)
		c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
		return
	}
	if err != nil {
		respondError(c, err)
		return
	}
	h.recordLogin(c, req.Username, http.StatusOK, start)
	c.JSON(http.StatusOK, gin.H{"token": token, "expiresAt": expires})
}

// logout 把当前令牌拉黑至自然过期(secured 组内,写请求由系统日志中间件留痕)。
//
// @Summary  登出
// @Tags     认证
// @Success  204 "令牌已吊销"
// @Security BearerAuth
// @Router   /api/v1/auth/logout [post]
func (h *authHandler) logout(c *gin.Context) {
	token, ok := strings.CutPrefix(c.GetHeader("Authorization"), "Bearer ")
	if ok && token != "" {
		h.svc.Logout(token)
	}
	c.Status(http.StatusNoContent)
}

// recordLogin 显式记录一次登录成败——登录不在 secured 组内，
// 系统日志中间件覆盖不到；登录失败是安全关键事件，必须留痕。
func (h *authHandler) recordLogin(c *gin.Context, username string, status int, start time.Time) {
	errMsg := ""
	if status == http.StatusUnauthorized {
		errMsg = "用户名、密码或验证码错误"
	} else if status == http.StatusTooManyRequests {
		errMsg = "连续失败已锁定"
	} else if status == http.StatusForbidden {
		errMsg = "密码登录已禁用"
	}
	h.logs.Record(model.SystemLog{
		Username:   username,
		Method:     http.MethodPost,
		Path:       requestPath(c),
		Status:     status,
		DurationMs: time.Since(start).Milliseconds(),
		ClientIP:   requestIP(c),
		UserAgent:  truncateLogField(c.Request.UserAgent(), 256),
		ErrMsg:     errMsg,
	})
}
