using Grpc.Core;
using Grpc.Net.Client;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using ZB.MOM.WW.Health;

namespace ZB.MOM.WW.Health.Tests;

/// <summary>
/// Verifies <see cref="GrpcDependencyHealthCheck"/> via an injected probe (no live gRPC server):
/// probe-true → Healthy, probe-false → Unhealthy, and an <see cref="RpcException"/> from the probe
/// → Unhealthy. The channel is constructed but never dialled because the probe is stubbed.
/// </summary>
public sealed class GrpcDependencyHealthCheckTests
{
    private static readonly GrpcChannel Channel = GrpcChannel.ForAddress("http://localhost");

    private static async Task<HealthCheckResult> RunAsync(
        GrpcDependencyOptions options, CancellationToken cancellationToken = default)
    {
        var check = new GrpcDependencyHealthCheck(Channel, options);
        var context = new HealthCheckContext
        {
            Registration = new HealthCheckRegistration("grpc-dep", check, HealthStatus.Unhealthy, tags: null),
        };
        return await check.CheckHealthAsync(context, cancellationToken);
    }

    [Fact]
    public async Task ProbeReturnsTrue_Healthy()
    {
        var result = await RunAsync(new GrpcDependencyOptions
        {
            Probe = static (_, _) => Task.FromResult(true),
        });

        Assert.Equal(HealthStatus.Healthy, result.Status);
    }

    [Fact]
    public async Task ProbeReturnsFalse_Unhealthy()
    {
        var result = await RunAsync(new GrpcDependencyOptions
        {
            Probe = static (_, _) => Task.FromResult(false),
        });

        Assert.Equal(HealthStatus.Unhealthy, result.Status);
    }

    [Fact]
    public async Task ProbeThrowsRpcException_Unhealthy()
    {
        var result = await RunAsync(new GrpcDependencyOptions
        {
            Probe = static (_, _) => throw new RpcException(new Status(StatusCode.Unavailable, "down")),
        });

        Assert.Equal(HealthStatus.Unhealthy, result.Status);
    }

    [Fact]
    public async Task DependencyName_AppearsInDescription()
    {
        var result = await RunAsync(new GrpcDependencyOptions
        {
            DependencyName = "mxaccessgw worker",
            Probe = static (_, _) => Task.FromResult(false),
        });

        Assert.Equal(HealthStatus.Unhealthy, result.Status);
        Assert.Contains("mxaccessgw worker", result.Description);
    }

    [Fact]
    public async Task ProbeThrowsArbitraryException_Unhealthy()
    {
        // A non-RpcException / non-OperationCanceledException (e.g. the transport surfacing an
        // InvalidOperationException) must be caught and mapped to Unhealthy, not allowed to escape.
        var result = await RunAsync(new GrpcDependencyOptions
        {
            DependencyName = "mxaccessgw worker",
            Probe = static (_, _) => throw new InvalidOperationException("channel disposed"),
        });

        Assert.Equal(HealthStatus.Unhealthy, result.Status);
        Assert.Contains("mxaccessgw worker", result.Description);
    }

    [Fact]
    public async Task ProbeExceedsTimeout_Unhealthy()
    {
        var result = await RunAsync(new GrpcDependencyOptions
        {
            Timeout = TimeSpan.FromMilliseconds(50),
            Probe = static async (_, ct) =>
            {
                await Task.Delay(Timeout.Infinite, ct);
                return true;
            },
        });

        Assert.Equal(HealthStatus.Unhealthy, result.Status);
    }

    [Fact]
    public async Task ExternalCancellation_Throws()
    {
        using var cts = new CancellationTokenSource();
        await cts.CancelAsync();

        await Assert.ThrowsAnyAsync<OperationCanceledException>(() => RunAsync(
            new GrpcDependencyOptions
            {
                Probe = static async (_, ct) =>
                {
                    await Task.Delay(Timeout.Infinite, ct);
                    return true;
                },
            },
            cts.Token));
    }
}
