package main

import (
	"context"
	"log"
	"os"
	"os/signal"
	"syscall"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/logger"
	"github.com/gofiber/fiber/v2/middleware/recover"

	"github.com/tapper/backend/config"
	"github.com/tapper/backend/database"
	"github.com/tapper/backend/handlers"
	"github.com/tapper/backend/middleware"
	"github.com/tapper/backend/services"
)

func main() {
	cfg, err := config.Load()
	if err != nil {
		log.Fatalf("Failed to load configuration: %v", err)
	}

	services.InitAuth(cfg.JWTPrivateKey, cfg.JWTPublicKey, cfg.JWTExpirySec)
	services.ConfigureMatchmaker(cfg.DemoMode)

	if err := database.Connect(cfg.DatabaseURL); err != nil {
		log.Fatalf("Failed to connect to database: %v", err)
	}

	database.Seed(cfg.OwnerEmail, cfg.PortRangeStart, cfg.PortRangeEnd)
	if cfg.DemoMode {
		database.SeedDemoUsers()
	}

	services.LoadPlatformConfig()

	app := fiber.New(fiber.Config{
		AppName:      "tapper API",
		BodyLimit:    10 * 1024 * 1024,
		ServerHeader: "tapper",
	})

	app.Use(recover.New())
	app.Use(logger.New(logger.Config{
		Format: "${time} | ${status} | ${latency} | ${ip} | ${method} ${path}\n",
	}))
	app.Use(cors.New(cors.Config{
		AllowOrigins:     cfg.FrontendURL + ", " + cfg.PublicURL,
		AllowHeaders:     "Origin, Content-Type, Accept, Authorization",
		AllowMethods:     "GET, POST, PUT, PATCH, DELETE, OPTIONS",
		AllowCredentials: true,
	}))

	setupRoutes(app, cfg)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go services.StartMatchmaker(ctx)
	go services.StartDecayCron(ctx)
	go services.StartEventListener(ctx, cfg.DatabaseURL)

	go func() {
		sigCh := make(chan os.Signal, 1)
		signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
		<-sigCh
		log.Println("[main] Shutting down gracefully...")
		cancel()
		if err := app.Shutdown(); err != nil {
			log.Printf("[main] Shutdown error: %v", err)
		}
	}()

	log.Printf("[main] Starting tapper API on :%s", cfg.Port)
	if err := app.Listen(":" + cfg.Port); err != nil {
		log.Fatalf("Server error: %v", err)
	}
}

func setupRoutes(app *fiber.App, cfg *config.Config) {
	api := app.Group("/api")

	api.Get("/health", handlers.Health)
	api.Post("/auth/register", handlers.Register(cfg.OwnerEmail))
	api.Post("/auth/login", handlers.Login)

	api.Get("/auth/steam", middleware.JWTAuth(), handlers.SteamRedirect(cfg.PublicURL, cfg.FrontendURL))
	api.Get("/auth/steam/callback", middleware.OptionalJWTAuth(), handlers.SteamCallback(cfg.SteamAPIKey, cfg.FrontendURL))
	api.Post("/auth/steam/dev-link", middleware.JWTAuth(), handlers.SteamDevLink(cfg.Environment, cfg.SteamAPIKey))

	api.Get("/leaderboard", handlers.GetLeaderboard)

	api.Post("/auth/refresh", handlers.Refresh)
	api.Post("/auth/logout", handlers.Logout)
	api.Post("/auth/sse-token", middleware.JWTAuth(), handlers.IssueSSEToken)

	protected := api.Group("", middleware.JWTAuth(), middleware.BanCheck())

	protected.Get("/profile", handlers.GetOwnProfile)
	protected.Get("/profile/:username", handlers.GetPublicProfile)

	protected.Post("/queue/join", handlers.JoinQueue)
	protected.Post("/queue/leave", handlers.LeaveQueue)
	protected.Get("/queue/status", handlers.QueueStatus)

	protected.Get("/matches", handlers.GetMatchHistory)
	protected.Get("/matches/:id", handlers.GetMatchDetails)
	protected.Get("/matches/:id/live", handlers.GetMatchLive)
	protected.Get("/matches/:id/connect", handlers.GetMatchConnect)
	protected.Post("/matches/:id/leave", handlers.LeaveActiveMatch)
	protected.Get("/matches/:id/stream", handlers.MatchSSE)

	protected.Post("/support/tickets", handlers.CreateTicket)
	protected.Get("/support/tickets", handlers.ListOwnTickets)
	protected.Get("/support/tickets/:id", handlers.GetTicketDetails)
	protected.Post("/support/tickets/:id/messages", handlers.AddTicketMessage)
	protected.Post("/support/tickets/:id/close", handlers.CloseOwnTicket)

	mod := api.Group("/mod", middleware.JWTAuth(), middleware.BanCheck(), middleware.RequireRole("moderator"))

	mod.Get("/users", handlers.ModSearchUsers)
	mod.Get("/users/:id", handlers.ModGetUser)
	mod.Post("/users/:id/ban", handlers.ModBanUser)
	mod.Post("/users/:id/unban", handlers.ModUnbanUser)
	mod.Post("/users/:id/warn", handlers.ModWarnUser)
	mod.Post("/users/:id/note", handlers.ModAddNote)
	mod.Get("/users/:id/history", handlers.ModGetUserHistory)

	mod.Get("/tickets", handlers.ModListTickets)
	mod.Get("/tickets/:id", handlers.ModGetTicket)
	mod.Post("/tickets/:id/messages", handlers.ModReplyTicket)
	mod.Post("/tickets/:id/assign", handlers.ModAssignTicket)
	mod.Post("/tickets/:id/close", handlers.ModCloseTicket)
	mod.Post("/tickets/:id/escalate", handlers.ModEscalateTicket)

	mod.Get("/matches", handlers.ModListMatches)
	mod.Post("/matches/:id/cancel", handlers.ModCancelMatch)
	mod.Post("/matches/:id/nullify", handlers.ModNullifyMatch)

	admin := api.Group("/admin", middleware.JWTAuth(), middleware.BanCheck(), middleware.RequireRole("admin"))

	admin.Get("/users", handlers.AdminListUsers)
	admin.Patch("/users/:id", handlers.AdminEditUser)
	admin.Post("/users/:id/unlink-steam", handlers.AdminUnlinkSteam)
	admin.Patch("/users/:id/tier", handlers.AdminChangeTier)
	admin.Patch("/users/:id/role", handlers.AdminChangeRole)

	admin.Get("/elo/config", handlers.AdminGetEloConfig)
	admin.Put("/elo/config", handlers.AdminUpdateEloConfig)

	admin.Get("/servers", handlers.AdminGetServers)
	admin.Post("/servers/:id/stop", handlers.AdminStopServer)

	admin.Get("/stats", handlers.AdminGetStats)
	admin.Get("/stats/stream", handlers.AdminStatsSSE)

	admin.Get("/audit-log", handlers.AdminGetAuditLog)

	owner := api.Group("/owner", middleware.JWTAuth(), middleware.BanCheck(), middleware.RequireRole("owner"))

	owner.Get("/config", handlers.OwnerGetConfig)
	owner.Put("/config", handlers.OwnerUpdateConfig)
	owner.Get("/config/cs2", handlers.OwnerGetCS2Config)
	owner.Put("/config/cs2", handlers.OwnerUpdateCS2Config)

	owner.Get("/system", handlers.OwnerGetSystem)
	owner.Get("/system/stream", handlers.OwnerSystemSSE)
	owner.Get("/system/containers", handlers.OwnerGetContainers)
	owner.Post("/system/containers/:id/restart", handlers.OwnerRestartContainer)
	owner.Get("/system/logs/:service", handlers.OwnerGetServiceLogs)

	owner.Post("/maintenance", handlers.OwnerToggleMaintenance)
	owner.Post("/seed", handlers.OwnerSeed(cfg.OwnerEmail, cfg.PortRangeStart, cfg.PortRangeEnd))

	internal := api.Group("/internal", middleware.InternalAuth(cfg.APISecret))

	internal.Get("/match-config/:match_id", handlers.GetMatchConfig(cfg.PublicHost))
	internal.Post("/match-events", handlers.HandleMatchEvents)
	internal.Post("/demo-upload", handlers.HandleDemoUpload)
}
