package main

import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"

	"freeleaps.com/gitea-webhook-ambassador/internal/auth"
	"freeleaps.com/gitea-webhook-ambassador/internal/config"
	"freeleaps.com/gitea-webhook-ambassador/internal/database"
	"freeleaps.com/gitea-webhook-ambassador/internal/handler"
	"freeleaps.com/gitea-webhook-ambassador/internal/jenkins"
	"freeleaps.com/gitea-webhook-ambassador/internal/logger"
	"freeleaps.com/gitea-webhook-ambassador/internal/web"
	webhandler "freeleaps.com/gitea-webhook-ambassador/internal/web/handler"
	"freeleaps.com/gitea-webhook-ambassador/internal/worker"
)

var (
	configFile = flag.String("config", "config.yaml", "Path to configuration file")
)

func main() {
	flag.Parse()

	// Initialize logger with default configuration
	logger.Configure(logger.Config{
		Level:  "info",
		Format: "text",
	})

	// Load initial configuration
	if err := config.Load(*configFile); err != nil {
		logger.Error("Failed to load configuration: %v", err)
		os.Exit(1)
	}

	// Setup application
	app, err := setupApplication()
	if err != nil {
		logger.Error("Failed to setup application: %v", err)
		os.Exit(1)
	}
	defer app.cleanup()

	// Start HTTP server
	go app.startServer()

	// Handle graceful shutdown
	app.handleShutdown()
}

type application struct {
	server     *http.Server
	workerPool *worker.Pool
	db         *database.DB
	watcher    *config.Watcher
}

func setupApplication() (*application, error) {
	cfg := config.Get()

	// Configure logger based on configuration
	logger.Configure(logger.Config{
		Level:  cfg.Logging.Level,
		Format: cfg.Logging.Format,
		File:   cfg.Logging.File,
	})

	// Ensure database directory exists
	dbDir := filepath.Dir(cfg.Database.Path)
	if err := os.MkdirAll(dbDir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create database directory: %v", err)
	}

	// Initialize database
	db, err := setupDatabase(cfg)
	if err != nil {
		return nil, fmt.Errorf("failed to setup database: %v", err)
	}

	// Create Jenkins client
	jenkinsClient := jenkins.New(jenkins.Config{
		URL:      cfg.Jenkins.URL,
		Username: cfg.Jenkins.Username,
		Token:    cfg.Jenkins.Token,
		Timeout:  time.Duration(cfg.Jenkins.Timeout) * time.Second,
	})

	// Create worker pool
	workerPool, err := setupWorkerPool(cfg, jenkinsClient, db)
	if err != nil {
		return nil, fmt.Errorf("failed to setup worker pool: %v", err)
	}

	// Setup config watcher
	watcher, err := setupConfigWatcher(*configFile)
	if err != nil {
		return nil, fmt.Errorf("failed to setup config watcher: %v", err)
	}

	if err := watcher.Start(); err != nil {
		return nil, fmt.Errorf("failed to start config watcher: %v", err)
	}

	// Create HTTP server
	server := setupHTTPServer(cfg, workerPool, db)

	return &application{
		server:     server,
		workerPool: workerPool,
		db:         db,
		watcher:    watcher,
	}, nil
}

func setupDatabase(cfg config.Configuration) (*database.DB, error) {
	return database.New(database.Config{
		Path: cfg.Database.Path,
	})
}

func setupWorkerPool(cfg config.Configuration, jenkinsClient *jenkins.Client, db *database.DB) (*worker.Pool, error) {
	pool, err := worker.New(worker.Config{
		PoolSize:     cfg.Worker.PoolSize,
		QueueSize:    cfg.Worker.QueueSize,
		MaxRetries:   cfg.Worker.MaxRetries,
		RetryBackoff: time.Duration(cfg.Worker.RetryBackoff) * time.Second,
		Client:       jenkinsClient,
		DB:           db,
	})
	if err != nil {
		return nil, err
	}

	// Start event cleanup
	go worker.CleanupEvents(time.Duration(cfg.EventCleanup.ExpireAfter) * time.Second)

	return pool, nil
}

func setupConfigWatcher(configPath string) (*config.Watcher, error) {
	return config.NewWatcher(configPath, func() error {
		if err := config.Load(configPath); err != nil {
			return err
		}
		newCfg := config.Get()

		// Update logger configuration
		logger.Configure(logger.Config{
			Level:  newCfg.Logging.Level,
			Format: newCfg.Logging.Format,
			File:   newCfg.Logging.File,
		})

		logger.Info("Configuration reloaded successfully")
		return nil
	})
}

func setupHTTPServer(cfg config.Configuration, workerPool *worker.Pool, db *database.DB) *http.Server {
	// Create handlers
	webhookHandler := handler.NewWebhookHandler(workerPool, db, &cfg)
	healthHandler := handler.NewHealthHandler(workerPool, &cfg)
	adminHandler := handler.NewAdminHandler(db, &cfg)
	projectHandler := handler.NewProjectHandler(db, &cfg)
	logsHandler := handler.NewLogsHandler(db, &cfg)

	// Create auth middleware
	authMiddleware := auth.NewMiddleware(cfg.Server.SecretKey)

	// Create dashboard handler
	dashboardHandler, err := webhandler.NewDashboardHandler(
		web.WebAssets,
		projectHandler,
		adminHandler,
		logsHandler,
		healthHandler,
	)
	if err != nil {
		logger.Error("Failed to create dashboard handler: %v", err)
		os.Exit(1)
	}

	// Setup HTTP routes
	mux := http.NewServeMux()

	// Static file handlers (not protected by auth)
	mux.HandleFunc("/css/", dashboardHandler.ServeHTTP)
	mux.HandleFunc("/js/", dashboardHandler.ServeHTTP)
	mux.HandleFunc("/img/", dashboardHandler.ServeHTTP)

	// Webhook endpoint (not protected by auth, uses its own validation)
	mux.HandleFunc(cfg.Server.WebhookPath, webhookHandler.HandleWebhook)

	// Login routes - must be defined before protected routes
	mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodGet {
			dashboardHandler.ServeHTTP(w, r)
		} else {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		}
	})
	mux.HandleFunc("/api/auth/login", func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodPost {
			authMiddleware.HandleLogin(w, r)
		} else {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		}
	})

	// Protected routes
	mux.Handle("/", authMiddleware.Authenticate(dashboardHandler))
	mux.Handle("/dashboard", authMiddleware.Authenticate(dashboardHandler))

	// Protected API routes
	mux.Handle("/api/projects", authMiddleware.Authenticate(http.HandlerFunc(projectHandler.HandleGetProjectMapping)))
	mux.Handle("/api/admin/api-keys", authMiddleware.Authenticate(http.HandlerFunc(adminHandler.HandleListAPIKeys)))
	mux.Handle("/api/admin/api-keys/delete", authMiddleware.Authenticate(http.HandlerFunc(adminHandler.HandleDeleteAPIKey)))
	mux.Handle("/api/logs", authMiddleware.Authenticate(http.HandlerFunc(logsHandler.HandleGetTriggerLogs)))
	mux.Handle("/api/health", authMiddleware.Authenticate(http.HandlerFunc(healthHandler.HandleHealth)))

	return &http.Server{
		Addr:         fmt.Sprintf(":%d", cfg.Server.Port),
		Handler:      mux,
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
		IdleTimeout:  60 * time.Second,
	}
}

func (app *application) startServer() {
	logger.Info("Server listening on %s", app.server.Addr)
	if err := app.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		logger.Error("HTTP server error: %v", err)
		os.Exit(1)
	}
}

func (app *application) handleShutdown() {
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)

	<-stop
	logger.Info("Shutting down server...")
	app.cleanup()
	logger.Info("Server shutdown complete")
}

func (app *application) cleanup() {
	if app.workerPool != nil {
		app.workerPool.Release()
	}
	if app.db != nil {
		app.db.Close()
	}
	if app.watcher != nil {
		app.watcher.Stop()
	}
}
