using System.Globalization;
using System.Net;
using System.Text;
using System.Text.Json;
using Ehwrj.Core.Services;

namespace Ehwrj.Tools.LocalApiStub;

internal static class Program
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        WriteIndented = true
    };

    private static async Task<int> Main(string[] args)
    {
        var options = StubOptions.Parse(args);
        if (options.ShowHelp)
        {
            StubOptions.PrintHelp();
            return 0;
        }

        if (!LoopbackGuard.IsLoopbackHost(options.Host))
        {
            Console.Error.WriteLine("Only loopback hosts are allowed for the local API stub.");
            return 1;
        }

        using var listener = new HttpListener();
        var prefix = $"http://{options.Host}:{options.Port}/";
        listener.Prefixes.Add(prefix);

        using var shutdown = new CancellationTokenSource();
        Console.CancelKeyPress += (_, eventArgs) =>
        {
            eventArgs.Cancel = true;
            shutdown.Cancel();
            listener.Stop();
        };

        try
        {
            listener.Start();
        }
        catch (HttpListenerException ex)
        {
            Console.Error.WriteLine($"Failed to listen on {prefix}: {ex.Message}");
            return 1;
        }

        Console.WriteLine($"Ehwrj local API stub listening on {prefix}");
        if (options.FixtureDirectory is not null)
        {
            Console.WriteLine($"Fixture replay: {Path.GetFullPath(options.FixtureDirectory)}");
        }
        Console.WriteLine("Endpoints: /map_info.json /map_obj.json /map.img /state /hudmsg /gamechat");
        Console.WriteLine("Press Ctrl+C to stop.");

        var startedAt = DateTimeOffset.UtcNow;
        while (!shutdown.IsCancellationRequested)
        {
            HttpListenerContext context;
            try
            {
                context = await listener.GetContextAsync().WaitAsync(shutdown.Token).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                break;
            }
            catch (HttpListenerException) when (shutdown.IsCancellationRequested)
            {
                break;
            }

            _ = Task.Run(() => HandleAsync(context, startedAt, options), shutdown.Token);
        }

        return 0;
    }

    private static async Task HandleAsync(HttpListenerContext context, DateTimeOffset startedAt, StubOptions options)
    {
        try
        {
            var path = context.Request.Url?.AbsolutePath ?? "/";
            if (await TryWriteFixtureAsync(context, path, options.FixtureDirectory).ConfigureAwait(false))
            {
                return;
            }

            switch (path)
            {
                case "/map_info.json":
                    await WriteJsonAsync(context, CreateMapInfo()).ConfigureAwait(false);
                    break;
                case "/map_obj.json":
                    await WriteJsonAsync(context, CreateMapObjects((DateTimeOffset.UtcNow - startedAt).TotalSeconds)).ConfigureAwait(false);
                    break;
                case "/map.img":
                    await WriteBytesAsync(context, "image/bmp", MapImageGenerator.CreateBmp(512, 512)).ConfigureAwait(false);
                    break;
                case "/state":
                    await WriteJsonAsync(context, CreateState((DateTimeOffset.UtcNow - startedAt).TotalSeconds)).ConfigureAwait(false);
                    break;
                case "/hudmsg":
                    await WriteJsonAsync(context, CreateHudMessages((DateTimeOffset.UtcNow - startedAt).TotalSeconds)).ConfigureAwait(false);
                    break;
                case "/gamechat":
                    await WriteJsonAsync(context, CreateGameChat((DateTimeOffset.UtcNow - startedAt).TotalSeconds)).ConfigureAwait(false);
                    break;
                default:
                    await WriteTextAsync(context, "Ehwrj local API stub\n").ConfigureAwait(false);
                    break;
            }
        }
        catch (Exception ex)
        {
            context.Response.StatusCode = 500;
            await WriteTextAsync(context, ex.Message).ConfigureAwait(false);
        }
        finally
        {
            context.Response.Close();
        }
    }

    private static object CreateMapInfo()
    {
        return new
        {
            grid_size = new[] { 1.0, 1.0 },
            grid_steps = new[] { 0.1, 0.1 },
            min_x = 0.0,
            max_x = 1.0,
            min_y = 0.0,
            max_y = 1.0
        };
    }

    private static object[] CreateMapObjects(double seconds)
    {
        var phase = seconds * 0.18;
        var playerX = 0.5 + Math.Cos(phase) * 0.08;
        var playerY = 0.5 + Math.Sin(phase) * 0.08;

        return new object[]
        {
            new
            {
                id = "player",
                name = "Player",
                type = "player_aircraft",
                team = "player",
                icon = "fighter",
                x = Round(playerX),
                y = Round(playerY),
                dx = Round(Math.Cos(phase)),
                dy = Round(Math.Sin(phase))
            },
            new
            {
                id = "bandit-1",
                name = "Bandit 1",
                type = "aircraft",
                team = "enemy",
                icon = "fighter",
                x = Round(0.5 + Math.Cos(phase + 1.8) * 0.22),
                y = Round(0.5 + Math.Sin(phase + 1.8) * 0.18)
            },
            new
            {
                id = "bandit-2",
                name = "Bandit 2",
                type = "aircraft",
                team = "enemy",
                icon = "bomber",
                x = Round(0.5 + Math.Cos(phase * 0.7 + 3.2) * 0.34),
                y = Round(0.5 + Math.Sin(phase * 0.7 + 3.2) * 0.25)
            },
            new
            {
                id = "objective-a",
                name = "A",
                type = "capture_zone",
                team = "ally",
                icon = "zone",
                x = 0.25,
                y = 0.28
            },
            new
            {
                id = "objective-b",
                name = "B",
                type = "bombing_point",
                team = "enemy",
                icon = "zone",
                x = 0.72,
                y = 0.68
            }
        };
    }

    private static object CreateState(double seconds)
    {
        return new Dictionary<string, object>
        {
            ["TAS, km/h"] = Math.Round(720 + Math.Sin(seconds * 0.25) * 80, 1),
            ["IAS, km/h"] = Math.Round(650 + Math.Sin(seconds * 0.22) * 65, 1),
            ["Vy, m/s"] = Math.Round(Math.Sin(seconds * 0.34) * 38, 2),
            ["H, m"] = Math.Round(2200 + Math.Sin(seconds * 0.11) * 420, 1)
        };
    }

    private static object CreateHudMessages(double seconds)
    {
        var wave = (int)(seconds / 12) % 4;
        return new
        {
            events = new object[]
            {
                new { text = wave == 0 ? "Enemy aircraft spotted" : "Maintain formation", enemy = wave == 0 },
                new { text = "Objective B under attack", enemy = true }
            }
        };
    }

    private static object CreateGameChat(double seconds)
    {
        var wave = (int)(seconds / 15) % 3;
        return new
        {
            items = new object[]
            {
                new { text = wave == 0 ? "Cover the bomber route" : "Regroup near A", enemy = false }
            }
        };
    }

    private static double Round(double value)
    {
        return Math.Round(Math.Clamp(value, 0.02, 0.98), 5);
    }

    private static Task WriteJsonAsync(HttpListenerContext context, object value)
    {
        var json = JsonSerializer.Serialize(value, JsonOptions);
        return WriteBytesAsync(context, "application/json", Encoding.UTF8.GetBytes(json));
    }

    private static Task WriteTextAsync(HttpListenerContext context, string text)
    {
        return WriteBytesAsync(context, "text/plain; charset=utf-8", Encoding.UTF8.GetBytes(text));
    }

    private static async Task WriteBytesAsync(HttpListenerContext context, string contentType, byte[] bytes)
    {
        context.Response.ContentType = contentType;
        context.Response.ContentLength64 = bytes.Length;
        await context.Response.OutputStream.WriteAsync(bytes).ConfigureAwait(false);
    }

    private static async Task<bool> TryWriteFixtureAsync(HttpListenerContext context, string path, string? fixtureDirectory)
    {
        if (fixtureDirectory is null)
        {
            return false;
        }

        var fileName = path switch
        {
            "/map_info.json" => "map_info.json",
            "/map_obj.json" => "map_obj.json",
            "/map.img" => "map.img",
            "/state" => "state.json",
            "/hudmsg" => "hudmsg.json",
            "/gamechat" => "gamechat.json",
            _ => null
        };

        if (fileName is null)
        {
            return false;
        }

        var filePath = Path.Combine(fixtureDirectory, fileName);
        if (!File.Exists(filePath))
        {
            return false;
        }

        var bytes = await File.ReadAllBytesAsync(filePath).ConfigureAwait(false);
        var contentType = fileName.EndsWith(".json", StringComparison.OrdinalIgnoreCase)
            ? "application/json"
            : "image/bmp";
        await WriteBytesAsync(context, contentType, bytes).ConfigureAwait(false);
        return true;
    }
}

internal sealed record StubOptions(string Host, int Port, string? FixtureDirectory, bool ShowHelp)
{
    public static StubOptions Parse(IReadOnlyList<string> args)
    {
        var host = "127.0.0.1";
        var port = 8111;
        string? fixtureDirectory = null;

        for (var i = 0; i < args.Count; i++)
        {
            switch (args[i])
            {
                case "-h":
                case "--help":
                    return new StubOptions(host, port, fixtureDirectory, true);
                case "--host" when i + 1 < args.Count:
                    host = args[++i];
                    break;
                case "--port" when i + 1 < args.Count && int.TryParse(args[++i], NumberStyles.None, CultureInfo.InvariantCulture, out var parsed):
                    port = parsed;
                    break;
                case "--fixture-dir" when i + 1 < args.Count:
                    fixtureDirectory = args[++i];
                    break;
                default:
                    Console.Error.WriteLine($"Ignoring unknown argument: {args[i]}");
                    break;
            }
        }

        return new StubOptions(host, port, fixtureDirectory, false);
    }

    public static void PrintHelp()
    {
        Console.WriteLine("""
        Usage:
          dotnet run --project tools/Ehwrj.Tools.LocalApiStub -- [--host 127.0.0.1] [--port 8111] [--fixture-dir captures/name]

        Serves deterministic War Thunder-like local map, telemetry, and message endpoints for UI development.
        When --fixture-dir is supplied, files captured by Ehwrj.Tools.Capture are replayed first.
        """);
    }
}

internal static class MapImageGenerator
{
    public static byte[] CreateBmp(int width, int height)
    {
        var stride = ((width * 3 + 3) / 4) * 4;
        var pixelBytes = stride * height;
        var fileSize = 54 + pixelBytes;
        var bytes = new byte[fileSize];

        bytes[0] = (byte)'B';
        bytes[1] = (byte)'M';
        WriteInt32(bytes, 2, fileSize);
        WriteInt32(bytes, 10, 54);
        WriteInt32(bytes, 14, 40);
        WriteInt32(bytes, 18, width);
        WriteInt32(bytes, 22, height);
        WriteInt16(bytes, 26, 1);
        WriteInt16(bytes, 28, 24);
        WriteInt32(bytes, 34, pixelBytes);

        for (var y = 0; y < height; y++)
        {
            var sourceY = height - 1 - y;
            for (var x = 0; x < width; x++)
            {
                var nx = x / (double)Math.Max(1, width - 1);
                var ny = sourceY / (double)Math.Max(1, height - 1);
                var grid = x % 64 == 0 || sourceY % 64 == 0;
                var water = Math.Sin(nx * 8.0) + Math.Cos(ny * 7.0) > 1.15;

                var r = water ? (byte)28 : (byte)(42 + ny * 34);
                var g = water ? (byte)78 : (byte)(74 + nx * 46);
                var b = water ? (byte)98 : (byte)(58 + (1 - ny) * 34);

                if (grid)
                {
                    r = (byte)Math.Min(255, r + 38);
                    g = (byte)Math.Min(255, g + 38);
                    b = (byte)Math.Min(255, b + 38);
                }

                var offset = 54 + y * stride + x * 3;
                bytes[offset] = b;
                bytes[offset + 1] = g;
                bytes[offset + 2] = r;
            }
        }

        return bytes;
    }

    private static void WriteInt16(byte[] bytes, int offset, short value)
    {
        bytes[offset] = (byte)value;
        bytes[offset + 1] = (byte)(value >> 8);
    }

    private static void WriteInt32(byte[] bytes, int offset, int value)
    {
        bytes[offset] = (byte)value;
        bytes[offset + 1] = (byte)(value >> 8);
        bytes[offset + 2] = (byte)(value >> 16);
        bytes[offset + 3] = (byte)(value >> 24);
    }
}
