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

namespace Ehwrj.Tests;

internal static class Program
{
    private static int Main()
    {
        try
        {
            ParsesMapInfoGridSize();
            ParsesMapInfoBoundsAndStringNumbers();
            ParsesObjectsAndClassifiesAircraft();
            ParsesObjectCoordinateAndDirectionArrays();
            ClassifiesTeamsAndObjectives();
            ParsesHeadingInDegrees();
            ParsesFlightState();
            ParsesBattleMessages();
            AnalyzesCaptureFixtureQuality();
            EnforcesLoopbackNetworkScope();
            LocalizesUiTextAndStatus();
            ParsesWrappedObjectList();
            ProjectsNormalizedCoordinates();
            ProjectsBoundedCoordinates();
            Console.WriteLine("All tests passed.");
            return 0;
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex.Message);
            return 1;
        }
    }

    private static void ParsesMapInfoGridSize()
    {
        var info = MapInfo.FromJson("""{"grid_size":[10,20],"grid_steps":[1,2]}""");
        AssertEqual(0, info.MinX, "MinX");
        AssertEqual(10, info.MaxX, "MaxX");
        AssertEqual(0, info.MinY, "MinY");
        AssertEqual(20, info.MaxY, "MaxY");
        AssertEqual(1, info.GridStepX, "GridStepX");
        AssertEqual(2, info.GridStepY, "GridStepY");
    }

    private static void ParsesMapInfoBoundsAndStringNumbers()
    {
        var info = MapInfo.FromJson(
            """
            {
              "bounds": [["-1,5","-2.25"],["3,5","4.25"]],
              "grid_steps": ["0,5","1.25"]
            }
            """);

        AssertEqual(-1.5, info.MinX, "bounds MinX");
        AssertEqual(3.5, info.MaxX, "bounds MaxX");
        AssertEqual(-2.25, info.MinY, "bounds MinY");
        AssertEqual(4.25, info.MaxY, "bounds MaxY");
        AssertEqual(0.5, info.GridStepX, "string GridStepX");
        AssertEqual(1.25, info.GridStepY, "string GridStepY");
    }

    private static void ParsesObjectsAndClassifiesAircraft()
    {
        var tracker = new ObjectTracker();
        var objects = MapObject.FromJson(
            """
            [
              {"id":"self","type":"player_aircraft","x":0.5,"y":0.5},
              {"id":"bandit","type":"aircraft","x":0.6,"y":0.6},
              {"id":"zone","type":"capture_zone","x":0.2,"y":0.3}
            ]
            """,
            tracker);

        AssertEqual(3, objects.Count, "object count");
        AssertTrue(objects[0].IsPlayer, "player classification");
        AssertTrue(objects[1].IsAircraft, "aircraft classification");
        AssertTrue(!objects[2].IsAircraft, "zone classification");
    }

    private static void ParsesObjectCoordinateAndDirectionArrays()
    {
        var tracker = new ObjectTracker();
        var objects = MapObject.FromJson(
            """
            [
              {
                "id":"array-bandit",
                "icon":"fighter",
                "team":"enemy",
                "pos":["0,25","0.75"],
                "direction":["1","0"],
                "heading":"180"
              }
            ]
            """,
            tracker);

        AssertEqual(0.25, objects[0].X, "array x");
        AssertEqual(0.75, objects[0].Y, "array y");
        AssertEqual(1, objects[0].DirectionX, "array direction x");
        AssertEqual(0, objects[0].DirectionY, "array direction y");
        AssertEqual(Math.PI, objects[0].HeadingRadians.GetValueOrDefault(), "string heading radians");
        AssertEqual((int)MapObjectKind.Enemy, (int)objects[0].Kind, "array enemy kind");
    }

    private static void ClassifiesTeamsAndObjectives()
    {
        var tracker = new ObjectTracker();
        var objects = MapObject.FromJson(
            """
            [
              {"id":"ally","type":"aircraft","team":"ally","x":0.1,"y":0.2},
              {"id":"enemy","type":"aircraft","team":"enemy","x":0.2,"y":0.3},
              {"id":"squad","type":"aircraft","team":"squad","x":0.3,"y":0.4},
              {"id":"target","type":"bombing_point","team":"enemy","x":0.4,"y":0.5}
            ]
            """,
            tracker);

        AssertEqual((int)MapObjectKind.Ally, (int)objects[0].Kind, "ally kind");
        AssertEqual((int)MapObjectKind.Enemy, (int)objects[1].Kind, "enemy kind");
        AssertEqual((int)MapObjectKind.Squad, (int)objects[2].Kind, "squad kind");
        AssertTrue(objects[3].IsEnemyBombingPoint, "enemy bombing point");
    }

    private static void ParsesHeadingInDegrees()
    {
        var tracker = new ObjectTracker();
        var objects = MapObject.FromJson("""[{"id":"p","type":"player_aircraft","heading":90,"x":0.5,"y":0.5}]""", tracker);
        AssertTrue(objects[0].HeadingRadians.HasValue, "heading exists");
        AssertEqual(Math.PI / 2, objects[0].HeadingRadians.GetValueOrDefault(), "heading radians");
    }

    private static void ParsesFlightState()
    {
        var state = FlightState.FromJson("""{"TAS, km/h":720,"IAS, km/h":"650","Vy, m/s":36,"H, m":2500}""");
        AssertEqual(720, state.TrueAirspeedKmh, "tas");
        AssertEqual(650, state.IndicatedAirspeedKmh, "ias");
        AssertTrue(state.Mach.HasValue, "mach");
        AssertTrue(state.ClimbAngleDegrees.HasValue, "climb angle");
    }

    private static void ParsesBattleMessages()
    {
        var messages = BattleMessage.FromJson(
            """
            {
              "events": [
                {"text":"<b>Enemy spotted</b>","enemy":true},
                {"message":"Ally captured A","enemy":false}
              ]
            }
            """);

        AssertEqual(2, messages.Count, "message count");
        AssertEqual("Enemy spotted", messages[0].Text, "cleaned message");
        AssertTrue(messages[0].Enemy == true, "enemy flag");
        AssertTrue(messages[1].Enemy == false, "ally flag");
    }

    private static void AnalyzesCaptureFixtureQuality()
    {
        var directory = Path.Combine(Path.GetTempPath(), $"ehwrj-capture-test-{Guid.NewGuid():N}");
        Directory.CreateDirectory(directory);

        try
        {
            File.WriteAllText(Path.Combine(directory, "map_info.json"), """{"grid_size":[10,20],"grid_steps":[1,2]}""");
            File.WriteAllText(
                Path.Combine(directory, "map_obj.json"),
                """
                [
                  {"id":"self","type":"player_aircraft","team":"ally","x":0.5,"y":0.5},
                  {"id":"wing","type":"aircraft","team":"ally","x":0.4,"y":0.5},
                  {"id":"bandit","type":"aircraft","team":"enemy","x":0.6,"y":0.5},
                  {"id":"target","type":"bombing_point","team":"enemy","x":0.7,"y":0.5},
                  {"id":"mystery","type":"supply_marker","x":0.2,"y":0.8,"custom_flag":"sample"}
                ]
                """);
            File.WriteAllBytes(Path.Combine(directory, "map.img"), [1, 2, 3, 4]);
            File.WriteAllText(Path.Combine(directory, "state.json"), """{"TAS, km/h":720,"H, m":2500}""");
            File.WriteAllText(Path.Combine(directory, "hudmsg.json"), """{"events":[{"text":"Enemy spotted","enemy":true}]}""");
            File.WriteAllText(Path.Combine(directory, "gamechat.json"), """["Good luck"]""");

            var report = CaptureFixtureAnalyzer.AnalyzeDirectory(directory);
            AssertTrue(report.IsUsableForReplay, "capture replay usable");
            AssertEqual(5, report.ObjectCount, "capture object count");
            AssertEqual(1, report.PlayerCount, "capture player count");
            AssertEqual(2, report.AlliedAircraftCount, "capture allied aircraft count");
            AssertEqual(1, report.EnemyAircraftCount, "capture enemy aircraft count");
            AssertEqual(1, report.ObjectiveCount, "capture objective count");
            AssertEqual(1, report.UnknownObjectCount, "capture unknown object count");
            AssertTrue(report.ObjectFields.Any(static f => f.Name == "custom_flag" && f.Count == 1), "capture object field coverage");
            AssertTrue(report.UnknownObjectSamples.Any(static s => s.Contains("supply_marker", StringComparison.Ordinal)), "capture unknown sample");
            AssertTrue(report.StateFieldNames.Contains("TAS, km/h", StringComparer.Ordinal), "capture state field names");
            AssertTrue(report.HasSpeedTelemetry, "capture speed telemetry");
            AssertTrue(report.HasAltitudeTelemetry, "capture altitude telemetry");
            AssertEqual(1, report.HudMessageCount, "capture hud messages");
            AssertEqual(1, report.ChatMessageCount, "capture chat messages");
            AssertTrue(report.ToText().Contains("Replay usable: yes", StringComparison.Ordinal), "capture report text");
            AssertTrue(report.ToText().Contains("Object field coverage:", StringComparison.Ordinal), "capture field report text");
        }
        finally
        {
            Directory.Delete(directory, recursive: true);
        }
    }

    private static void EnforcesLoopbackNetworkScope()
    {
        AssertTrue(LoopbackGuard.IsLoopbackHttp(new Uri("http://127.0.0.1:8111/")), "127 loopback allowed");
        AssertTrue(LoopbackGuard.IsLoopbackHttp(new Uri("http://localhost:8111/")), "localhost allowed");
        AssertTrue(LoopbackGuard.IsLoopbackHost("127.0.0.1"), "127 host allowed");
        AssertTrue(LoopbackGuard.IsLoopbackHost("localhost"), "localhost host allowed");
        AssertTrue(!LoopbackGuard.IsLoopbackHttp(new Uri("https://127.0.0.1:8111/")), "https rejected");
        AssertTrue(!LoopbackGuard.IsLoopbackHttp(new Uri("http://example.com:8111/")), "external host rejected");
        AssertTrue(!LoopbackGuard.IsLoopbackHost("0.0.0.0"), "wildcard host rejected");
    }

    private static void LocalizesUiTextAndStatus()
    {
        var english = UiText.For("en-US");
        var korean = UiText.For("ko-KR");

        AssertEqual("Language", english.Language, "english language label");
        AssertEqual("언어", korean.Language, "korean language label");
        AssertEqual("127.0.0.1:8111 연결됨", korean.FormatStatus("Connected to 127.0.0.1:8111"), "localized connected status");
        AssertTrue(UiText.LanguageOptions.Any(static o => o.Code == "ko-KR"), "korean language option");
    }

    private static void ProjectsNormalizedCoordinates()
    {
        var point = CoordinateProjector.Project(
            new MapObject { Id = "p", X = 0.25, Y = 0.75 },
            MapInfo.Empty,
            new MapViewport(0, 0, 400, 200));

        AssertTrue(point.HasValue, "projection exists");
        var projected = point.GetValueOrDefault();
        AssertEqual(100, projected.X, "projected x");
        AssertEqual(150, projected.Y, "projected y");
    }

    private static void ParsesWrappedObjectList()
    {
        var tracker = new ObjectTracker();
        var objects = MapObject.FromJson(
            """
            {
              "objects": [
                {"id":"a","icon":"fighter","x":"0.1","y":"0.9"}
              ]
            }
            """,
            tracker);

        AssertEqual(1, objects.Count, "wrapped object count");
        AssertTrue(objects[0].IsAircraft, "wrapped aircraft classification");
        AssertEqual(0.1, objects[0].X, "wrapped x");
        AssertEqual(0.9, objects[0].Y, "wrapped y");
    }

    private static void ProjectsBoundedCoordinates()
    {
        var info = new MapInfo(-100, 100, -50, 50, null, null, null);
        var point = CoordinateProjector.Project(
            new MapObject { Id = "p", X = 0, Y = 25 },
            info,
            new MapViewport(10, 20, 200, 100));

        AssertTrue(point.HasValue, "bounded projection exists");
        var projected = point.GetValueOrDefault();
        AssertEqual(110, projected.X, "bounded x");
        AssertEqual(95, projected.Y, "bounded y");
    }

    private static void AssertTrue(bool condition, string name)
    {
        if (!condition)
        {
            throw new InvalidOperationException($"Assertion failed: {name}");
        }
    }

    private static void AssertEqual(double expected, double? actual, string name)
    {
        if (!actual.HasValue || Math.Abs(expected - actual.Value) > 0.0001)
        {
            throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
        }
    }

    private static void AssertEqual(double expected, double actual, string name)
    {
        if (Math.Abs(expected - actual) > 0.0001)
        {
            throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
        }
    }

    private static void AssertEqual(int expected, int actual, string name)
    {
        if (expected != actual)
        {
            throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
        }
    }

    private static void AssertEqual(string expected, string actual, string name)
    {
        if (!string.Equals(expected, actual, StringComparison.Ordinal))
        {
            throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
        }
    }
}
