using System.Security.Cryptography;
using Grpc.Core;
using Satellite.V1;
using SatelliteProvider.Common.DTO;
using SatelliteProvider.Common.Utils;

namespace SatelliteProvider.IntegrationTests;

public static class RouteTileDeliveryGrpcTests
{
    private const double Lat1 = 48.276067180586544;
    private const double Lon1 = 37.38445758819581;
    private const double Lat2 = 48.27074009522731;
    private const double Lon2 = 37.374029159545906;
    private const double RegionSizeMeters = 500;
    private const int Zoom = 18;

    public static async Task RunAll(HttpClient httpClient, string apiUrl, string secret)
    {
        RouteTestHelpers.PrintTestHeader("Test: gRPC RouteTileDelivery (AZ-1074 / AZ-1075)");

        var token = JwtTestHelpers.MintAuthenticated(secret);
        var client = GrpcTestHelpers.CreateClient(apiUrl, token);
        var headers = GrpcTestHelpers.AuthMetadata(token);

        await RunHappyPath(client, headers);
        await RunInvalidRequests(client, headers);
        await RunBackpressureSafe(client, headers);
        await RunRestConsistency(httpClient, client, headers);
    }

    private static async Task RunHappyPath(RouteTileDelivery.RouteTileDeliveryClient client, Metadata headers)
    {
        RouteTestHelpers.PrintSectionHeader("AC-1: Happy path streams tiles");

        var request = GrpcTestHelpers.BuildValidRequest(
            lat1: Lat1, lon1: Lon1, lat2: Lat2, lon2: Lon2,
            regionSizeMeters: RegionSizeMeters, zoom: Zoom);

        using var call = client.DeliverRouteTiles(request, headers);
        var (manifest, tiles, complete, error) = await GrpcTestHelpers.CollectStreamAsync(call);

        if (error is not null)
        {
            throw new Exception($"Happy path returned DeliveryError: {error.Code} — {error.Message}");
        }

        if (manifest is null)
        {
            throw new Exception("Happy path did not emit RouteManifest");
        }

        if (tiles.Count == 0 && manifest.ToDeliver > 0)
        {
            throw new Exception($"Expected tiles in stream (to_deliver={manifest.ToDeliver})");
        }

        if (complete is null)
        {
            throw new Exception("Happy path did not emit DeliveryComplete");
        }

        Console.WriteLine($"  ✓ Manifest: candidates={manifest.TotalCandidates}, to_deliver={manifest.ToDeliver}");
        Console.WriteLine($"  ✓ Received {tiles.Count} tile(s), complete.delivered={complete.Delivered}");
        Console.WriteLine();
    }

    private static async Task RunInvalidRequests(RouteTileDelivery.RouteTileDeliveryClient client, Metadata headers)
    {
        RouteTestHelpers.PrintSectionHeader("AC-2/AC-3: Invalid requests return InvalidArgument");

        var emptyRoute = GrpcTestHelpers.BuildValidRequest();
        emptyRoute.Route!.Waypoints.Clear();
        emptyRoute.Route.Waypoints.Add(new Waypoint { Lat = Lat1, Lon = Lon1 });

        await GrpcTestHelpers.ExpectInvalidArgumentAsync(async () =>
        {
            using var call = client.DeliverRouteTiles(emptyRoute, headers);
            await GrpcTestHelpers.CollectStreamAsync(call);
        }, "Single waypoint route");

        var invalidCoord = GrpcTestHelpers.BuildValidRequest();
        invalidCoord.Route!.Waypoints[0] = new Waypoint { Lat = 91.0, Lon = Lon1 };

        await GrpcTestHelpers.ExpectInvalidArgumentAsync(async () =>
        {
            using var call = client.DeliverRouteTiles(invalidCoord, headers);
            await GrpcTestHelpers.CollectStreamAsync(call);
        }, "Latitude out of range");

        var invalidZoom = GrpcTestHelpers.BuildValidRequest(zoom: 99);

        await GrpcTestHelpers.ExpectInvalidArgumentAsync(async () =>
        {
            using var call = client.DeliverRouteTiles(invalidZoom, headers);
            await GrpcTestHelpers.CollectStreamAsync(call);
        }, "Zoom out of allowed range");

        Console.WriteLine();
    }

    private static async Task RunBackpressureSafe(RouteTileDelivery.RouteTileDeliveryClient client, Metadata headers)
    {
        RouteTestHelpers.PrintSectionHeader("AC-4: Backpressure safe — slow consumer preserves JPEG integrity");

        var request = GrpcTestHelpers.BuildValidRequest(
            lat1: Lat1, lon1: Lon1, lat2: Lat2, lon2: Lon2,
            regionSizeMeters: RegionSizeMeters, zoom: Zoom);

        using var call = client.DeliverRouteTiles(request, headers);
        var (_, tiles, _, error) = await GrpcTestHelpers.CollectStreamAsync(call, delayMsBetweenEvents: 50);

        if (error is not null)
        {
            throw new Exception($"Backpressure test returned DeliveryError: {error.Code}");
        }

        foreach (var tile in tiles)
        {
            if (tile.Jpeg.Length < 2 || tile.Jpeg[0] != 0xFF || tile.Jpeg[1] != 0xD8)
            {
                throw new Exception($"Tile ({tile.Z},{tile.X},{tile.Y}) JPEG header invalid after slow read");
            }

            if (tile.ContentSha256.Length == 32)
            {
                var computed = SHA256.HashData(tile.Jpeg.ToByteArray());
                if (!computed.AsSpan().SequenceEqual(tile.ContentSha256.Span))
                {
                    throw new Exception($"Tile ({tile.Z},{tile.X},{tile.Y}) content_sha256 mismatch after slow read");
                }
            }
        }

        Console.WriteLine($"  ✓ Verified {tiles.Count} tile(s) with intact JPEG + SHA256 under slow consumption");
        Console.WriteLine();
    }

    private static async Task RunRestConsistency(
        HttpClient httpClient,
        RouteTileDelivery.RouteTileDeliveryClient grpcClient,
        Metadata headers)
    {
        RouteTestHelpers.PrintSectionHeader("AC-3 (AZ-1075): REST and gRPC tile metadata consistency");

        var routeId = Guid.NewGuid();
        var restRequest = new CreateRouteRequest
        {
            Id = routeId,
            Name = "gRPC consistency probe",
            RegionSizeMeters = RegionSizeMeters,
            ZoomLevel = Zoom,
            RequestMaps = true,
            Points =
            [
                new RoutePointInput { Lat = Lat1, Lon = Lon1 },
                new RoutePointInput { Lat = Lat2, Lon = Lon2 },
            ],
        };

        await RouteTestHelpers.CreateRoute(httpClient, restRequest);
        var restRoute = await RouteTestHelpers.WaitForRouteReady(httpClient, routeId, maxAttempts: 120);

        if (string.IsNullOrEmpty(restRoute.CsvFilePath) || !File.Exists(restRoute.CsvFilePath))
        {
            throw new Exception("REST route CSV not available for consistency check");
        }

        var csvLines = await File.ReadAllLinesAsync(restRoute.CsvFilePath);
        var restTileKeys = new HashSet<(int Z, int X, int Y)>();
        foreach (var line in csvLines.Skip(1))
        {
            var parts = line.Split(',');
            if (parts.Length < 2
                || !double.TryParse(parts[0], out var lat)
                || !double.TryParse(parts[1], out var lon))
            {
                continue;
            }

            var (x, y) = GeoUtils.WorldToTilePos(new GeoPoint(lat, lon), Zoom);
            restTileKeys.Add((Zoom, x, y));
        }

        var grpcRequest = GrpcTestHelpers.BuildValidRequest(
            routeId: Guid.NewGuid(),
            lat1: Lat1, lon1: Lon1, lat2: Lat2, lon2: Lon2,
            regionSizeMeters: RegionSizeMeters, zoom: Zoom);

        using var call = grpcClient.DeliverRouteTiles(grpcRequest, headers);
        var (_, grpcTiles, _, error) = await GrpcTestHelpers.CollectStreamAsync(call);

        if (error is not null)
        {
            throw new Exception($"gRPC consistency run failed: {error.Code} — {error.Message}");
        }

        var grpcKeys = grpcTiles.Select(t => (t.Z, t.X, t.Y)).ToHashSet();
        var overlap = grpcKeys.Intersect(restTileKeys).Count();

        if (grpcKeys.Count == 0)
        {
            throw new Exception("gRPC stream delivered no tiles for consistency comparison");
        }

        if (overlap == 0)
        {
            throw new Exception(
                $"No overlapping tile keys between REST CSV ({restTileKeys.Count}) and gRPC stream ({grpcKeys.Count})");
        }

        Console.WriteLine($"  ✓ REST CSV tiles: {restTileKeys.Count}, gRPC tiles: {grpcKeys.Count}, overlap: {overlap}");
        Console.WriteLine();
    }
}
