using Prospect.Unreal.Core;
using Prospect.Unreal.Net.Actors;
using Prospect.Unreal.Runtime;
using Serilog;

namespace Prospect.Server.Game;

internal static class Program
{
    private const float TickRate = (1000.0f / 60.0f) / 1000.0f;

    private static readonly ILogger Logger = Log.ForContext(typeof(Program));
    private static readonly PeriodicTimer Tick = new PeriodicTimer(TimeSpan.FromSeconds(TickRate));

    public static async Task Main()
    {
        Console.CancelKeyPress += (_, e) =>
        {
            Tick.Dispose();
            e.Cancel = true;
        };

        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Verbose()
            .Enrich.FromLogContext()
            .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] ({SourceContext,-52}) {Message:lj}{NewLine}{Exception}")
            .CreateLogger();

        // The Cycle: Frontier authoritative game server.
        // We start on the station map (the simplest shared space to get two players spawned
        // and moving); the raid maps (Bright Sands, …) come once spawn + movement replication
        // work end to end. AI and loot are intentionally out of scope for now.
        var map = Environment.GetEnvironmentVariable("PROSPECT_MAP") ?? "/Game/Maps/MP/Station/Station_P";
        var gameMode = Environment.GetEnvironmentVariable("PROSPECT_GAMEMODE") ?? "/Script/Prospect/YGameMode_Station";
        var port = int.TryParse(Environment.GetEnvironmentVariable("PROSPECT_PORT"), out var p) ? p : 7777;

        Logger.Information("Starting Prospect.Server.Game — map={Map} gameMode={GameMode} port={Port}", map, gameMode, port);

        var worldUrl = new FUrl { Map = map, Port = port };
        worldUrl.Options.Add($"game={gameMode}");

        await using (var world = new ProspectWorld())
        {
            world.SetGameInstance(new UGameInstance());
            world.SetGameMode(worldUrl);

            // The game mode needs a GameSession, otherwise NMT_Join -> SpawnPlayActor -> Login
            // fails with "GameSession is null" and the player is never spawned.
            var authGameMode = world.GetAuthGameMode();
            if (authGameMode != null && authGameMode.GameSession == null)
            {
                authGameMode.GameSession = new AGameSession();
            }

            world.InitializeActorsForPlay(worldUrl, true);

            if (!world.Listen())
            {
                Logger.Fatal("Failed to start listening (port {Port} already in use?)", port);
                return;
            }

            Logger.Information("Server listening on :{Port}. Waiting for players…", port);

            while (await Tick.WaitForNextTickAsync())
            {
                try
                {
                    world.Tick(TickRate);
                }
                catch (Exception ex)
                {
                    // A misaligned/incompatible client packet must not kill the whole server
                    // (R&D: the client's exact net version isn't matched yet). Log and continue.
                    Logger.Error(ex, "Tick error (bad packet?) — continuing");
                }
            }
        }

        Logger.Information("Shutting down");
    }
}
