using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Grpc.Core;
using Grpc.Net.Client;
using Satellite.V1;

namespace SatelliteProvider.IntegrationTests;

public static class GrpcTestHelpers
{
    public static GrpcChannel CreateChannel(string apiUrl)
    {
        var handler = new SocketsHttpHandler
        {
            EnableMultipleHttp2Connections = true,
        };

        var caCertPath = ResolveCaCertPath();
        if (!string.IsNullOrEmpty(caCertPath))
        {
            var caCert = X509Certificate2.CreateFromPem(File.ReadAllText(caCertPath));
            handler.SslOptions = new SslClientAuthenticationOptions
            {
                RemoteCertificateValidationCallback = (_, certificate, _, errors) =>
                {
                    if (errors == SslPolicyErrors.None)
                    {
                        return true;
                    }

                    if (certificate is null)
                    {
                        return false;
                    }

                    using var chain = new X509Chain();
                    chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                    chain.ChainPolicy.CustomTrustStore.Add(caCert);
                    chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
                    return chain.Build(new X509Certificate2(certificate));
                },
            };
        }

        return GrpcChannel.ForAddress(apiUrl, new GrpcChannelOptions
        {
            HttpHandler = handler,
        });
    }

    private static string? ResolveCaCertPath()
    {
        var explicitPath = Environment.GetEnvironmentVariable("PERF_CA_CERT");
        if (!string.IsNullOrWhiteSpace(explicitPath) && File.Exists(explicitPath))
        {
            return explicitPath;
        }

        var projectRoot = Environment.GetEnvironmentVariable("PROJECT_ROOT");
        if (!string.IsNullOrWhiteSpace(projectRoot))
        {
            var defaultPath = Path.Combine(projectRoot, "certs", "api.crt");
            if (File.Exists(defaultPath))
            {
                return defaultPath;
            }
        }

        return null;
    }

    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}");
        }
    }
}
