using Grpc.Core;
using Grpc.Net.Client;
using Satellite.V1;

namespace SatelliteProvider.IntegrationTests;

public static class GrpcTestHelpers
{
    public static GrpcChannel CreateChannel(string apiUrl)
    {
        return GrpcChannel.ForAddress(apiUrl, new GrpcChannelOptions
        {
            HttpHandler = new SocketsHttpHandler
            {
                EnableMultipleHttp2Connections = true,
            },
        });
    }

    public static RouteTileDelivery.RouteTileDeliveryClient CreateClient(string apiUrl, string jwtToken)
    {
        var channel = CreateChannel(apiUrl);
        var client = new RouteTileDelivery.RouteTileDeliveryClient(channel);
        return client;
    }

    public static Metadata AuthMetadata(string jwtToken)
    {
        return new Metadata { { "Authorization", $"Bearer {jwtToken}" } };
    }

    public static DeliverRouteTilesRequest BuildValidRequest(
        Guid? routeId = null,
        double lat1 = 48.276067180586544,
        double lon1 = 37.38445758819581,
        double lat2 = 48.27074009522731,
        double lon2 = 37.374029159545906,
        double regionSizeMeters = 500,
        int zoom = 18)
    {
        return new DeliverRouteTilesRequest
        {
            Route = new RouteSpec
            {
                RouteId = (routeId ?? Guid.NewGuid()).ToString(),
                RegionSizeMeters = regionSizeMeters,
                Zoom = zoom,
                Waypoints =
                {
                    new Waypoint { Lat = lat1, Lon = lon1 },
                    new Waypoint { Lat = lat2, Lon = lon2 },
                },
            },
        };
    }

    public static async Task<(RouteManifest? Manifest, List<TilePayload> Tiles, DeliveryComplete? Complete, DeliveryError? Error)>
        CollectStreamAsync(AsyncServerStreamingCall<RouteTileEvent> call, int? delayMsBetweenEvents = null)
    {
        RouteManifest? manifest = null;
        var tiles = new List<TilePayload>();
        DeliveryComplete? complete = null;
        DeliveryError? error = null;

        await foreach (var evt in call.ResponseStream.ReadAllAsync())
        {
            switch (evt.PayloadCase)
            {
                case RouteTileEvent.PayloadOneofCase.Manifest:
                    manifest = evt.Manifest;
                    break;
                case RouteTileEvent.PayloadOneofCase.Batch:
                    tiles.AddRange(evt.Batch.Tiles);
                    break;
                case RouteTileEvent.PayloadOneofCase.Complete:
                    complete = evt.Complete;
                    break;
                case RouteTileEvent.PayloadOneofCase.Error:
                    error = evt.Error;
                    break;
            }

            if (delayMsBetweenEvents.HasValue)
            {
                await Task.Delay(delayMsBetweenEvents.Value);
            }
        }

        return (manifest, tiles, complete, error);
    }

    public static async Task ExpectInvalidArgumentAsync(Func<Task> action, string context)
    {
        try
        {
            await action();
            throw new Exception($"{context}: expected RpcException with InvalidArgument, but call succeeded");
        }
        catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument)
        {
            Console.WriteLine($"  ✓ {context}: InvalidArgument — {ex.Status.Detail}");
        }
        catch (RpcException ex)
        {
            throw new Exception($"{context}: expected InvalidArgument, got {ex.StatusCode} — {ex.Status.Detail}");
        }
    }
}
