package server

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

	"git.dcentral.systems/toolz/goplt/internal/health"
	"git.dcentral.systems/toolz/goplt/internal/metrics"
	"git.dcentral.systems/toolz/goplt/pkg/config"
	"git.dcentral.systems/toolz/goplt/pkg/errorbus"
	"git.dcentral.systems/toolz/goplt/pkg/logger"
	"github.com/gin-gonic/gin"
	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
	"go.opentelemetry.io/otel/trace"
)

// Server wraps the HTTP server and Gin router.
type Server struct {
	httpServer *http.Server
	router     *gin.Engine
}

// NewServer creates a new HTTP server with all middleware and routes.
func NewServer(
	cfg config.ConfigProvider,
	log logger.Logger,
	healthRegistry *health.Registry,
	metricsRegistry *metrics.Metrics,
	errorBus errorbus.ErrorPublisher,
	tracer trace.TracerProvider,
) (*Server, error) {
	// Set Gin mode
	env := cfg.GetString("environment")
	if env == "production" {
		gin.SetMode(gin.ReleaseMode)
	}

	router := gin.New()

	// Add middleware (order matters!)
	// OpenTelemetry tracing should be first to capture all requests
	if tracer != nil {
		router.Use(otelgin.Middleware("platform", otelgin.WithTracerProvider(tracer)))
	}
	router.Use(RequestIDMiddleware())
	router.Use(LoggingMiddleware(log))
	router.Use(PanicRecoveryMiddleware(errorBus))
	router.Use(metricsRegistry.HTTPMiddleware())
	router.Use(CORSMiddleware())

	// Request timeout middleware (optional, can be configured per route if needed)
	// router.Use(TimeoutMiddleware(timeout))

	// Register core routes
	registerRoutes(router, healthRegistry, metricsRegistry)

	// Get server configuration
	port := cfg.GetInt("server.port")
	if port == 0 {
		port = 8080
	}
	host := cfg.GetString("server.host")
	if host == "" {
		host = "0.0.0.0"
	}

	readTimeout := cfg.GetDuration("server.read_timeout")
	if readTimeout == 0 {
		readTimeout = 30 * time.Second
	}

	writeTimeout := cfg.GetDuration("server.write_timeout")
	if writeTimeout == 0 {
		writeTimeout = 30 * time.Second
	}

	addr := fmt.Sprintf("%s:%d", host, port)

	httpServer := &http.Server{
		Addr:         addr,
		Handler:      router,
		ReadTimeout:  readTimeout,
		WriteTimeout: writeTimeout,
		IdleTimeout:  120 * time.Second,
	}

	return &Server{
		httpServer: httpServer,
		router:     router,
	}, nil
}

// registerRoutes registers all core routes.
func registerRoutes(
	router *gin.Engine,
	healthRegistry *health.Registry,
	metricsRegistry *metrics.Metrics,
) {
	// Health endpoints
	router.GET("/healthz", func(c *gin.Context) {
		status := healthRegistry.LivenessCheck(c.Request.Context())
		if status.Status == "healthy" {
			c.JSON(http.StatusOK, status)
		} else {
			c.JSON(http.StatusServiceUnavailable, status)
		}
	})

	router.GET("/ready", func(c *gin.Context) {
		status := healthRegistry.ReadinessCheck(c.Request.Context())
		if status.Status == "healthy" {
			c.JSON(http.StatusOK, status)
		} else {
			c.JSON(http.StatusServiceUnavailable, status)
		}
	})

	// Metrics endpoint
	router.GET("/metrics", gin.WrapH(metricsRegistry.Handler()))
}

// Start starts the HTTP server.
func (s *Server) Start() error {
	// ListenAndServe will block until the server is closed
	// If there's an immediate error (like port in use), it will return immediately
	return s.httpServer.ListenAndServe()
}

// StartAsync starts the HTTP server in a goroutine and returns a channel that signals when it's ready or errored.
func (s *Server) StartAsync() <-chan error {
	errChan := make(chan error, 1)
	go func() {
		if err := s.Start(); err != nil && err != http.ErrServerClosed {
			errChan <- err
		}
		close(errChan)
	}()
	return errChan
}

// Shutdown gracefully shuts down the HTTP server.
func (s *Server) Shutdown(ctx context.Context) error {
	return s.httpServer.Shutdown(ctx)
}

// Router returns the Gin router (for adding additional routes).
func (s *Server) Router() *gin.Engine {
	return s.router
}
