package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"

	"github.com/kms/api-key-service/internal/config"
	"github.com/kms/api-key-service/internal/database"
	"github.com/kms/api-key-service/internal/handlers"
	"github.com/kms/api-key-service/internal/metrics"
	"github.com/kms/api-key-service/internal/middleware"
	"github.com/kms/api-key-service/internal/repository/postgres"
	"github.com/kms/api-key-service/internal/services"
)

func main() {
	// Initialize configuration
	cfg := config.NewConfig()
	if err := cfg.Validate(); err != nil {
		log.Fatal("Configuration validation failed:", err)
	}

	// Initialize logger
	logger := initLogger(cfg)
	defer logger.Sync()

	logger.Info("Starting API Key Management Service",
		zap.String("version", cfg.GetString("APP_VERSION")),
		zap.String("environment", cfg.GetString("APP_ENV")),
	)

	// Initialize database
	db, err := database.NewPostgresProvider(
		cfg.GetDatabaseDSN(),
		cfg.GetInt("DB_MAX_OPEN_CONNS"),
		cfg.GetInt("DB_MAX_IDLE_CONNS"),
		cfg.GetString("DB_CONN_MAX_LIFETIME"),
	)
	if err != nil {
		logger.Fatal("Failed to initialize database", zap.Error(err))
	}

	// Run database migrations
	logger.Info("Running database migrations")
	if err := db.Migrate(context.Background(), cfg.GetString("MIGRATION_PATH")); err != nil {
		logger.Fatal("Failed to run migrations", zap.Error(err))
	}

	// Initialize repositories
	appRepo := postgres.NewApplicationRepository(db)
	tokenRepo := postgres.NewStaticTokenRepository(db)
	permRepo := postgres.NewPermissionRepository(db)
	grantRepo := postgres.NewGrantedPermissionRepository(db)

	// Initialize services
	appService := services.NewApplicationService(appRepo, logger)
	tokenService := services.NewTokenService(tokenRepo, appRepo, permRepo, grantRepo, cfg.GetString("INTERNAL_HMAC_KEY"), logger)
	authService := services.NewAuthenticationService(cfg, logger)

	// Initialize handlers
	healthHandler := handlers.NewHealthHandler(db, logger)
	appHandler := handlers.NewApplicationHandler(appService, authService, logger)
	tokenHandler := handlers.NewTokenHandler(tokenService, authService, logger)
	authHandler := handlers.NewAuthHandler(authService, tokenService, logger)

	// Set up router
	router := setupRouter(cfg, logger, healthHandler, appHandler, tokenHandler, authHandler)

	// Create HTTP server
	srv := &http.Server{
		Addr:         cfg.GetServerAddress(),
		Handler:      router,
		ReadTimeout:  cfg.GetDuration("SERVER_READ_TIMEOUT"),
		WriteTimeout: cfg.GetDuration("SERVER_WRITE_TIMEOUT"),
		IdleTimeout:  cfg.GetDuration("SERVER_IDLE_TIMEOUT"),
	}

	// Initialize bootstrap data
	logger.Info("Initializing bootstrap data")
	if err := initializeBootstrapData(context.Background(), appService, tokenService, cfg, logger); err != nil {
		logger.Fatal("Failed to initialize bootstrap data", zap.Error(err))
	}

	// Start server in goroutine
	go func() {
		logger.Info("Starting HTTP server", zap.String("address", srv.Addr))
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Fatal("Failed to start server", zap.Error(err))
		}
	}()

	// Start metrics server if enabled
	var metricsSrv *http.Server
	if cfg.GetBool("METRICS_ENABLED") {
		metricsSrv = startMetricsServer(cfg, logger)
	}

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

	logger.Info("Shutting down server...")

	// Give outstanding requests time to complete
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Shutdown main server
	if err := srv.Shutdown(ctx); err != nil {
		logger.Error("Server forced to shutdown", zap.Error(err))
	}

	// Shutdown metrics server
	if metricsSrv != nil {
		if err := metricsSrv.Shutdown(ctx); err != nil {
			logger.Error("Metrics server forced to shutdown", zap.Error(err))
		}
	}

	logger.Info("Server exited")
}

func initLogger(cfg config.ConfigProvider) *zap.Logger {
	var logger *zap.Logger
	var err error

	if cfg.IsProduction() {
		logger, err = zap.NewProduction()
	} else {
		logger, err = zap.NewDevelopment()
	}

	if err != nil {
		log.Fatal("Failed to initialize logger:", err)
	}

	return logger
}

func setupRouter(cfg config.ConfigProvider, logger *zap.Logger, healthHandler *handlers.HealthHandler, appHandler *handlers.ApplicationHandler, tokenHandler *handlers.TokenHandler, authHandler *handlers.AuthHandler) *gin.Engine {
	// Set Gin mode based on environment
	if cfg.IsProduction() {
		gin.SetMode(gin.ReleaseMode)
	}

	router := gin.New()

	// Add middleware
	router.Use(middleware.Logger(logger))
	router.Use(middleware.Recovery(logger))
	router.Use(metrics.Middleware(logger))
	router.Use(middleware.CORS())
	router.Use(middleware.Security())
	router.Use(middleware.ValidateContentType())

	if cfg.GetBool("RATE_LIMIT_ENABLED") {
		router.Use(middleware.RateLimit(cfg.GetInt("RATE_LIMIT_RPS"), cfg.GetInt("RATE_LIMIT_BURST")))
	}

	// Health check endpoint (no authentication required)
	router.GET("/health", healthHandler.Health)
	router.GET("/ready", healthHandler.Ready)

	// API routes
	api := router.Group("/api")
	{
		// Authentication endpoints (no prior auth required)
		api.POST("/login", authHandler.Login)
		api.POST("/verify", authHandler.Verify)
		api.POST("/renew", authHandler.Renew)

		// Protected routes (require authentication)
		protected := api.Group("/")
		protected.Use(middleware.Authentication(cfg, logger))
		{
			// Application management
			protected.GET("/applications", appHandler.List)
			protected.POST("/applications", appHandler.Create)
			protected.GET("/applications/:id", appHandler.GetByID)
			protected.PUT("/applications/:id", appHandler.Update)
			protected.DELETE("/applications/:id", appHandler.Delete)

			// Token management
			protected.GET("/applications/:id/tokens", tokenHandler.ListByApp)
			protected.POST("/applications/:id/tokens", tokenHandler.Create)
			protected.DELETE("/tokens/:id", tokenHandler.Delete)

			// Documentation endpoint
			protected.GET("/docs", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{
					"service": "API Key Management Service",
					"version": cfg.GetString("APP_VERSION"),
					"documentation": "See README.md and docs/ directory",
					"endpoints": map[string]interface{}{
						"authentication": []string{
							"POST /api/login",
							"POST /api/verify",
							"POST /api/renew",
						},
						"applications": []string{
							"GET /api/applications",
							"POST /api/applications",
							"GET /api/applications/:id",
							"PUT /api/applications/:id",
							"DELETE /api/applications/:id",
						},
						"tokens": []string{
							"GET /api/applications/:id/tokens",
							"POST /api/applications/:id/tokens",
							"DELETE /api/tokens/:id",
						},
					},
				})
			})
		}
	}

	return router
}

func startMetricsServer(cfg config.ConfigProvider, logger *zap.Logger) *http.Server {
	mux := http.NewServeMux()
	
	// Prometheus metrics endpoint
	mux.HandleFunc("/metrics", metrics.PrometheusHandler())
	
	// Health endpoint for metrics server
	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	})

	srv := &http.Server{
		Addr:    cfg.GetMetricsAddress(),
		Handler: mux,
	}

	go func() {
		logger.Info("Starting metrics server", zap.String("address", srv.Addr))
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Error("Failed to start metrics server", zap.Error(err))
		}
	}()

	return srv
}

func initializeBootstrapData(ctx context.Context, appService services.ApplicationService, tokenService services.TokenService, cfg config.ConfigProvider, logger *zap.Logger) error {
	// Check if internal application already exists
	internalAppID := cfg.GetString("INTERNAL_APP_ID")
	_, err := appService.GetByID(ctx, internalAppID)
	if err == nil {
		logger.Info("Internal application already exists, skipping bootstrap")
		return nil
	}

	logger.Info("Creating internal application for bootstrap", zap.String("app_id", internalAppID))

	// This will be implemented when we create the services
	// For now, we'll just log that we need to do this
	logger.Warn("Bootstrap data initialization not yet implemented - will be added when services are ready")

	return nil
}
