using System;
using System.Threading.Tasks;
using Friflo.Engine.ECS;
using Friflo.Engine.ECS.Systems;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MrGameEng.Core;
using MrGameEng.Net;

namespace LittleSim.Web;

/// <summary>Состояние соединения веб-клиента — его читает оверлей подключения (Blazor).</summary>
public enum ConnectionStatus
{
    /// <summary>Сервер ещё не выбран — ждём, пока пользователь нажмёт «Подключиться».</summary>
    Idle,

    /// <summary>Идёт первая попытка подключения.</summary>
    Connecting,

    /// <summary>Соединение установлено, снапшоты приходят.</summary>
    Connected,

    /// <summary>Соединение оборвалось/не удалось — ждём следующей попытки (бэкофф).</summary>
    Reconnecting,
}

/// <summary>
/// Браузерный клиент мира LittleSim: по команде <see cref="Connect"/> подключается к
/// дедикейтед-серверу (LittleSim.Server --listen), применяет дельта-снапшоты в свой
/// EntityStore и рисует жителей через KNI SpriteBatch (WebGL). Мира не создаёт — только
/// наблюдает: симуляция целиком на сервере, сюда приезжают лишь компоненты схемы (позиция +
/// потребности). Позиции сглаживаются до частоты кадра, усталость затемняет квадратик. При
/// обрыве переподключается сам с экспоненциальным бэкоффом, очищая устаревшие сущности.
/// </summary>
public sealed class WorldViewScene : Scene
{
    // Бэкофф переподключения: задержка удваивается с каждой неудачей до потолка.
    private const float MaxBackoffSeconds = 8f;

    private readonly Func<SpriteBatch> _spriteBatch;
    private readonly Func<Texture2D> _pixel;

    private ReplicationClient _replication = null!;
    private Task<WebSocketClient>? _connecting;
    private WebSocketClient? _connection;
    private Uri? _server; // целевой сервер; null — пользователь ещё не подключался
    private double _nextAttemptAt;
    private int _attempt;

    /// <summary>Текущее состояние соединения — для оверлея подключения.</summary>
    public ConnectionStatus Status { get; private set; } = ConnectionStatus.Idle;

    /// <summary>Сколько реплицированных жителей сейчас в мире клиента.</summary>
    public int EntityCount => _replication?.EntityCount ?? 0;

    /// <summary>Сцена-наблюдатель; сервер задаётся позже через <see cref="Connect"/>.</summary>
    public WorldViewScene(Func<SpriteBatch> spriteBatch, Func<Texture2D> pixel)
    {
        _spriteBatch = spriteBatch;
        _pixel = pixel;
    }

    /// <summary>
    /// Подключиться к <paramref name="server"/> (ws:// или wss://). Сбрасывает прежнее
    /// соединение и счётчик попыток — подключение начнётся в ближайшем тике. Дальше клиент сам
    /// переподключается к этому адресу при обрывах.
    /// </summary>
    public void Connect(Uri server)
    {
        _connection?.Close();
        _connection = null;
        _replication?.Clear();
        _server = server;
        _attempt = 0;
        _nextAttemptAt = 0f;
        Status = ConnectionStatus.Connecting;
    }

    /// <summary>Сглаживание сетевой позиции: цель из снапшота, визуал лерпится покадрово.</summary>
    private struct NetLerp : IComponent
    {
        public Vector2 Visual;
        public Vector2 Target;
        public bool Initialized;
    }

    protected override void OnLoad()
    {
        _replication = new ReplicationClient(WebNetSchema.Create(), Store);
        _replication.EntitySpawned += entity => entity.AddComponent(new NetLerp());

        UpdateSystems.Add(new CallbackSystem(Pump));
        UpdateSystems.Add(new NetSmoothingSystem());
        DrawSystems.Add(new PawnDrawSystem(_spriteBatch, _pixel));

        Log.Info("LittleSim.Web ready — awaiting connect");
    }

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

    private void Pump()
    {
        if (_server is null)
        {
            return; // адрес ещё не задан — ждём команды Connect из оверлея
        }

        var now = Context.Clock.UnscaledTotalTime;

        // Завершилась попытка подключения: успех — берём соединение; провал — планируем повтор.
        if (_connecting is { IsCompleted: true } finished)
        {
            _connecting = null;
            if (finished.IsCompletedSuccessfully)
            {
                _connection = finished.Result;
                _replication.Clear(); // сбрасываем устаревшие сущности перед свежим полным снапшотом
                _attempt = 0;
                Status = ConnectionStatus.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)
        {
            Status = _attempt == 0 ? ConnectionStatus.Connecting : ConnectionStatus.Reconnecting;
            _connecting = WebSocketClient.ConnectAsync(_server);
        }
    }

    // Экспоненциальный бэкофф: 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;
        Status = ConnectionStatus.Reconnecting;
    }

    /// <summary>Вызывает делегат каждый тик — мелкая логика сцены без отдельного класса.</summary>
    private sealed class CallbackSystem(Action update) : BaseSystem
    {
        protected override void OnUpdateGroup() => update();
    }

    /// <summary>Та же экспонента, что в NetSmoothingSystem десктопа (LittleSim/Net/NetSmoothing.cs).</summary>
    private sealed class NetSmoothingSystem : QuerySystem<NetTransform, NetLerp>
    {
        private const float Rate = 12f;

        protected override void OnUpdate()
        {
            var blend = 1f - MathF.Exp(-Rate * Tick.deltaTime);
            Query.ForEachEntity(
                (ref NetTransform transform, ref NetLerp lerp, Entity _) =>
                {
                    if (!lerp.Initialized)
                    {
                        lerp.Visual = lerp.Target = transform.Position;
                        lerp.Initialized = true;
                        return;
                    }

                    if (transform.Position != lerp.Visual)
                    {
                        lerp.Target = transform.Position;
                    }

                    lerp.Visual = Vector2.Lerp(lerp.Visual, lerp.Target, blend);
                    transform.Position = lerp.Visual;
                }
            );
        }
    }

    /// <summary>Житель — квадратик размером Scale, затемняющийся с усталостью (как на десктопе).</summary>
    private sealed class PawnDrawSystem(Func<SpriteBatch> spriteBatch, Func<Texture2D> pixel)
        : QuerySystem<NetTransform, NetPawnNeeds>
    {
        private const float MinBrightness = 0.45f;

        protected override void OnUpdate()
        {
            var batch = spriteBatch();
            var white = pixel();
            batch.Begin();
            Query.ForEachEntity(
                (ref NetTransform transform, ref NetPawnNeeds needs, Entity _) =>
                {
                    var size = transform.Scale;
                    var brightness =
                        MinBrightness + (1f - MinBrightness) * Math.Clamp(needs.Energy, 0f, 1f);
                    batch.Draw(
                        white,
                        new Rectangle(
                            (int)(transform.Position.X - size.X / 2f),
                            (int)(transform.Position.Y - size.Y / 2f),
                            (int)size.X,
                            (int)size.Y
                        ),
                        Color.White * brightness
                    );
                }
            );
            batch.End();
        }
    }
}
