package main

import (
	"flag"
	"fmt"
	"os"
	"os/signal"
	"runtime"
	"runtime/debug"
	"syscall"
	"time"

	"Noooste/garage-ui/internal/auth"
	"Noooste/garage-ui/internal/config"
	"Noooste/garage-ui/internal/handlers"
	appmw "Noooste/garage-ui/internal/middleware"
	"Noooste/garage-ui/internal/routes"
	"Noooste/garage-ui/internal/services"
	"Noooste/garage-ui/pkg/logger"

	"github.com/gofiber/fiber/v3"
	"github.com/gofiber/fiber/v3/middleware/recover"
	"github.com/rs/zerolog/log"
)

//	@title			Garage UI API
//	@version		0.1.0
//	@description	REST API for managing Garage distributed object storage system
//	@description	This API provides endpoints for managing buckets, objects, users, and cluster operations.
//	@termsOfService	http://swagger.io/terms/

//	@license.name	MIT
//	@license.url	https://opensource.org/licenses/MIT

//	@host		localhost:8080
//	@BasePath	/
//	@schemes	http https

//	@tag.name			Health
//	@tag.description	Health check endpoints

//	@tag.name			Buckets
//	@tag.description	Bucket management operations

//	@tag.name			Objects
//	@tag.description	Object storage and retrieval operations

//	@tag.name			Users
//	@tag.description	User and access key management

//	@tag.name			Cluster
//	@tag.description	Cluster status and node management

//	@tag.name			Monitoring
//	@tag.description	Monitoring and metrics endpoints

//	@securityDefinitions.apikey	BearerAuth
//	@in							header
//	@name						Authorization
//	@description				Type "Bearer" followed by a space and JWT token.

var version = "dev"

func main() {
	// Parse command-line flags
	configPath := flag.String("config", "config.yaml", "Path to configuration file")
	flag.Parse()

	// Load configuration first (before initializing logger)
	cfg, err := config.Load(*configPath)
	if err != nil {
		// If config fails to load, use default logger to report the error
		logger.Get().Fatal().Err(err).Str("config_path", *configPath).Msg("Failed to load configuration")
	}

	// Initialize logger with configuration from config file
	logger.Init(logger.Config{
		Level:  cfg.Logging.Level,
		Format: cfg.Logging.Format,
	})

	// Now log with the properly configured logger
	logger.Info().
		Str("config_path", *configPath).
		Str("version", version).
		Str("go_version", runtime.Version()).
		Str("environment", cfg.Server.Environment).
		Msg("Starting Garage UI Backend")

	// Initialize services
	logger.Info().Msg("Initializing Garage Admin service")
	adminService := services.NewGarageAdminService(&cfg.Garage, cfg.Logging.Level)

	logger.Info().Msg("Initializing S3 service")
	s3Service := services.NewS3Service(&cfg.Garage, adminService)

	// Determine enabled auth methods for logging
	authMethods := []string{}
	if cfg.Auth.Admin.Enabled {
		authMethods = append(authMethods, "admin")
	}
	if cfg.Auth.OIDC.Enabled {
		authMethods = append(authMethods, "oidc")
	}
	if len(authMethods) == 0 {
		authMethods = append(authMethods, "none")
	}
	logger.Info().Strs("enabled_methods", authMethods).Msg("Initializing authentication service")
	authService, err := auth.NewAuthService(&cfg.Auth, &cfg.Server)
	if err != nil {
		logger.Fatal().Err(err).Msg("Failed to initialize auth service")
	}

	// Initialize handlers
	healthHandler := handlers.NewHealthHandler(version)
	bucketHandler := handlers.NewBucketHandler(adminService, s3Service)
	objectHandler := handlers.NewObjectHandler(s3Service)
	userHandler := handlers.NewUserHandler(adminService)
	clusterHandler := handlers.NewClusterHandler(adminService)
	monitoringHandler := handlers.NewMonitoringHandler(adminService, s3Service)

	// Set default values for buffer sizes if not configured
	maxBodySize := cfg.Server.MaxBodySize
	if maxBodySize == 0 {
		maxBodySize = 300 * 1024 * 1024 // 300MB default
	}
	maxHeaderSize := cfg.Server.MaxHeaderSize
	if maxHeaderSize == 0 {
		maxHeaderSize = 1 * 1024 * 1024 // 1MB default
	}
	readBufferSize := cfg.Server.ReadBufferSize
	if readBufferSize == 0 {
		readBufferSize = 4096 // 4KB default
	}
	writeBufferSize := cfg.Server.WriteBufferSize
	if writeBufferSize == 0 {
		writeBufferSize = 4096 // 4KB default
	}

	logger.Info().
		Int64("max_body_bytes", maxBodySize).
		Float64("max_body_mb", float64(maxBodySize)/(1024*1024)).
		Int("max_header_bytes", maxHeaderSize).
		Float64("max_header_kb", float64(maxHeaderSize)/1024).
		Msg("Server request limits configured")

	// Create Fiber app with configuration
	app := fiber.New(fiber.Config{
		AppName:         "Garage UI Backend | Version: " + version,
		BodyLimit:       int(maxBodySize),
		ReadBufferSize:  readBufferSize,
		WriteBufferSize: writeBufferSize,
		ErrorHandler:    customErrorHandler,
	})

	// Apply global middleware (order matters):
	//   1. recover — must be outermost so panics become 500s.
	//   2. RequestID — mints/reads X-Request-ID before any logger needs it.
	//   3. Logging — builds per-request zerolog logger + emits access log.
	// Auth middleware is installed per-route inside routes.SetupRoutes.
	app.Use(recover.New(recover.Config{
		EnableStackTrace: true,
		StackTraceHandler: func(c fiber.Ctx, e interface{}) {
			logger.FromCtx(c.Context()).Error().
				Interface("panic", e).
				Bytes("stack", debug.Stack()).
				Msg("panic_recovered")
		},
	}))
	app.Use(appmw.RequestID())
	app.Use(appmw.Logging(log.Logger))

	// Setup routes
	logger.Info().Msg("Setting up routes")
	routes.SetupRoutes(
		app,
		cfg,
		authService,
		healthHandler,
		bucketHandler,
		objectHandler,
		userHandler,
		clusterHandler,
		monitoringHandler,
	)

	// Start server in a goroutine
	go func() {
		addr := cfg.GetAddress()
		logger.Info().
			Str("address", addr).
			Str("health_endpoint", fmt.Sprintf("http://%s/health", addr)).
			Str("api_docs", fmt.Sprintf("http://%s/api/v1/", addr)).
			Msg("Server starting")

		if err := app.Listen(addr); err != nil {
			logger.Fatal().Err(err).Msg("Failed to start server")
		}
	}()

	// Wait for interrupt signal to gracefully shutdown the server.
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
	sig := <-quit

	logger.Info().Str("signal", sig.String()).Msg("Shutting down server")
	shutdownStart := time.Now()
	if err := app.Shutdown(); err != nil {
		logger.Fatal().Err(err).Msg("Server shutdown failed")
	}

	logger.Info().
		Dur("shutdown_duration", time.Since(shutdownStart)).
		Msg("Server stopped gracefully")
}

// customErrorHandler handles errors globally. It uses the per-request logger
// from c.Context() so request_id / user_id attach automatically, and it
// demotes expected 4xx responses to warn (5xx stays at error).
func customErrorHandler(c fiber.Ctx, err error) error {
	code := fiber.StatusInternalServerError
	if e, ok := err.(*fiber.Error); ok {
		code = e.Code
	}

	l := logger.FromCtx(c.Context())
	evt := l.Error()
	if code >= 400 && code < 500 {
		evt = l.Warn()
	}
	evt.Err(err).Int("status_code", code).Msg("request_error")

	return c.Status(code).JSON(fiber.Map{
		"success": false,
		"error": fiber.Map{
			"code":    fmt.Sprintf("ERROR_%d", code),
			"message": err.Error(),
		},
	})
}
