package main

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

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

	"github.com/RyanCopley/skybridge/faas/internal/config"
	"github.com/RyanCopley/skybridge/faas/internal/database"
	"github.com/RyanCopley/skybridge/faas/internal/domain"
	"github.com/RyanCopley/skybridge/faas/internal/handlers"
	"github.com/RyanCopley/skybridge/faas/internal/repository/postgres"
	"github.com/RyanCopley/skybridge/faas/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 Function-as-a-Service",
		zap.String("version", "1.0.0"),
		zap.String("environment", cfg.GetString("FAAS_APP_ENV")),
	)

	// Initialize database
	logger.Info("Connecting to database", 
		zap.String("dsn", cfg.GetDatabaseDSNForLogging()))
	
	db, err := database.NewPostgresProvider(
		cfg.GetDatabaseDSN(),
		cfg.GetInt("DB_MAX_OPEN_CONNS"),
		cfg.GetInt("DB_MAX_IDLE_CONNS"),
		cfg.GetString("DB_CONN_MAX_LIFETIME"),
		logger,
	)
	if err != nil {
		logger.Fatal("Failed to initialize database", 
			zap.String("dsn", cfg.GetDatabaseDSNForLogging()),
			zap.Error(err))
	}
	
	logger.Info("Database connection established successfully")

	// Initialize repositories
	functionRepo := postgres.NewFunctionRepository(db, logger)
	executionRepo := postgres.NewExecutionRepository(db, logger)

	// Initialize services
	runtimeService := services.NewRuntimeService(logger, nil)
	functionService := services.NewFunctionService(functionRepo, runtimeService, logger)
	executionService := services.NewExecutionService(executionRepo, functionRepo, runtimeService, logger)
	authService := services.NewAuthService(logger) // Mock auth service for now

	// Initialize handlers
	healthHandler := handlers.NewHealthHandler(db, logger)
	functionHandler := handlers.NewFunctionHandler(functionService, authService, logger)
	executionHandler := handlers.NewExecutionHandler(executionService, authService, logger)

	// Set up router
	router := setupRouter(cfg, logger, healthHandler, functionHandler, executionHandler)

	// 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"),
	}

	// 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

	logLevel := cfg.GetString("FAAS_LOG_LEVEL")
	
	if cfg.IsProduction() && logLevel != "debug" {
		logger, err = zap.NewProduction()
	} else {
		// Use development logger for non-production or when debug is explicitly requested
		config := zap.NewDevelopmentConfig()
		
		// Set log level based on environment variable
		switch strings.ToLower(logLevel) {
		case "debug":
			config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
		case "info":
			config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
		case "warn":
			config.Level = zap.NewAtomicLevelAt(zap.WarnLevel)
		case "error":
			config.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
		default:
			config.Level = zap.NewAtomicLevelAt(zap.DebugLevel) // Default to debug for development
		}
		
		logger, err = config.Build()
	}

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

	return logger
}

func setupRouter(cfg config.ConfigProvider, logger *zap.Logger, healthHandler *handlers.HealthHandler, functionHandler *handlers.FunctionHandler, executionHandler *handlers.ExecutionHandler) *gin.Engine {
	// Set Gin mode based on environment
	if cfg.IsProduction() {
		gin.SetMode(gin.ReleaseMode)
	}

	router := gin.New()

	// Add middleware
	router.Use(gin.Logger())
	router.Use(gin.Recovery())

	// CORS middleware
	router.Use(func(c *gin.Context) {
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-User-Email")
		
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}
		
		c.Next()
	})

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

	// API routes
	api := router.Group("/api")
	{
		// Function Management
		api.GET("/functions", functionHandler.List)
		api.POST("/functions", functionHandler.Create)
		api.GET("/functions/:id", functionHandler.GetByID)
		api.PUT("/functions/:id", functionHandler.Update)
		api.DELETE("/functions/:id", functionHandler.Delete)
		api.POST("/functions/:id/deploy", functionHandler.Deploy)

		// Function Execution
		api.POST("/functions/:id/execute", executionHandler.Execute)
		api.POST("/functions/:id/invoke", executionHandler.Invoke)
		
		// Execution Management
		api.GET("/executions", executionHandler.List)
		api.GET("/executions/:id", executionHandler.GetByID)
		api.DELETE("/executions/:id", executionHandler.Cancel)
		api.GET("/executions/:id/logs", executionHandler.GetLogs)
		api.GET("/executions/running", executionHandler.GetRunning)

		// Runtime information endpoint
		api.GET("/runtimes", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"runtimes": domain.GetAvailableRuntimes(),
			})
		})

		// Documentation endpoint
		api.GET("/docs", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"service": "Function-as-a-Service",
				"version": "1.0.0",
				"documentation": "FaaS API Documentation",
				"endpoints": map[string]interface{}{
					"functions": []string{
						"GET /api/functions",
						"POST /api/functions",
						"GET /api/functions/:id",
						"PUT /api/functions/:id",
						"DELETE /api/functions/:id",
						"POST /api/functions/:id/deploy",
					},
					"executions": []string{
						"POST /api/functions/:id/execute",
						"POST /api/functions/:id/invoke",
						"GET /api/executions",
						"GET /api/executions/:id",
						"DELETE /api/executions/:id",
						"GET /api/executions/:id/logs",
						"GET /api/executions/running",
					},
				},
			})
		})
	}

	return router
}

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

	// Metrics endpoint would go here
	mux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("# FaaS metrics placeholder\n"))
	})

	srv := &http.Server{
		Addr:    cfg.GetString("FAAS_SERVER_HOST") + ":" + cfg.GetString("METRICS_PORT"),
		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
}