package api

import (
	"errors"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/wormhole/wormhole/internal/auth"
	"github.com/wormhole/wormhole/internal/bridge"
	"github.com/wormhole/wormhole/internal/cache"
	"github.com/wormhole/wormhole/internal/config"
	"github.com/wormhole/wormhole/internal/metrics"
	"github.com/wormhole/wormhole/internal/proxy"
	"github.com/wormhole/wormhole/internal/security"
	"github.com/wormhole/wormhole/internal/store"
)

type Server struct {
	cfg      *config.Config
	store    *store.Store
	cache    *cache.Manager
	auth     *auth.Service
	resAuth  *auth.ResourceAuth
	security *security.Service
	bridge   *bridge.Bridge
	proxy    *proxy.Manager
	metrics  *metrics.Collector
}

func NewServer(
	cfg *config.Config,
	st *store.Store,
	c *cache.Manager,
	authSvc *auth.Service,
	resAuth *auth.ResourceAuth,
	sec *security.Service,
	b *bridge.Bridge,
	pm *proxy.Manager,
	mc *metrics.Collector,
) *Server {
	return &Server{
		cfg:      cfg,
		store:    st,
		cache:    c,
		auth:     authSvc,
		resAuth:  resAuth,
		security: sec,
		bridge:   b,
		proxy:    pm,
		metrics:  mc,
	}
}

func (s *Server) Router() *gin.Engine {
	gin.SetMode(gin.ReleaseMode)
	r := gin.New()
	r.Use(gin.Recovery(), gin.Logger())

	api := r.Group("/api")
	{
		api.GET("/openapi.json", s.openapiDoc)
		api.POST("/auth/login", s.login)

		authGroup := api.Group("")
		authGroup.Use(s.authMiddleware())
		{
			authGroup.GET("/auth/me", s.me)
			authGroup.PUT("/auth/me/password", s.changeMyPassword)
			authGroup.GET("/dashboard", s.dashboard)
			authGroup.GET("/dashboard/rankings", s.dashboardRankings)

			authGroup.GET("/api-keys", s.listAPIKeys)
			authGroup.POST("/api-keys", s.createAPIKey)
			authGroup.DELETE("/api-keys/:id", s.deleteAPIKey)

			authGroup.GET("/clients", s.listClients)
			authGroup.POST("/clients", s.createClient)
			authGroup.PUT("/clients/:id", s.updateClient)
			authGroup.DELETE("/clients/:id", s.deleteClient)

			authGroup.GET("/tunnels", s.listTunnels)
			authGroup.POST("/tunnels", s.createTunnel)
			authGroup.PUT("/tunnels/:id", s.updateTunnel)
			authGroup.DELETE("/tunnels/:id", s.deleteTunnel)
			authGroup.POST("/tunnels/:id/start", s.startTunnel)
			authGroup.POST("/tunnels/:id/stop", s.stopTunnel)
			authGroup.POST("/tunnels/:id/pause", s.pauseTunnel)
			authGroup.POST("/tunnels/:id/resume", s.resumeTunnel)
			authGroup.GET("/tunnels/export", s.exportTunnels)
			authGroup.POST("/tunnels/import", s.importTunnels)

			authGroup.GET("/hosts", s.listHosts)
			authGroup.POST("/hosts", s.createHost)
			authGroup.PUT("/hosts/:id", s.updateHost)
			authGroup.DELETE("/hosts/:id", s.deleteHost)
			authGroup.POST("/hosts/:id/pause", s.pauseHost)
			authGroup.POST("/hosts/:id/resume", s.resumeHost)
			authGroup.GET("/hosts/export", s.exportHosts)
			authGroup.POST("/hosts/import", s.importHosts)

			authGroup.GET("/security/ip-rules", s.listIPRules)
			authGroup.POST("/security/ip-rules", s.createIPRule)
			authGroup.DELETE("/security/ip-rules/:id", s.deleteIPRule)
			authGroup.GET("/security/request-ips", s.listRequestIPs)
			authGroup.POST("/security/block-ip", s.blockIP)

			authGroup.GET("/auth-policies", s.listAuthPolicies)
			authGroup.POST("/auth-policies", s.createAuthPolicy)
			authGroup.DELETE("/auth-policies/:id", s.deleteAuthPolicy)

			authGroup.GET("/settings", s.getSettings)
			authGroup.PUT("/settings", s.updateSettings)

			authGroup.GET("/users", s.listUsers)
			authGroup.GET("/users/:id/access-logs", s.listVisitorAccessLogs)
			authGroup.POST("/users", s.createUser)
			authGroup.PUT("/users/:id", s.updateUser)
			authGroup.DELETE("/users/:id", s.deleteUser)
		}
	}
	return r
}

func getClaims(c *gin.Context) *auth.Claims {
	v, _ := c.Get("claims")
	claims, _ := v.(*auth.Claims)
	return claims
}

func (s *Server) login(c *gin.Context) {
	var req struct {
		Username string `json:"username"`
		Password string `json:"password"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	ip := auth.DirectRemoteIP(c.Request)
	token, user, err := s.auth.Login(s.store, ip, req.Username, req.Password)
	if err != nil {
		var blocked *auth.LoginBlockedError
		if errors.As(err, &blocked) {
			secs := int(blocked.RetryAfter.Seconds())
			if secs < 1 {
				secs = 1
			}
			c.Header("Retry-After", strconv.Itoa(secs))
			c.JSON(http.StatusTooManyRequests, gin.H{
				"error":       "登录尝试次数过多，请稍后再试",
				"retry_after": secs,
			})
			return
		}
		c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"token": token,
		"user": gin.H{
			"id":       user.ID,
			"username": user.Username,
			"role":     user.Role,
		},
	})
}

func (s *Server) me(c *gin.Context) {
	claims := getClaims(c)
	c.JSON(http.StatusOK, gin.H{
		"id":       claims.UserID,
		"username": claims.Username,
		"role":     claims.Role,
	})
}

func (s *Server) changeMyPassword(c *gin.Context) {
	claims := getClaims(c)
	if claims.Role == "visitor" {
		c.JSON(http.StatusForbidden, gin.H{"error": "访客账号无法修改管理密码"})
		return
	}
	var req struct {
		CurrentPassword string `json:"current_password"`
		NewPassword     string `json:"new_password"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	if req.CurrentPassword == "" || req.NewPassword == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "请输入当前密码和新密码"})
		return
	}
	if len(req.NewPassword) < 4 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "新密码至少 4 位"})
		return
	}
	user, err := s.store.GetUserByID(claims.UserID)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
		return
	}
	if !store.CheckPassword(user.PasswordHash, req.CurrentPassword) {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "当前密码不正确"})
		return
	}
	hash, err := store.HashPassword(req.NewPassword)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	user.PasswordHash = hash
	if err := s.store.UpdateUser(user); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) dashboard(c *gin.Context) {
	data, err := s.metrics.Dashboard()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if tops, ok := data["top_ips"].([]store.RequestIP); ok {
		data["top_ips"] = requestIPViews(s.store, tops)
	}
	c.JSON(http.StatusOK, data)
}

func (s *Server) listClients(c *gin.Context) {
	claims := getClaims(c)
	clients, err := s.store.ListClients(claims.UserID, auth.IsAdmin(claims.Role))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	type clientView struct {
		store.Client
		Online bool `json:"online"`
	}
	out := make([]clientView, 0, len(clients))
	for _, cl := range clients {
		out = append(out, clientView{
			Client: cl,
			Online: s.bridge.IsOnline(cl.ID),
		})
	}
	c.JSON(http.StatusOK, out)
}

func (s *Server) createClient(c *gin.Context) {
	claims := getClaims(c)
	var req store.Client
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	req.OwnerUserID = claims.UserID
	if auth.IsAdmin(claims.Role) && req.OwnerUserID == 0 {
		req.OwnerUserID = claims.UserID
	}
	if err := s.store.CreateClient(&req); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	s.cache.UpsertClient(&req)
	c.JSON(http.StatusOK, req)
}

func (s *Server) updateClient(c *gin.Context) {
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	claims := getClaims(c)
	if err := store.ClientOwnerCheck(s.store, uint(id), claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	var req store.Client
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	existing, err := s.store.GetClientByID(uint(id))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	req.ID = existing.ID
	req.VerifyKey = existing.VerifyKey
	req.OwnerUserID = existing.OwnerUserID
	if err := s.store.UpdateClient(&req); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	s.cache.UpsertClient(&req)
	c.JSON(http.StatusOK, req)
}

func (s *Server) deleteClient(c *gin.Context) {
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	claims := getClaims(c)
	if err := store.ClientOwnerCheck(s.store, uint(id), claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	if err := s.store.DeleteClient(uint(id)); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	s.cache.DeleteClient(uint(id))
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) listTunnels(c *gin.Context) {
	claims := getClaims(c)
	clientID, _ := strconv.ParseUint(c.Query("client_id"), 10, 64)
	p := pageParams(c)
	tunnels, total, err := s.store.ListTunnelsPaged(uint(clientID), claims.UserID, auth.IsAdmin(claims.Role), p)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	items := make([]tunnelView, 0, len(tunnels))
	for _, t := range tunnels {
		view, err := s.enrichTunnelView(t)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		items = append(items, view)
	}
	c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
}

func (s *Server) createTunnel(c *gin.Context) {
	claims := getClaims(c)
	var req tunnelRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	if err := store.ClientOwnerCheck(s.store, req.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	if req.Mode == "" {
		req.Mode = "tcp"
	}
	req.ListenIP = "0.0.0.0"
	if req.VisitorBindMode == "" {
		req.VisitorBindMode = "all"
	}
	if req.VisitorTTLSeconds <= 0 {
		req.VisitorTTLSeconds = 7200
	}
	if req.VisitorAuthEnabled && req.Mode == "udp" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "UDP 隧道不支持访客登录"})
		return
	}
	inUse, err := s.store.TunnelPortInUse(req.ListenPort, 0)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if inUse {
		c.JSON(http.StatusConflict, gin.H{"error": "监听端口已被占用"})
		return
	}
	if err := s.store.CreateTunnel(&req.Tunnel); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if err := s.saveResourceVisitors("tunnel", req.ID, req.VisitorAuthEnabled, req.VisitorBindMode, req.VisitorIDs); err != nil {
		_ = s.store.DeleteTunnel(req.ID)
		writeVisitorSaveError(c, err)
		return
	}
	s.cache.UpsertTunnel(&req.Tunnel)
	if req.Status {
		_ = s.proxy.StartTunnel(req.ID)
	}
	view, _ := s.enrichTunnelView(req.Tunnel)
	c.JSON(http.StatusOK, view)
}

func (s *Server) updateTunnel(c *gin.Context) {
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	claims := getClaims(c)
	existing, err := s.store.GetTunnelByID(uint(id))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if err := store.ClientOwnerCheck(s.store, existing.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	var req tunnelRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	req.ID = existing.ID
	req.ClientID = existing.ClientID
	req.ListenIP = "0.0.0.0"
	if req.VisitorBindMode == "" {
		req.VisitorBindMode = "all"
	}
	if req.VisitorTTLSeconds <= 0 {
		req.VisitorTTLSeconds = 7200
	}
	if req.VisitorAuthEnabled && req.Mode == "udp" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "UDP 隧道不支持访客登录"})
		return
	}
	inUse, err := s.store.TunnelPortInUse(req.ListenPort, uint(id))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if inUse {
		c.JSON(http.StatusConflict, gin.H{"error": "监听端口已被占用"})
		return
	}
	if err := s.store.UpdateTunnel(&req.Tunnel); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if err := s.saveResourceVisitors("tunnel", req.ID, req.VisitorAuthEnabled, req.VisitorBindMode, req.VisitorIDs); err != nil {
		writeVisitorSaveError(c, err)
		return
	}
	wasRunning := existing.RunStatus
	s.cache.UpsertTunnel(&req.Tunnel)
	if wasRunning || (req.Status && req.VisitorAuthEnabled != existing.VisitorAuthEnabled) {
		_ = s.proxy.StopTunnel(uint(id))
		if req.Status {
			_ = s.proxy.StartTunnel(uint(id))
		}
	}
	view, _ := s.enrichTunnelView(req.Tunnel)
	c.JSON(http.StatusOK, view)
}

func (s *Server) deleteTunnel(c *gin.Context) {
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	claims := getClaims(c)
	existing, err := s.store.GetTunnelByID(uint(id))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if err := store.ClientOwnerCheck(s.store, existing.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	_ = s.proxy.StopTunnel(uint(id))
	if err := s.store.DeleteTunnel(uint(id)); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	s.cache.DeleteTunnel(uint(id))
	s.cache.DeleteResourceVisitors("tunnel", uint(id))
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) startTunnel(c *gin.Context) {
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	claims := getClaims(c)
	existing, err := s.store.GetTunnelByID(uint(id))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if err := store.ClientOwnerCheck(s.store, existing.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	if err := s.proxy.StartTunnel(uint(id)); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) stopTunnel(c *gin.Context) {
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	claims := getClaims(c)
	existing, err := s.store.GetTunnelByID(uint(id))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if err := store.ClientOwnerCheck(s.store, existing.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	if err := s.proxy.StopTunnel(uint(id)); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) listHosts(c *gin.Context) {
	claims := getClaims(c)
	clientID, _ := strconv.ParseUint(c.Query("client_id"), 10, 64)
	p := pageParams(c)
	hosts, total, err := s.store.ListHostsPaged(uint(clientID), claims.UserID, auth.IsAdmin(claims.Role), p)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	items := make([]hostView, 0, len(hosts))
	for _, h := range hosts {
		view, err := s.enrichHostView(h)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		items = append(items, view)
	}
	c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
}

func (s *Server) createHost(c *gin.Context) {
	claims := getClaims(c)
	var req hostRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	if err := store.ClientOwnerCheck(s.store, req.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	if req.Location == "" {
		req.Location = "/"
	}
	if req.Scheme == "" {
		req.Scheme = "all"
	}
	if req.VisitorBindMode == "" {
		req.VisitorBindMode = "all"
	}
	if req.VisitorTTLSeconds <= 0 {
		req.VisitorTTLSeconds = 7200
	}
	if err := s.store.HostRouteConflict(req.Host.Host, req.Location, 0); err != nil {
		c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
		return
	}
	if err := s.store.CreateHost(&req.Host); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if err := s.saveResourceVisitors("host", req.ID, req.VisitorAuthEnabled, req.VisitorBindMode, req.VisitorIDs); err != nil {
		_ = s.store.DeleteHost(req.ID)
		writeVisitorSaveError(c, err)
		return
	}
	s.cache.UpsertHost(&req.Host)
	view, _ := s.enrichHostView(req.Host)
	c.JSON(http.StatusOK, view)
}

func (s *Server) updateHost(c *gin.Context) {
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	claims := getClaims(c)
	existing, err := s.store.GetHostByID(uint(id))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if err := store.ClientOwnerCheck(s.store, existing.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	var req hostRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	req.ID = existing.ID
	req.ClientID = existing.ClientID
	if req.VisitorBindMode == "" {
		req.VisitorBindMode = "all"
	}
	if req.VisitorTTLSeconds <= 0 {
		req.VisitorTTLSeconds = 7200
	}
	if err := s.store.HostRouteConflict(req.Host.Host, req.Location, uint(id)); err != nil {
		c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
		return
	}
	if err := s.store.UpdateHost(&req.Host); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if err := s.saveResourceVisitors("host", req.ID, req.VisitorAuthEnabled, req.VisitorBindMode, req.VisitorIDs); err != nil {
		writeVisitorSaveError(c, err)
		return
	}
	s.cache.UpsertHost(&req.Host)
	view, _ := s.enrichHostView(req.Host)
	c.JSON(http.StatusOK, view)
}

func (s *Server) deleteHost(c *gin.Context) {
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	claims := getClaims(c)
	existing, err := s.store.GetHostByID(uint(id))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if err := store.ClientOwnerCheck(s.store, existing.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}
	if err := s.store.DeleteHost(uint(id)); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	s.cache.DeleteHost(uint(id))
	s.cache.DeleteResourceVisitors("host", uint(id))
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) listIPRules(c *gin.Context) {
	scope := c.Query("scope")
	resourceID, _ := strconv.ParseUint(c.Query("resource_id"), 10, 64)
	rules, err := s.store.ListIPRules(scope, uint(resourceID))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, rules)
}

func (s *Server) createIPRule(c *gin.Context) {
	var req store.IPRule
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	if req.PatternKind == "" {
		req.PatternKind = security.DetectPatternKind(req.Pattern)
	}
	if err := s.store.CreateIPRule(&req); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.cache.ReloadIPRules(s.store)
	c.JSON(http.StatusOK, req)
}

func (s *Server) deleteIPRule(c *gin.Context) {
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	if err := s.store.DeleteIPRule(uint(id)); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.cache.ReloadIPRules(s.store)
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) listRequestIPs(c *gin.Context) {
	resourceType := c.Query("resource_type")
	resourceID, _ := strconv.ParseUint(c.Query("resource_id"), 10, 64)
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
	offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
	q := c.Query("q")
	if resourceType == "" && resourceID == 0 {
		items, total, err := s.store.ListAllRequestIPs(limit, offset, q)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"items": requestIPViews(s.store, items), "total": total})
		return
	}
	items, total, err := s.store.ListRequestIPs(resourceType, uint(resourceID), limit, offset)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": requestIPViews(s.store, items), "total": total})
}

func (s *Server) blockIP(c *gin.Context) {
	var req struct {
		IP           string `json:"ip"`
		Scope        string `json:"scope"`
		ResourceType string `json:"resource_type"`
		ResourceID   uint   `json:"resource_id"`
		Remark       string `json:"remark"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	scope := req.Scope
	if scope == "" {
		scope = req.ResourceType
	}
	if scope == "" {
		scope = "global"
	}
	if err := s.security.BlockIP(req.IP, scope, req.ResourceID, req.Remark); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) listAuthPolicies(c *gin.Context) {
	resourceType := c.Query("resource_type")
	resourceID, _ := strconv.ParseUint(c.Query("resource_id"), 10, 64)
	policies, err := s.store.ListAuthPolicies(resourceType, uint(resourceID))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, policies)
}

func (s *Server) createAuthPolicy(c *gin.Context) {
	var req store.AuthPolicy
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	if err := s.resAuth.CreatePolicy(s.store, &req); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.cache.ReloadAll(s.store)
	c.JSON(http.StatusOK, req)
}

func (s *Server) deleteAuthPolicy(c *gin.Context) {
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	if err := s.store.DeleteAuthPolicy(uint(id)); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.cache.ReloadAll(s.store)
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) listUsers(c *gin.Context) {
	claims := getClaims(c)
	if !auth.IsAdmin(claims.Role) {
		c.JSON(http.StatusForbidden, gin.H{"error": "admin only"})
		return
	}
	users, err := s.store.ListVisitors()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, users)
}

func (s *Server) listVisitorAccessLogs(c *gin.Context) {
	claims := getClaims(c)
	if !auth.IsAdmin(claims.Role) {
		c.JSON(http.StatusForbidden, gin.H{"error": "admin only"})
		return
	}
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	user, err := s.store.GetUserByID(uint(id))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if user.Role != "visitor" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "仅可查看访客访问记录"})
		return
	}
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "200"))
	logs, err := s.store.ListVisitorAccessLogs(uint(id), limit)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	items := make([]gin.H, 0, len(logs))
	for _, log := range logs {
		items = append(items, gin.H{
			"id":             log.ID,
			"user_id":        log.UserID,
			"resource_type":  log.ResourceType,
			"resource_id":    log.ResourceID,
			"resource_label": visitorResourceLabel(s.store, log.ResourceType, log.ResourceID),
			"action":         log.Action,
			"method":         log.Method,
			"path":           log.Path,
			"ip":             log.IP,
			"user_agent":     log.UserAgent,
			"created_at":     log.CreatedAt,
		})
	}
	c.JSON(http.StatusOK, gin.H{"items": items, "total": len(items)})
}

func (s *Server) createUser(c *gin.Context) {
	claims := getClaims(c)
	if !auth.IsAdmin(claims.Role) {
		c.JSON(http.StatusForbidden, gin.H{"error": "admin only"})
		return
	}
	var req struct {
		Username string `json:"username"`
		Password string `json:"password"`
		Role     string `json:"role"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	hash, err := store.HashPassword(req.Password)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if req.Role == "" {
		req.Role = "visitor"
	}
	if req.Role != "visitor" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "仅支持创建访客用户"})
		return
	}
	user := store.User{
		Username:     req.Username,
		PasswordHash: hash,
		Role:         req.Role,
		Status:       true,
	}
	if err := s.store.CreateUser(&user); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, user)
}

func (s *Server) updateUser(c *gin.Context) {
	claims := getClaims(c)
	if !auth.IsAdmin(claims.Role) {
		c.JSON(http.StatusForbidden, gin.H{"error": "admin only"})
		return
	}
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	user, err := s.store.GetUserByID(uint(id))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if user.Role != "visitor" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "仅可编辑访客用户"})
		return
	}
	var req struct {
		Password string `json:"password"`
		Role     string `json:"role"`
		Status   *bool  `json:"status"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
		return
	}
	if req.Password != "" {
		hash, err := store.HashPassword(req.Password)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		user.PasswordHash = hash
	}
	if req.Role != "" {
		if req.Role != "visitor" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "仅支持访客用户"})
			return
		}
		user.Role = req.Role
	}
	if req.Status != nil {
		user.Status = *req.Status
	}
	if err := s.store.UpdateUser(user); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, user)
}

func (s *Server) deleteUser(c *gin.Context) {
	claims := getClaims(c)
	if !auth.IsAdmin(claims.Role) {
		c.JSON(http.StatusForbidden, gin.H{"error": "admin only"})
		return
	}
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	user, err := s.store.GetUserByID(uint(id))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if user.Role != "visitor" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "仅可删除访客用户"})
		return
	}
	if err := s.store.DeleteUser(uint(id), claims.UserID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}
