//go:generate swag init -g ../../cmd/server/main.go -d ../../pkg/greet,../../pkg/server --parseDependency && mv ../../docs/* ./docs/

package server

import (
	"context"
	"embed"
	"fmt"
	"net"
	"net/http"
	"os/signal"
	"syscall"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/rs/zerolog/log"
	httpSwagger "github.com/swaggo/http-swagger"

	"dance-lessons-coach/pkg/config"
	"dance-lessons-coach/pkg/greet"
	"dance-lessons-coach/pkg/telemetry"
	"dance-lessons-coach/pkg/user"
	userapi "dance-lessons-coach/pkg/user/api"
	"dance-lessons-coach/pkg/validation"
	"dance-lessons-coach/pkg/version"
	"encoding/json"

	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

//go:embed docs/swagger.json
var swaggerJSON embed.FS

type Server struct {
	router         *chi.Mux
	readyCtx       context.Context
	withOTEL       bool
	config         *config.Config
	tracerProvider *sdktrace.TracerProvider
	validator      *validation.Validator
	userRepo       user.UserRepository
	userService    user.UserService
}

func NewServer(cfg *config.Config, readyCtx context.Context) *Server {
	// Create validator instance
	validator, err := validation.GetValidatorFromConfig(cfg)
	if err != nil {
		log.Error().Err(err).Msg("Failed to create validator, continuing without validation")
	} else {
		log.Trace().Msg("Validator created successfully")
	}

	// Initialize user repository and services
	userRepo, userService, err := initializeUserServices(cfg)
	if err != nil {
		log.Warn().Err(err).Msg("Failed to initialize user services, user functionality will be disabled")
	}

	s := &Server{
		router:      chi.NewRouter(),
		readyCtx:    readyCtx,
		withOTEL:    cfg.GetTelemetryEnabled(),
		config:      cfg,
		validator:   validator,
		userRepo:    userRepo,
		userService: userService,
	}
	s.setupRoutes()
	return s
}

// initializeUserServices initializes the user repository and unified user service
func initializeUserServices(cfg *config.Config) (user.UserRepository, user.UserService, error) {
	// Use in-memory SQLite database
	dbPath := "file::memory:?cache=shared"

	// Create user repository
	repo, err := user.NewSQLiteRepository(dbPath, cfg)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to create user repository: %w", err)
	}

	// Create JWT config
	jwtConfig := user.JWTConfig{
		Secret:         cfg.GetJWTSecret(),
		ExpirationTime: time.Hour * 24, // 24 hours
		Issuer:         "dance-lessons-coach",
	}

	// Create unified user service
	userService := user.NewUserService(repo, jwtConfig, cfg.GetAdminMasterPassword())

	return repo, userService, nil
}

func (s *Server) setupRoutes() {
	// Use Zerolog middleware instead of Chi's default logger
	s.router.Use(middleware.RequestLogger(&middleware.DefaultLogFormatter{
		Logger:  &log.Logger,
		NoColor: false,
	}))

	// Health endpoint at root level
	s.router.Get("/api/health", s.handleHealth)

	// Readiness endpoint at root level
	s.router.Get("/api/ready", s.handleReadiness)

	// Version endpoint at root level
	s.router.Get("/api/version", s.handleVersion)

	// API routes
	s.router.Route("/api/v1", func(r chi.Router) {
		r.Use(s.getAllMiddlewares()...)
		s.registerApiV1Routes(r)
	})

	// Register v2 routes if enabled
	if s.config.GetV2Enabled() {
		s.router.Route("/api/v2", func(r chi.Router) {
			r.Use(s.getAllMiddlewares()...)
			s.registerApiV2Routes(r)
		})
	}

	// Add Swagger UI with embedded spec
	// Serve the embedded swagger.json file
	s.router.Handle("/swagger/doc.json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		data, err := swaggerJSON.ReadFile("docs/swagger.json")
		if err != nil {
			log.Error().Err(err).Msg("Failed to read embedded swagger.json")
			http.Error(w, "Failed to read swagger.json", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write(data)
	}))

	// Setup Swagger UI handler
	s.router.Get("/swagger/*", httpSwagger.WrapHandler)
}

func (s *Server) registerApiV1Routes(r chi.Router) {
	greetService := greet.NewService()
	greetHandler := greet.NewApiV1GreetHandler(greetService)

	// Create auth middleware if available
	var authMiddleware *AuthMiddleware
	if s.userService != nil {
		authMiddleware = NewAuthMiddleware(s.userService)
	}

	r.Route("/greet", func(r chi.Router) {
		// Add optional authentication middleware
		if authMiddleware != nil {
			r.Use(authMiddleware.Middleware)
		}
		greetHandler.RegisterRoutes(r)
	})

	// Register user authentication routes
	if s.userService != nil && s.userRepo != nil {
		// Use unified user service - much simpler!
		if s.userService != nil {
			handler := userapi.NewAuthHandler(s.userService, s.userService, s.validator)
			r.Route("/auth", func(r chi.Router) {
				handler.RegisterRoutes(r)
			})
		}
	}
}

func (s *Server) registerApiV2Routes(r chi.Router) {
	greetServiceV2 := greet.NewServiceV2()
	greetHandlerV2 := greet.NewApiV2GreetHandler(greetServiceV2, s.validator)
	r.Route("/greet", func(r chi.Router) {
		greetHandlerV2.RegisterRoutes(r)
	})
}

// getAllMiddlewares returns all middleware including OpenTelemetry if enabled
func (s *Server) getAllMiddlewares() []func(http.Handler) http.Handler {
	middlewares := []func(http.Handler) http.Handler{
		middleware.StripSlashes,
		middleware.Recoverer,
	}

	if s.withOTEL {
		middlewares = append(middlewares, func(next http.Handler) http.Handler {
			return otelhttp.NewHandler(next, "")
		})
	}

	return middlewares
}

// handleHealth godoc
//
//	@Summary		Health check
//	@Description	Check if the service is healthy
//	@Tags			System/Health
//	@Accept			json
//	@Produce		json
//	@Success		200	{object}	map[string]string	"Service is healthy"
//	@Router			/health [get]
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
	log.Trace().Msg("Health check requested")
	w.Write([]byte(`{"status":"healthy"}`))
}

// handleReadiness godoc
//
//	@Summary		Readiness check
//	@Description	Check if the service is ready to accept traffic including detailed connection status
//	@Tags			System/Health
//	@Accept			json
//	@Produce		json
//	@Success		200	{object}	object	"Service is ready with connection details"
//	@Failure		503	{object}	object	"Service is not ready with failure details"
//	@Router			/ready [get]
func (s *Server) handleReadiness(w http.ResponseWriter, r *http.Request) {
	log.Trace().Msg("Readiness check requested")

	// Check if server is shutting down
	select {
	case <-s.readyCtx.Done():
		log.Trace().Msg("Readiness check: not ready (shutting down)")
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusServiceUnavailable)
		json.NewEncoder(w).Encode(map[string]interface{}{
			"ready":  false,
			"reason": "server_shutting_down",
			"connections": map[string]interface{}{
				"database": "not_checked",
			},
		})
		return
	default:
		// Server is not shutting down, check all connections
		connectionStatus := make(map[string]interface{})
		allHealthy := true
		var failureReason string

		// Check database if available
		if s.userRepo != nil {
			if err := s.userRepo.CheckDatabaseHealth(r.Context()); err != nil {
				log.Warn().Err(err).Msg("Database health check failed")
				connectionStatus["database"] = map[string]interface{}{
					"status": "unhealthy",
					"error":  err.Error(),
				}
				allHealthy = false
				failureReason = "database_unhealthy"
			} else {
				connectionStatus["database"] = map[string]interface{}{
					"status": "healthy",
				}
			}
		} else {
			connectionStatus["database"] = map[string]interface{}{
				"status": "not_configured",
			}
		}

		if allHealthy {
			log.Trace().Msg("Readiness check: ready")
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)
			json.NewEncoder(w).Encode(map[string]interface{}{
				"ready":       true,
				"connections": connectionStatus,
			})
		} else {
			log.Warn().Str("reason", failureReason).Msg("Readiness check: not ready")
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusServiceUnavailable)
			json.NewEncoder(w).Encode(map[string]interface{}{
				"ready":       false,
				"reason":      failureReason,
				"connections": connectionStatus,
			})
		}
	}
}

// handleVersion godoc
//
//	@Summary		Get API version
//	@Description	Returns the API version information
//	@Tags			System/Version
//	@Accept			plain,json
//	@Produce		plain,json
//	@Param			format	query		string	false	"Response format (plain, full, json)"	Enums(plain, full, json)	default(plain)
//	@Success		200		{string}	string	"Version information"
//	@Router			/version [get]
func (s *Server) handleVersion(w http.ResponseWriter, r *http.Request) {
	log.Trace().Msg("Version check requested")

	// Get format parameter
	format := r.URL.Query().Get("format")
	if format == "" {
		format = "plain" // default format
	}

	switch format {
	case "plain":
		w.Header().Set("Content-Type", "text/plain")
		w.Write([]byte(version.Short()))
	case "full":
		w.Header().Set("Content-Type", "text/plain")
		w.Write([]byte(version.Full()))
	case "json":
		w.Header().Set("Content-Type", "application/json")
		jsonResponse := fmt.Sprintf(`{
			"version": "%s",
			"commit": "%s",
			"built": "%s",
			"go": "%s"
		}`, version.Version, version.Commit, version.Date, version.GoVersion)
		w.Write([]byte(jsonResponse))
	default:
		w.Header().Set("Content-Type", "text/plain")
		w.Write([]byte(version.Short()))
	}
}

func (s *Server) Router() http.Handler {
	return s.router
}

// Run starts the HTTP server and handles graceful shutdown
func (s *Server) Run() error {
	// Initialize OpenTelemetry if enabled
	var err error
	if s.withOTEL {
		log.Trace().Msg("Initializing OpenTelemetry tracing")

		telemetrySetup := &telemetry.Setup{
			ServiceName:  s.config.GetServiceName(),
			OTLPEndpoint: s.config.GetOTLPEndpoint(),
			Insecure:     s.config.GetTelemetryInsecure(),
			SamplerType:  s.config.GetSamplerType(),
			SamplerRatio: s.config.GetSamplerRatio(),
			Version:      version.Short(),
		}

		if s.tracerProvider, err = telemetrySetup.InitializeTracing(context.Background()); err != nil {
			log.Error().Err(err).Msg("Failed to initialize OpenTelemetry, continuing without tracing")
			s.withOTEL = false
		} else {
			log.Trace().Msg("OpenTelemetry tracing initialized successfully")
		}
	}

	// Setup signal context for graceful shutdown
	rootCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	// Create ongoing context for active requests
	ongoingCtx, stopOngoingGracefully := context.WithCancel(context.Background())
	defer stopOngoingGracefully()

	// Create HTTP server
	log.Trace().Str("address", s.config.GetServerAddress()).Msg("Server running")

	srv := &http.Server{
		Addr:    s.config.GetServerAddress(),
		Handler: s.router,
		BaseContext: func(_ net.Listener) context.Context {
			return ongoingCtx
		},
	}

	// Start the HTTP server in a separate goroutine
	serverErrors := make(chan error, 1)
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			serverErrors <- err
		}
		close(serverErrors)
	}()

	// Wait for signal
	<-rootCtx.Done()
	stop()
	log.Trace().Msg("Shutdown signal received")

	// Cancel readiness context to stop accepting new requests
	if cancelReady, ok := s.readyCtx.(interface{ Cancel() }); ok {
		cancelReady.Cancel()
	}
	log.Trace().Msg("Readiness set to false, no longer accepting new requests")

	// Give time for readiness check to propagate
	time.Sleep(1 * time.Second)
	log.Trace().Msg("Readiness check propagated, now waiting for ongoing requests to finish.")

	// Create shutdown context with timeout from config
	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), s.config.Shutdown.Timeout)
	defer shutdownCancel()

	if err := srv.Shutdown(shutdownCtx); err != nil {
		log.Error().Err(err).Msg("Server shutdown failed")
		return err
	}
	log.Trace().Msg("Server shutdown complete")

	// Shutdown OpenTelemetry tracer provider
	if s.tracerProvider != nil {
		if err := telemetry.Shutdown(context.Background(), s.tracerProvider); err != nil {
			log.Error().Err(err).Msg("Failed to shutdown OpenTelemetry tracer provider")
		} else {
			log.Trace().Msg("OpenTelemetry tracer provider shutdown complete")
		}
	}

	// Return any server errors
	if err, ok := <-serverErrors; ok {
		return err
	}

	return nil
}
