package admin

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/rose_cat707/luminary/internal/auth"
	"github.com/rose_cat707/luminary/internal/auth/loginlimit"
	"github.com/rose_cat707/luminary/internal/gateway"
	"github.com/rose_cat707/luminary/internal/middleware"
	"github.com/rose_cat707/luminary/internal/model"
	"github.com/rose_cat707/luminary/internal/monitor"
	"github.com/rose_cat707/luminary/internal/prediction"
	"github.com/rose_cat707/luminary/internal/settings"
	"github.com/rose_cat707/luminary/internal/storage/imagestore"
	"github.com/rose_cat707/luminary/internal/store/cache"
	sqlitestore "github.com/rose_cat707/luminary/internal/store/sqlite"
)

type Handler struct {
	repo        *sqlitestore.Repository
	db          *sqlitestore.Store
	cache       *cache.Store
	gateway     *gateway.Service
	settings    *settings.Runtime
	monitor     *monitor.Service
	loginLimit  *loginlimit.Limiter
	predictions *prediction.Service
	images      *imagestore.Store
	publicURL   func() string
}

func New(repo *sqlitestore.Repository, db *sqlitestore.Store, c *cache.Store, gw *gateway.Service, rt *settings.Runtime, mon *monitor.Service, pred *prediction.Service, imgs *imagestore.Store, publicURL func() string) *Handler {
	return &Handler{
		repo:        repo,
		db:          db,
		cache:       c,
		gateway:     gw,
		settings:    rt,
		monitor:     mon,
		loginLimit:  loginlimit.New(rt.LoginMaxAttempts(), rt.LoginLockout()),
		predictions: pred,
		images:      imgs,
		publicURL:   publicURL,
	}
}

func (h *Handler) publicBaseURL() string {
	if h.publicURL != nil {
		return h.publicURL()
	}
	return "http://localhost:8293"
}

func (h *Handler) Register(r *gin.RouterGroup) {
	r.POST("/login", h.Login)
	r.GET("/auth/status", h.AuthStatus)

	authGroup := r.Group("", middleware.AdminAuth(h.validateSession))
	authGroup.POST("/logout", h.Logout)
	authGroup.PUT("/password", h.ChangePassword)

	authGroup.GET("/endpoint-meta", h.ListEndpointMeta)
	authGroup.GET("/providers", h.ListProviders)
	authGroup.POST("/providers", h.CreateProvider)
	authGroup.GET("/providers/:id", h.GetProvider)
	authGroup.PUT("/providers/:id", h.UpdateProvider)
	authGroup.DELETE("/providers/:id", h.DeleteProvider)
	authGroup.POST("/providers/:id/sync-models", h.SyncModels)

	authGroup.GET("/providers/:id/keys", h.ListProviderKeys)
	authGroup.POST("/providers/:id/keys", h.CreateProviderKey)
	authGroup.PUT("/provider-keys/:keyId", h.UpdateProviderKey)
	authGroup.DELETE("/provider-keys/:keyId", h.DeleteProviderKey)

	authGroup.GET("/providers/:id/models", h.ListModels)
	authGroup.POST("/providers/:id/models", h.CreateModel)
	authGroup.PUT("/providers/:id/models/:modelId", h.UpdateModel)
	authGroup.DELETE("/providers/:id/models/:modelId", h.DeleteModel)
	authGroup.PUT("/models/:modelId", h.UpdateModel)
	authGroup.DELETE("/models/:modelId", h.DeleteModel)

	authGroup.GET("/ingress-keys", h.ListIngressKeys)
	authGroup.POST("/ingress-keys", h.CreateIngressKey)
	authGroup.GET("/ingress-keys/:id", h.GetIngressKey)
	authGroup.PUT("/ingress-keys/:id", h.UpdateIngressKey)
	authGroup.DELETE("/ingress-keys/:id", h.DeleteIngressKey)
	authGroup.GET("/ingress-keys/:id/usage", h.IngressUsage)

	authGroup.GET("/ingress-keys/:id/routing-rules", h.ListRoutingRules)
	authGroup.POST("/ingress-keys/:id/routing-rules", h.CreateRoutingRule)
	authGroup.PUT("/routing-rules/:ruleId", h.UpdateRoutingRule)
	authGroup.DELETE("/routing-rules/:ruleId", h.DeleteRoutingRule)

	authGroup.GET("/workflows/param-meta", h.GetWorkflowParamMeta)
	authGroup.POST("/workflows/analyze", h.AnalyzeWorkflow)
	authGroup.GET("/predictions/:id", h.GetPrediction)
	authGroup.GET("/images/:id", h.ServeAdminImage)

	authGroup.GET("/request-logs", h.ListRequestLogs)
	authGroup.GET("/request-logs/:id", h.GetRequestLog)
	authGroup.GET("/pricing/catalog", h.PricingCatalog)

	authGroup.GET("/ip-rules", h.ListIPRules)
	authGroup.POST("/ip-rules", h.CreateIPRule)
	authGroup.PUT("/ip-rules/:id", h.UpdateIPRule)
	authGroup.DELETE("/ip-rules/:id", h.DeleteIPRule)

	authGroup.GET("/settings", h.GetSettings)
	authGroup.PUT("/settings", h.UpdateSettings)

	authGroup.GET("/dashboard/overview", h.DashboardOverview)
	authGroup.GET("/dashboard/providers", h.DashboardProviders)
	authGroup.GET("/dashboard/ingress-usage", h.DashboardIngressUsage)
	authGroup.GET("/dashboard/egress-usage", h.DashboardEgressUsage)
	authGroup.GET("/dashboard/provider-model-usage", h.DashboardProviderModelUsage)
	authGroup.GET("/dashboard/client-ip-usage", h.DashboardClientIPUsage)
	authGroup.GET("/dashboard/request-trend", h.DashboardRequestTrend)
}

func (h *Handler) validateSession(token string) (bool, time.Time) {
	hash := auth.HashToken(token)
	var session model.AdminSession
	if err := h.db.DB().Where("token_hash = ?", hash).First(&session).Error; err != nil {
		return false, time.Time{}
	}
	return session.ExpiresAt.After(time.Now()), session.ExpiresAt
}

func (h *Handler) Login(c *gin.Context) {
	ip := middleware.ClientIP(c)
	if locked, retryAfter := h.loginLimit.Check(ip); locked {
		sec := int(retryAfter.Seconds()) + 1
		c.Header("Retry-After", strconv.Itoa(sec))
		h.recordLoginLog(ip, "", "error", "登录尝试次数过多", "", fmt.Sprintf(`{"retry_after_sec":%d}`, sec))
		c.JSON(http.StatusTooManyRequests, gin.H{
			"error":           "登录尝试次数过多，请稍后再试",
			"retry_after_sec": sec,
		})
		return
	}

	var req struct {
		Username string `json:"username"`
		Password string `json:"password"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		h.recordLoginLog(ip, "", "error", "invalid request", "", "")
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	reqBody := loginRequestBody(req.Username)

	var user model.AdminUser
	if err := h.db.DB().Where("username = ?", req.Username).First(&user).Error; err != nil {
		h.loginLimit.RecordFailure(ip)
		h.recordLoginLog(ip, req.Username, "error", "invalid credentials", reqBody, "")
		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
		return
	}
	if !auth.CheckPassword(user.PasswordHash, req.Password) {
		h.loginLimit.RecordFailure(ip)
		h.recordLoginLog(ip, req.Username, "error", "invalid credentials", reqBody, "")
		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
		return
	}
	h.loginLimit.Reset(ip)
	token, err := generateSessionToken()
	if err != nil {
		h.recordLoginLog(ip, req.Username, "error", "session error", reqBody, "")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "session error"})
		return
	}
	session := model.AdminSession{
		TokenHash: auth.HashToken(token),
		ExpiresAt: time.Now().Add(h.settings.SessionTTL()),
	}
	if err := h.db.DB().Create(&session).Error; err != nil {
		h.recordLoginLog(ip, req.Username, "error", "session error", reqBody, "")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "session error"})
		return
	}
	respBody := fmt.Sprintf(`{"expires_at":%q}`, session.ExpiresAt.Format(time.RFC3339))
	h.recordLoginLog(ip, req.Username, "success", "", reqBody, respBody)
	c.SetSameSite(http.SameSiteStrictMode)
	c.SetCookie(middleware.SessionCookieName(), token, int(h.settings.SessionTTL().Seconds()), "/", "", false, true)
	c.JSON(http.StatusOK, gin.H{"token": token, "expires_at": session.ExpiresAt})
}

func loginRequestBody(username string) string {
	b, _ := json.Marshal(map[string]string{"username": username})
	return string(b)
}

func (h *Handler) recordLoginLog(ip, username, status, errMsg, requestBody, responseBody string) {
	h.repo.RecordRequestLog(model.UsageRecord{
		ClientIP:     ip,
		Endpoint:     model.EndpointAdminLogin,
		Model:        username,
		Status:       status,
		ErrorMsg:     errMsg,
		RequestBody:  requestBody,
		ResponseBody: responseBody,
	})
}

func (h *Handler) Logout(c *gin.Context) {
	token, _ := c.Get("session_token")
	if t, ok := token.(string); ok {
		h.db.DB().Where("token_hash = ?", auth.HashToken(t)).Delete(&model.AdminSession{})
	}
	c.SetCookie(middleware.SessionCookieName(), "", -1, "/", "", false, true)
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (h *Handler) AuthStatus(c *gin.Context) {
	token := c.GetHeader("Authorization")
	if len(token) > 7 {
		token = token[7:]
	}
	if token == "" {
		if cookie, err := c.Cookie(middleware.SessionCookieName()); err == nil {
			token = cookie
		}
	}
	valid := false
	if token != "" {
		valid, _ = h.validateSession(token)
	}
	c.JSON(http.StatusOK, gin.H{"authenticated": valid})
}

func (h *Handler) ChangePassword(c *gin.Context) {
	var req struct {
		OldPassword string `json:"old_password"`
		NewPassword string `json:"new_password"`
	}
	if err := c.ShouldBindJSON(&req); err != nil || req.NewPassword == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	var user model.AdminUser
	if err := h.db.DB().First(&user).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "user not found"})
		return
	}
	if !auth.CheckPassword(user.PasswordHash, req.OldPassword) {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid old password"})
		return
	}
	hash, err := auth.HashPassword(req.NewPassword)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "hash error"})
		return
	}
	user.PasswordHash = hash
	h.db.DB().Save(&user)
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func generateSessionToken() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}
