package main

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

	"github.com/denisovdennis/autohero/internal/config"
	"github.com/denisovdennis/autohero/internal/game"
	"github.com/denisovdennis/autohero/internal/handler"
	"github.com/denisovdennis/autohero/internal/migrate"
	"github.com/denisovdennis/autohero/internal/model"
	"github.com/denisovdennis/autohero/internal/router"
	"github.com/denisovdennis/autohero/internal/storage"
	"github.com/denisovdennis/autohero/internal/tuning"
)

func main() {
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))
	slog.SetDefault(logger)

	cfg := config.Load()

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

	// Connect to PostgreSQL.
	pgPool, err := storage.NewPostgres(ctx, cfg.DB, logger)
	if err != nil {
		logger.Error("failed to connect to PostgreSQL", "error", err)
		os.Exit(1)
	}
	defer pgPool.Close()

	// Run database migrations.
	if err := migrate.Run(ctx, pgPool, "migrations"); err != nil {
		logger.Error("database migration failed", "error", err)
		os.Exit(1)
	}

	// Connect to Redis.
	redisClient, err := storage.NewRedis(ctx, cfg.Redis, logger)
	if err != nil {
		logger.Error("failed to connect to Redis", "error", err)
		os.Exit(1)
	}
	defer redisClient.Close()

	// Combat event channel bridges game engine to WebSocket hub.
	eventCh := make(chan model.CombatEvent, 256)

	// Game engine.
	engine := game.NewEngine(cfg.Game.TickRate, eventCh, logger)

	// WebSocket hub.
	hub := handler.NewHub(logger)

	// Stores (created before hub callbacks which reference them).
	heroStore := storage.NewHeroStore(pgPool, logger)
	logStore := storage.NewLogStore(pgPool)
	runtimeConfigStore := storage.NewRuntimeConfigStore(pgPool)
	if err := tuning.ReloadNow(ctx, logger, runtimeConfigStore); err != nil {
		logger.Error("failed to load runtime config", "error", err)
		os.Exit(1)
	}
	buffDebuffStore := storage.NewBuffDebuffConfigStore(pgPool)
	if err := model.ReloadBuffDebuffCatalog(ctx, logger, buffDebuffStore); err != nil {
		logger.Error("failed to load buff/debuff catalog", "error", err)
		os.Exit(1)
	}
	contentStore := storage.NewContentStore(pgPool)
	enemiesFromDB, err := contentStore.LoadEnemyTemplates(ctx)
	if err != nil {
		logger.Error("failed to load enemy templates from db", "error", err)
		os.Exit(1)
	}
	model.SetEnemyTemplates(enemiesFromDB)
	gearFamiliesFromDB, err := contentStore.LoadGearFamilies(ctx)
	if err != nil {
		logger.Error("failed to load gear templates from db", "error", err)
		os.Exit(1)
	}
	model.SetGearCatalog(gearFamiliesFromDB)

	// Load road graph for server-authoritative movement.
	roadGraph, err := game.LoadRoadGraph(ctx, pgPool)
	if err != nil {
		logger.Error("failed to load road graph", "error", err)
		os.Exit(1)
	}
	logger.Info("road graph loaded",
		"towns", len(roadGraph.Towns),
		"roads", len(roadGraph.Roads),
	)

	// Wire engine dependencies.
	engine.SetSender(hub) // Hub implements game.MessageSender
	engine.SetRoadGraph(roadGraph)
	engine.SetHeroStore(heroStore)
	engine.SetAdventureLog(func(heroID int64, msg string) {
		logCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		if err := logStore.Add(logCtx, heroID, msg); err != nil {
			logger.Warn("failed to write adventure log", "hero_id", heroID, "error", err)
			return
		}
		hub.SendToHero(heroID, "adventure_log_line", model.AdventureLogLinePayload{Message: msg})
	})

	// Hub callbacks: on connect, load hero and register movement; on disconnect, persist.
	hub.OnConnect = func(heroID int64) {
		hero, err := heroStore.GetByID(ctx, heroID)
		if err != nil || hero == nil {
			logger.Error("failed to load hero on ws connect", "hero_id", heroID, "error", err)
			return
		}
		engine.RegisterHeroMovement(hero)
	}
	hub.OnDisconnect = func(heroID int64, remainingSameHero int) {
		engine.HeroSocketDetached(heroID, remainingSameHero == 0)
	}

	// Bridge hub incoming client messages to engine's command channel.
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case msg := <-hub.Incoming:
				if engine.IsTimePaused() {
					hub.SendToHero(msg.HeroID, "error", model.ErrorPayload{
						Code:    "time_paused",
						Message: "server time is paused",
					})
					continue
				}
				engine.IncomingCh() <- game.IncomingMessage{
					HeroID:  msg.HeroID,
					Type:    msg.Type,
					Payload: msg.Payload,
				}
			}
		}
	}()

	go hub.Run()

	// Bridge: forward engine events to WebSocket hub.
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case evt := <-eventCh:
				hub.BroadcastEvent(evt)
			}
		}
	}()

	// Start game engine.
	go func() {
		if err := engine.Run(ctx); err != nil && err != context.Canceled {
			logger.Error("game engine error", "error", err)
		}
	}()

	// Record server start time for catch-up gap calculation.
	serverStartedAt := time.Now()
	offlineSim := game.NewOfflineSimulator(heroStore, logStore, roadGraph, logger, func() bool {
		return engine.IsTimePaused()
	}, engine.HeroHasActiveMovement)
	go func() {
		if err := offlineSim.Run(ctx); err != nil && err != context.Canceled {
			logger.Error("offline simulator error", "error", err)
		}
	}()

	// HTTP server.
	r := router.New(router.Deps{
		Engine:                 engine,
		Hub:                    hub,
		PgPool:                 pgPool,
		BotToken:               cfg.BotToken,
		PaymentProviderToken:   cfg.PaymentProviderToken,
		AdminBasicAuthUsername: cfg.Admin.BasicAuthUsername,
		AdminBasicAuthPassword: cfg.Admin.BasicAuthPassword,
		AdminBasicAuthRealm:    cfg.Admin.BasicAuthRealm,
		Logger:                 logger,
		ServerStartedAt:        serverStartedAt,
	})
	srv := &http.Server{
		Addr:         ":" + cfg.ServerPort,
		Handler:      r,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 15 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	// Start server in background.
	go func() {
		logger.Info("server starting", "port", cfg.ServerPort)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Error("server error", "error", err)
			os.Exit(1)
		}
	}()

	// Graceful shutdown.
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	sig := <-quit
	logger.Info("shutting down", "signal", sig.String())

	// Cancel game engine and event forwarding.
	cancel()

	// Give HTTP connections time to drain.
	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer shutdownCancel()

	if err := srv.Shutdown(shutdownCtx); err != nil {
		logger.Error("server shutdown error", "error", err)
	}

	logger.Info("server stopped")
}
