using System;
using System.Threading.Tasks;
using Friflo.Engine.ECS;
using LittleSim.Content;
using LittleSim.Net;
using LittleSim.Sim;
using LittleSim.UI;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MrGameEng.Assets;
using MrGameEng.Core;
using MrGameEng.DevConsole;
using MrGameEng.Graphics;
using MrGameEng.Host;
using MrGameEng.Input;
using MrGameEng.Net;
using MrGameEng.UI;
using Myra.Graphics2D.UI;

namespace LittleSim.Scenes;

/// <summary>
/// Сетевой клиент: подключается к дедикейтед-серверу (LittleSim.Server --listen) и рендерит
/// реплицированных жителей. Симуляция целиком на сервере; сюда приезжают только компоненты
/// из <see cref="NetSchema"/>, а спрайты вешаются локально при спавне — граница
/// sim/presentation проходит теперь через сеть. Esc — назад в меню, команда консоли `net` —
/// состояние соединения.
/// </summary>
public sealed class MultiplayerScene : Scene
{
    private static readonly RectF Bounds = new(0f, 0f, 1280f, 720f);

    // Бэкофф переподключения: задержка удваивается с каждой неудачей до потолка.
    private const float MaxBackoffSeconds = 8f;

    private readonly Uri _server;
    private Task<WebSocketClient>? _connecting;
    private WebSocketClient? _connection;
    private ReplicationClient _replication = null!;
    private InputManager _input = null!;
    private GameContent _content = null!;
    private Label _hud = null!;
    private string _statusKey = "net.connecting";
    private double _nextAttemptAt; // время (unscaled) следующей попытки подключения
    private int _attempt; // 0 — первая попытка; растёт при обрывах, задаёт бэкофф

    /// <summary>Сцена, подключающаяся к серверу <paramref name="server"/> (ws:// или wss://).</summary>
    public MultiplayerScene(Uri server) => _server = server;

    protected override void OnLoad()
    {
        _content = Context.Services.Get<GameContent>();
        var assets = Context.Services.GetOrDefault<AssetManager>() ?? Context.UseAssets();
        _input = this.UseInput();
        var renderer = this.UseRenderer2D(
            new Renderer2DOptions { VirtualResolution = new Point(1280, 720) }
        );
        GameLayers.EnsureRegistered(renderer);
        var white = new Texture2DRegion(assets.Load(GameAssets.Textures.White));
        Store.CreateEntity(new Camera(Bounds.Center, zoom: 1f, bounds: Bounds));

        // Реплика пишет в ECS сцены; презентацию (спрайт) вешаем сами при спавне.
        _replication = new ReplicationClient(NetSchema.Create(), Store);
        _replication.EntitySpawned += entity =>
        {
            var sprite = new Sprite(white, GameLayers.Beings);
            sprite.CenterOrigin();
            entity.AddComponent(sprite);
            entity.AddComponent(new NetLerp());
        };

        var desktop = this.UseScaledUI();
        _hud = new Label { Left = 10, Top = 8 };
        desktop.Root = Ui.Screen(_hud);

        UpdateSystems.Add(new CallbackSystem(Pump));
        // Снапшоты приходят ~10 раз в секунду — сглаживаем позиции до частоты кадра.
        UpdateSystems.Add(new NetSmoothingSystem());
        // Усталость жителей видна и по сети: PawnNeeds реплицируется, спрайт темнеет локально.
        UpdateSystems.Add(new PawnAppearanceSystem());

        var console = this.UseDevConsole();
        console.Register(
            "net",
            "net — connection status and replicated entity count",
            (c, _) =>
            {
                c.WriteLine($"server: {_server}");
                c.WriteLine($"state: {(_connection?.IsOpen == true ? "connected" : "offline")}");
                c.WriteLine($"entities: {_replication.EntityCount}");
            }
        );

        // Первую попытку запускает сам Pump (единый путь с переподключением): _nextAttemptAt = 0.
    }

    protected override void OnUnload() => _connection?.Close();

    private void Pump()
    {
        var now = Context.Clock.UnscaledTotalTime;

        // Завершилась попытка подключения: успех — берём соединение; провал — планируем повтор.
        if (_connecting is { IsCompleted: true } finished)
        {
            _connecting = null;
            if (finished.IsCompletedSuccessfully)
            {
                _connection = finished.Result;
                _replication.Clear(); // сбрасываем устаревшие сущности перед свежим полным снапшотом
                _attempt = 0;
                _statusKey = "net.connected";
                Log.Info($"Connected to {_server}");
            }
            else
            {
                Log.Error(
                    $"Connect to {_server} failed: "
                        + finished.Exception?.GetBaseException().Message
                );
                ScheduleReconnect(now);
            }
        }

        // Обрыв уже установленного соединения: чистим и уходим в переподключение.
        if (_connection is { IsOpen: false })
        {
            _connection = null;
            _replication.Clear();
            ScheduleReconnect(now);
        }

        if (_connection is not null)
        {
            _replication.Pump(_connection);
        }
        else if (_connecting is null && now >= _nextAttemptAt)
        {
            _statusKey = _attempt == 0 ? "net.connecting" : "net.reconnecting";
            _connecting = WebSocketClient.ConnectAsync(_server);
        }

        _hud.Text = _content.Languages.Format(
            "net.hud",
            _content.Languages.Get(_statusKey),
            _replication.EntityCount
        );

        if (_input.IsKeyPressed(Keys.Escape))
        {
            _connection?.Close();
            Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.4f));
        }
    }

    // Экспоненциальный бэкофф: 1, 2, 4, 8, 8, … секунд между попытками.
    private void ScheduleReconnect(double now)
    {
        var delay = MathF.Min(MaxBackoffSeconds, 1f * (1 << Math.Min(_attempt, 3)));
        _attempt++;
        _nextAttemptAt = now + delay;
        _statusKey = "net.reconnecting";
    }
}
