package auth

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"hyapp-admin-server/internal/config"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/repository"
	"hyapp-admin-server/internal/response"
	authcore "hyapp-admin-server/internal/service"
)

const (
	refreshCookieName       = "hyapp_admin_refresh"
	refreshCookiePath       = "/"
	legacyRefreshCookiePath = "/api/v1/auth"
)

type Handler struct {
	service *AuthFlowService
	audit   shared.OperationLogger
	cfg     config.Config
}

func New(store *repository.Store, auth *authcore.AuthService, audit shared.OperationLogger, cfg config.Config) *Handler {
	return &Handler{service: NewService(store, auth, cfg), audit: audit, cfg: cfg}
}

func (h *Handler) Login(c *gin.Context) {
	var req loginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "用户名和密码不能为空")
		return
	}

	result, err := h.service.Login(LoginInput{
		Username:  req.Username,
		Password:  req.Password,
		IP:        c.ClientIP(),
		UserAgent: c.Request.UserAgent(),
	})
	if err != nil {
		response.Unauthorized(c, "用户名或密码错误")
		return
	}

	h.setRefreshCookie(c, result.RefreshToken, int(h.cfg.RefreshTokenTTL.Seconds()))
	response.OK(c, gin.H{
		"accessToken": result.AccessToken,
		"expiresAtMs": result.ExpiresAtMS,
		"user":        shared.UserDTO(result.User),
		"permissions": result.Permissions,
	})
}

func (h *Handler) Logout(c *gin.Context) {
	for _, token := range refreshCookieCandidates(c) {
		_ = h.service.Logout(token)
	}
	h.setRefreshCookie(c, "", -1)
	response.OK(c, gin.H{"loggedOut": true})
}

func (h *Handler) Refresh(c *gin.Context) {
	tokens := refreshCookieCandidates(c)
	if len(tokens) == 0 {
		response.Unauthorized(c, "刷新会话不存在")
		return
	}

	var result LoginResult
	var err error
	for _, token := range tokens {
		result, err = h.service.Refresh(token, LoginInput{
			IP:        c.ClientIP(),
			UserAgent: c.Request.UserAgent(),
		})
		if err == nil {
			h.setRefreshCookie(c, result.RefreshToken, int(h.cfg.RefreshTokenTTL.Seconds()))
			response.OK(c, gin.H{
				"accessToken": result.AccessToken,
				"expiresAtMs": result.ExpiresAtMS,
				"user":        shared.UserDTO(result.User),
				"permissions": result.Permissions,
			})
			return
		}
	}

	response.Unauthorized(c, "刷新会话已失效")
}

func (h *Handler) Me(c *gin.Context) {
	user, permissions, err := h.service.Me(shared.ActorFromContext(c))
	if err != nil {
		response.Unauthorized(c, "用户不存在")
		return
	}

	response.OK(c, gin.H{
		"user":        shared.UserDTO(*user),
		"permissions": permissions,
	})
}

func (h *Handler) ChangePassword(c *gin.Context) {
	var req changePasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "旧密码和新密码不能为空")
		return
	}

	if err := h.service.ChangePassword(shared.ActorFromContext(c), req.OldPassword, req.NewPassword); err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLog(c, h.audit, "change-password", "admin_users", "success", "用户修改自己的密码")
	response.OK(c, gin.H{"changed": true})
}

func (h *Handler) setRefreshCookie(c *gin.Context, token string, maxAge int) {
	c.SetSameSite(refreshCookieSameSite(h.cfg.RefreshCookieSameSite))
	c.SetCookie(refreshCookieName, token, maxAge, refreshCookiePath, "", h.cfg.RefreshCookieSecure, true)
	c.SetCookie(refreshCookieName, "", -1, legacyRefreshCookiePath, "", h.cfg.RefreshCookieSecure, true)
}

func refreshCookieCandidates(c *gin.Context) []string {
	values := make([]string, 0, 2)
	seen := map[string]struct{}{}
	for _, cookie := range c.Request.Cookies() {
		if cookie.Name != refreshCookieName || cookie.Value == "" {
			continue
		}
		if _, ok := seen[cookie.Value]; ok {
			continue
		}
		seen[cookie.Value] = struct{}{}
		values = append(values, cookie.Value)
	}
	return values
}

func refreshCookieSameSite(value string) http.SameSite {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "strict":
		return http.SameSiteStrictMode
	case "none":
		return http.SameSiteNoneMode
	default:
		return http.SameSiteLaxMode
	}
}
