package api

import (
	"context"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/prism/proxy/internal/auth"

	"github.com/prism/proxy/internal/country"
	"github.com/prism/proxy/internal/health"
	"github.com/prism/proxy/internal/metrics"
	applog "github.com/prism/proxy/internal/log"
	"github.com/prism/proxy/internal/mihomoapi"
	"github.com/prism/proxy/internal/service"
	"github.com/prism/proxy/internal/store"
	"github.com/prism/proxy/internal/subscription"
	"github.com/prism/proxy/internal/trafficlog"
)

type Server struct {
	app          *service.App
	health       *health.Checker
	metrics      *metrics.Collector
	subscription *subscription.Service
	logs         *applog.Collector
	traffic      *trafficlog.Collector
	loginLimiter *auth.LoginLimiter
}

func NewServer(app *service.App, hc *health.Checker, mc *metrics.Collector, sub *subscription.Service, logs *applog.Collector, traffic *trafficlog.Collector) *Server {
	return &Server{
		app:          app,
		health:       hc,
		metrics:      mc,
		subscription: sub,
		logs:         logs,
		traffic:      traffic,
		loginLimiter: auth.NewLoginLimiter(app.Store),
	}
}

func (s *Server) syncMihomoClient(ctx context.Context) {
	base, secret, err := s.app.MihomoAPIBase(ctx)
	if err != nil {
		return
	}
	s.health.UpdateAPIBase(base, secret)
	s.metrics.UpdateAPIBase(base, secret)
	if s.traffic != nil {
		s.traffic.UpdateClient(mihomoapi.New(base, secret))
	}
}

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

	r.GET("/api/v1/openapi.json", s.serveOpenAPIJSON)
	r.GET("/api/v1/openapi.yaml", s.serveOpenAPIYAML)
	r.GET("/api/docs", s.serveOpenAPIDocs)

	api := r.Group("/api/v1")
	api.POST("/auth/login", s.login)
	api.GET("/auth/status", s.authStatus)

	protected := api.Group("")
	protected.Use(s.authMiddleware())
	{
		protected.GET("/dashboard", s.dashboard)
		protected.GET("/connections", s.connections)

		protected.GET("/subscriptions", s.listSubscriptions)
		protected.POST("/subscriptions", s.createSubscription)
		protected.PUT("/subscriptions/:id", s.updateSubscription)
		protected.DELETE("/subscriptions/:id", s.deleteSubscription)
		protected.POST("/subscriptions/:id/refresh", s.refreshSubscription)

		protected.GET("/proxies", s.listProxies)
		protected.POST("/proxies", s.createProxy)
		protected.PUT("/proxies/:id", s.updateProxy)
		protected.DELETE("/proxies/:id", s.deleteProxy)
		protected.POST("/proxies/:id/test", s.testProxy)
		protected.POST("/proxies/test-all", s.testAllProxies)
		protected.POST("/proxies/rebuild-countries", s.rebuildCountries)
		protected.GET("/proxies/export", s.exportProxies)
		protected.POST("/proxies/import", s.importProxies)
		protected.GET("/countries", s.listCountries)

		protected.GET("/outbound", s.outboundOverview)
		protected.PUT("/outbound/groups/:name", s.selectOutboundGroup)
		protected.PUT("/outbound/default-policy", s.setDefaultPolicy)

		protected.GET("/traffic-logs", s.listTrafficLogs)

		protected.GET("/rules", s.listRules)
		protected.POST("/rules", s.createRule)
		protected.PUT("/rules/:id", s.updateRule)
		protected.DELETE("/rules/:id", s.deleteRule)
		protected.GET("/rules/merged", s.mergedRules)
		protected.POST("/rules/test", s.testRule)
		protected.GET("/rules/export", s.exportRules)
		protected.POST("/rules/import", s.importRules)

		protected.GET("/settings", s.getSettings)
		protected.PUT("/settings", s.putSettings)

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

		protected.GET("/logs", s.listLogs)
		protected.GET("/audit-logs", s.listAuditLogs)

		protected.GET("/system/status", s.systemStatus)
		protected.POST("/system/reload", s.systemReload)
		protected.GET("/system/geo", s.geoStatus)
		protected.POST("/system/geo/update", s.geoUpdate)
	}

	r.NoRoute(s.serveSPA)
	return r
}

func (s *Server) corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-API-Key")
		c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
		if c.Request.Method == http.MethodOptions {
			c.AbortWithStatus(204)
			return
		}
		c.Next()
	}
}

func (s *Server) dashboard(c *gin.Context) {
	ctx := c.Request.Context()
	s.metrics.RefreshAsync(10 * time.Second)
	s.app.RefreshEngineStatusAsync()
	history, _ := s.app.Store.ListTrafficSnapshots(ctx, 60)
	conns := s.metrics.CachedConnections()
	policyGroups, _ := s.app.PolicyGroupRuntimes(ctx)
	names, _ := s.app.ProxyDisplayNames(ctx)
	liveDomainsAll, liveNodesAll := metrics.AggregateConnections(conns, policyGroups)
	liveDomainsAll = metrics.ApplyDomainLabels(liveDomainsAll, names)
	liveNodesAll = metrics.ApplyTrafficLabels(liveNodesAll, names)

	liveDomainLimit, liveDomainOffset := parseLimitOffset(c.DefaultQuery("live_domain_limit", "50"), c.DefaultQuery("live_domain_offset", "0"))
	liveNodeLimit, liveNodeOffset := parseLimitOffset(c.DefaultQuery("live_node_limit", "50"), c.DefaultQuery("live_node_offset", "0"))
	periodDomainLimit, periodDomainOffset := parseLimitOffset(c.DefaultQuery("period_domain_limit", "50"), c.DefaultQuery("period_domain_offset", "0"))
	periodNodeLimit, periodNodeOffset := parseLimitOffset(c.DefaultQuery("period_node_limit", "50"), c.DefaultQuery("period_node_offset", "0"))

	liveDomains, liveDomainTotal := paginateSlice(liveDomainsAll, liveDomainLimit, liveDomainOffset)
	liveNodes, liveNodeTotal := paginateSlice(liveNodesAll, liveNodeLimit, liveNodeOffset)

	hostNodeTotals, _ := s.app.Store.AggregateTrafficByHostNode(ctx, 24, periodDomainLimit, periodDomainOffset)
	nodeTotals, _ := s.app.Store.AggregateTrafficByNode(ctx, 24, periodNodeLimit, periodNodeOffset)
	periodDomainTotal, _ := s.app.Store.CountAggregateTrafficByHostNode(ctx, 24)
	periodNodeTotal, _ := s.app.Store.CountAggregateTrafficByNode(ctx, 24)
	periodDomains := metrics.TrafficHostNodeAggToItems(hostNodeTotals, names)
	periodNodes := metrics.TrafficAggToItems(nodeTotals, names)

	c.JSON(http.StatusOK, gin.H{
		"snapshot":           s.metrics.Snapshot(ctx),
		"history":            history,
		"connections":        conns,
		"engine_running":     s.app.EngineStatusCached(),
		"live_domains":       liveDomains,
		"live_nodes":         liveNodes,
		"live_domain_total":  liveDomainTotal,
		"live_node_total":    liveNodeTotal,
		"period_domains":     periodDomains,
		"period_nodes":       periodNodes,
		"period_domain_total": periodDomainTotal,
		"period_node_total":  periodNodeTotal,
		"period_hours":       24,
	})
}

func (s *Server) connections(c *gin.Context) {
	conns, err := s.metrics.GetConnectionsCached(8 * time.Second)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"connections": conns})
}

func (s *Server) listSubscriptions(c *gin.Context) {
	items, err := s.app.Store.ListSubscriptions(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": items})
}

func (s *Server) createSubscription(c *gin.Context) {
	var sub store.Subscription
	if err := c.ShouldBindJSON(&sub); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if sub.Type == "" {
		sub.Type = "auto"
	}
	if sub.IntervalSec <= 0 {
		sub.IntervalSec = 3600
	}
	sub.Enabled = true
	if err := s.app.Store.CreateSubscription(c.Request.Context(), &sub); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.app.Store.AddAuditLog(c.Request.Context(), "create", "subscription", sub.Name)
	go func(id int64) {
		ctx := context.Background()
		_ = s.subscription.FetchAndStore(ctx, id)
		_ = s.app.ReloadEngine(ctx)
	}(sub.ID)
	c.JSON(http.StatusCreated, sub)
}

func (s *Server) updateSubscription(c *gin.Context) {
	id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
	sub, err := s.app.Store.GetSubscription(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if err := c.ShouldBindJSON(sub); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	sub.ID = id
	if err := s.app.Store.UpdateSubscription(c.Request.Context(), sub); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, sub)
}

func (s *Server) deleteSubscription(c *gin.Context) {
	id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
	if err := s.app.Store.DeleteSubscription(c.Request.Context(), id); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.app.ReloadEngine(c.Request.Context())
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) refreshSubscription(c *gin.Context) {
	id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
	if err := s.subscription.FetchAndStore(c.Request.Context(), id); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if err := s.app.ReloadEngine(c.Request.Context()); err != nil {
		c.JSON(http.StatusOK, gin.H{"ok": true, "warning": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) listProxies(c *gin.Context) {
	source := c.Query("source")
	country := c.Query("country")
	var subID *int64
	if v := c.Query("subscription_id"); v != "" {
		id, _ := strconv.ParseInt(v, 10, 64)
		subID = &id
	}
	limit, offset := parseLimitOffset(c.DefaultQuery("limit", "50"), c.DefaultQuery("offset", "0"))
	items, err := s.app.Store.ListProxyNodesPage(c.Request.Context(), source, subID, country, limit, offset)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	total, err := s.app.Store.CountProxyNodes(c.Request.Context(), source, subID, country)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
}

type proxyReq struct {
	Name      string `json:"name"`
	Type      string `json:"type"`
	RawConfig string `json:"raw_config"`
	Enabled   *bool  `json:"enabled"`
}

func (s *Server) createProxy(c *gin.Context) {
	var req proxyReq
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	n := store.ProxyNode{
		Name:        req.Name,
		RuntimeName: store.RuntimeName("manual", 0, req.Name),
		Type:        req.Type,
		RawConfig:   req.RawConfig,
		Source:      "manual",
		Country:     country.Detect(req.Name),
		Enabled:     true,
	}
	if req.Enabled != nil {
		n.Enabled = *req.Enabled
	}
	if err := s.app.Store.CreateProxyNode(c.Request.Context(), &n); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.app.ReloadEngine(c.Request.Context())
	c.JSON(http.StatusCreated, n)
}

func (s *Server) updateProxy(c *gin.Context) {
	id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
	n, err := s.app.Store.GetProxyNode(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	var req proxyReq
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if req.Name != "" {
		n.Name = req.Name
		n.Country = country.Detect(req.Name)
	}
	if req.Type != "" {
		n.Type = req.Type
	}
	if req.RawConfig != "" {
		n.RawConfig = req.RawConfig
	}
	if req.Enabled != nil {
		n.Enabled = *req.Enabled
	}
	if err := s.app.Store.UpdateProxyNode(c.Request.Context(), n); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.app.ReloadEngine(c.Request.Context())
	c.JSON(http.StatusOK, n)
}

func (s *Server) deleteProxy(c *gin.Context) {
	id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
	n, err := s.app.Store.GetProxyNode(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if n.Source != "manual" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "only manual proxies can be deleted"})
		return
	}
	if err := s.app.Store.DeleteProxyNode(c.Request.Context(), id); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.app.ReloadEngine(c.Request.Context())
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) testProxy(c *gin.Context) {
	id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
	latency, err := s.health.TestNode(c.Request.Context(), id, s.app.ReloadEngine)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{"latency_ms": -1, "alive": false, "error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"latency_ms": latency, "alive": latency >= 0})
}

func (s *Server) testAllProxies(c *gin.Context) {
	if err := s.health.TestAll(c.Request.Context(), s.app.ReloadEngine); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) listRules(c *gin.Context) {
	source := c.DefaultQuery("source", "manual")
	items, err := s.app.Store.ListRules(c.Request.Context(), source)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": items})
}

func (s *Server) createRule(c *gin.Context) {
	var r store.Rule
	if err := c.ShouldBindJSON(&r); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	r.Source = "manual"
	r.Enabled = true
	if r.Priority <= 0 {
		p, _ := s.app.Store.NextManualRulePriority(c.Request.Context())
		r.Priority = p
	}
	if err := s.app.Store.CreateRule(c.Request.Context(), &r); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.app.ReloadEngine(c.Request.Context())
	c.JSON(http.StatusCreated, r)
}

func (s *Server) updateRule(c *gin.Context) {
	id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
	r, err := s.app.Store.GetRule(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if err := c.ShouldBindJSON(r); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	r.ID = id
	if err := s.app.Store.UpdateRule(c.Request.Context(), r); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.app.ReloadEngine(c.Request.Context())
	c.JSON(http.StatusOK, r)
}

func (s *Server) deleteRule(c *gin.Context) {
	id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
	if err := s.app.Store.DeleteRule(c.Request.Context(), id); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	_ = s.app.ReloadEngine(c.Request.Context())
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

func (s *Server) listLogs(c *gin.Context) {
	level := c.Query("level")
	keyword := c.Query("keyword")
	limit, offset := parseLimitOffset(c.DefaultQuery("limit", "50"), c.DefaultQuery("offset", "0"))
	items, total, err := s.app.Store.ListRequestLogs(c.Request.Context(), level, keyword, limit, offset)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
}

func (s *Server) listAuditLogs(c *gin.Context) {
	limit, offset := parseLimitOffset(c.DefaultQuery("limit", "50"), c.DefaultQuery("offset", "0"))
	items, err := s.app.Store.ListAuditLogs(c.Request.Context(), limit, offset)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	total, err := s.app.Store.CountAuditLogs(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
}

func (s *Server) systemStatus(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"engine_running":    s.app.EngineReachable(c.Request.Context()),
		"has_config":        len(s.app.Cache.GetLastYAML()) > 0,
		"last_reload_error": s.app.LastReloadError,
	})
}

func (s *Server) systemReload(c *gin.Context) {
	if err := s.app.ReloadEngine(c.Request.Context()); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}
