using Ehwrj.App.Models;
using Ehwrj.Core.Models;
using Ehwrj.Core.Services;

namespace Ehwrj.App.Services;

public sealed class LiveMapService : IDisposable
{
    private static readonly EndpointDescriptor[] Endpoints =
    [
        new("map_info.json", Required: true),
        new("map_obj.json", Required: true),
        new("map.img", Required: true),
        new("state", Required: false),
        new("hudmsg", Required: false),
        new("gamechat", Required: false)
    ];

    private readonly IWarThunderClient _client;
    private readonly ProcessProbe _processProbe;
    private readonly ObjectTracker _tracker = new();
    private CancellationTokenSource? _cts;
    private Task? _loop;
    private byte[]? _lastImageBytes;
    private Avalonia.Media.Imaging.Bitmap? _lastImage;

    public LiveMapService(IWarThunderClient client, ProcessProbe processProbe)
    {
        _client = client;
        _processProbe = processProbe;
    }

    public event EventHandler<LiveSnapshot>? SnapshotUpdated;

    public bool IsRunning => _loop is { IsCompleted: false };

    public void Start(int intervalMs)
    {
        if (IsRunning) return;
        _cts = new CancellationTokenSource();
        _loop = Task.Run(() => RunAsync(Math.Max(100, intervalMs), _cts.Token));
    }

    public void Stop()
    {
        _cts?.Cancel();
    }

    private async Task RunAsync(int intervalMs, CancellationToken cancellationToken)
    {
        using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(intervalMs));
        while (!cancellationToken.IsCancellationRequested)
        {
            await PublishOnceAsync(cancellationToken).ConfigureAwait(false);
            await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false);
        }
    }

    private async Task PublishOnceAsync(CancellationToken cancellationToken)
    {
        var gameRunning = _processProbe.IsWarThunderRunning();
        var endpointHealth = new List<EndpointHealth>();
        try
        {
            var mapInfoJson = await ReadRequiredAsync("map_info.json", _client.GetMapInfoAsync, endpointHealth, cancellationToken).ConfigureAwait(false);
            if (mapInfoJson is null)
            {
                PublishFailure(gameRunning, endpointHealth, "Waiting for local map API: map_info.json failed");
                return;
            }

            var objectsJson = await ReadRequiredAsync("map_obj.json", _client.GetObjectsAsync, endpointHealth, cancellationToken).ConfigureAwait(false);
            if (objectsJson is null)
            {
                PublishFailure(gameRunning, endpointHealth, "Waiting for local map API: map_obj.json failed");
                return;
            }

            var imageBytes = await ReadRequiredAsync("map.img", _client.GetMapImageAsync, endpointHealth, cancellationToken).ConfigureAwait(false);
            if (imageBytes is null)
            {
                PublishFailure(gameRunning, endpointHealth, "Waiting for local map API: map.img failed");
                return;
            }

            var stateJson = await ReadOptionalAsync("state", _client.TryGetStateAsync, endpointHealth, cancellationToken).ConfigureAwait(false);
            var hudJson = await ReadOptionalAsync("hudmsg", _client.TryGetHudMessagesAsync, endpointHealth, cancellationToken).ConfigureAwait(false);
            var chatJson = await ReadOptionalAsync("gamechat", _client.TryGetGameChatAsync, endpointHealth, cancellationToken).ConfigureAwait(false);

            var mapInfo = MapInfo.FromJson(mapInfoJson);
            var flightState = FlightState.FromJson(stateJson);
            var objects = MapObject.FromJson(objectsJson, _tracker, flightState);
            var messages = BattleMessage.FromJson(hudJson)
                .Concat(BattleMessage.FromJson(chatJson))
                .DistinctBy(static m => $"{m.Enemy}:{m.Text}", StringComparer.Ordinal)
                .ToArray();
            var mapImage = GetBitmap(imageBytes);
            var snapshot = new LiveSnapshot(mapInfo, objects, flightState, messages, mapImage, DateTimeOffset.Now, gameRunning, "Connected to 127.0.0.1:8111", CompleteEndpointHealth(endpointHealth));
            SnapshotUpdated?.Invoke(this, snapshot);
        }
        catch (OperationCanceledException)
        {
        }
        catch (Exception ex)
        {
            var status = gameRunning
                ? $"Waiting for local map API: {ex.Message}"
                : "War Thunder process not detected";
            PublishFailure(gameRunning, endpointHealth, status);
        }
    }

    private async Task<T?> ReadRequiredAsync<T>(
        string path,
        Func<CancellationToken, Task<T>> read,
        List<EndpointHealth> health,
        CancellationToken cancellationToken)
        where T : class
    {
        try
        {
            var value = await read(cancellationToken).ConfigureAwait(false);
            health.Add(new EndpointHealth(path, Required: true, EndpointHealthState.Ok, "ok"));
            return value;
        }
        catch (OperationCanceledException)
        {
            throw;
        }
        catch (Exception ex)
        {
            health.Add(new EndpointHealth(path, Required: true, EndpointHealthState.Error, ex.Message));
            return null;
        }
    }

    private async Task<string?> ReadOptionalAsync(
        string path,
        Func<CancellationToken, Task<string?>> read,
        List<EndpointHealth> health,
        CancellationToken cancellationToken)
    {
        try
        {
            var value = await read(cancellationToken).ConfigureAwait(false);
            health.Add(value is null
                ? new EndpointHealth(path, Required: false, EndpointHealthState.Warning, "not available")
                : new EndpointHealth(path, Required: false, EndpointHealthState.Ok, "ok"));
            return value;
        }
        catch (OperationCanceledException)
        {
            throw;
        }
        catch (Exception ex)
        {
            health.Add(new EndpointHealth(path, Required: false, EndpointHealthState.Warning, ex.Message));
            return null;
        }
    }

    private void PublishFailure(bool gameRunning, List<EndpointHealth> endpointHealth, string status)
    {
        SnapshotUpdated?.Invoke(
            this,
            new LiveSnapshot(
                MapInfo.Empty,
                Array.Empty<MapObject>(),
                FlightState.Empty,
                Array.Empty<BattleMessage>(),
                _lastImage,
                DateTimeOffset.Now,
                gameRunning,
                status,
                CompleteEndpointHealth(endpointHealth)));
    }

    private static IReadOnlyList<EndpointHealth> CompleteEndpointHealth(IReadOnlyList<EndpointHealth> health)
    {
        var completed = new List<EndpointHealth>(health);
        foreach (var endpoint in Endpoints)
        {
            if (completed.Any(h => string.Equals(h.Path, endpoint.Path, StringComparison.Ordinal)))
            {
                continue;
            }

            completed.Add(new EndpointHealth(endpoint.Path, endpoint.Required, EndpointHealthState.NotChecked, "not checked"));
        }

        return completed;
    }

    private Avalonia.Media.Imaging.Bitmap? GetBitmap(byte[] imageBytes)
    {
        if (_lastImageBytes is not null && imageBytes.SequenceEqual(_lastImageBytes))
        {
            return _lastImage;
        }

        using var stream = new MemoryStream(imageBytes);
        var bitmap = new Avalonia.Media.Imaging.Bitmap(stream);
        _lastImageBytes = imageBytes;
        _lastImage = bitmap;
        return bitmap;
    }

    public void Dispose()
    {
        Stop();
        _cts?.Dispose();
    }
}

internal sealed record EndpointDescriptor(string Path, bool Required);
