using System.Net;
using System.Net.Sockets;
using ClaudeDo.Installer.Checks;
using ClaudeDo.Installer.Checks.Interfaces;
using ClaudeDo.Installer.Core;

namespace ClaudeDo.Installer.Tests.Checks;

public sealed class PortCheckTests
{
    private sealed class FakeOwnerResolver : IPortOwnerResolver
    {
        private readonly string? _owner;
        public FakeOwnerResolver(string? owner) => _owner = owner;
        public Task<string?> FindOwningProcessNameAsync(int port, CancellationToken ct) => Task.FromResult(_owner);
    }

    private static int GetFreePort()
    {
        var listener = new TcpListener(IPAddress.Loopback, 0);
        listener.Start();
        var port = ((IPEndPoint)listener.LocalEndpoint).Port;
        listener.Stop();
        return port;
    }

    [Fact]
    public async Task Both_ports_free_reports_ok()
    {
        var ctx = new InstallContext { SignalRPort = GetFreePort(), ExternalMcpPort = GetFreePort() };
        var check = new PortCheck(new FakeOwnerResolver(null));

        var result = await check.RunAsync(ctx, CancellationToken.None);

        Assert.Equal(CheckStatus.Ok, result.Status);
    }

    [Fact]
    public async Task Port_in_use_by_other_process_reports_failed()
    {
        var port = GetFreePort();
        using var occupying = new TcpListener(IPAddress.Loopback, port);
        occupying.Start();
        var ctx = new InstallContext { SignalRPort = port, ExternalMcpPort = GetFreePort() };
        var check = new PortCheck(new FakeOwnerResolver("SomeOtherApp"));

        var result = await check.RunAsync(ctx, CancellationToken.None);

        Assert.Equal(CheckStatus.Failed, result.Status);
        Assert.Equal(CheckSeverity.Warning, result.Severity);
        Assert.Contains("SomeOtherApp", result.Message);
    }

    [Fact]
    public async Task Port_in_use_by_own_worker_reports_ok()
    {
        var port = GetFreePort();
        using var occupying = new TcpListener(IPAddress.Loopback, port);
        occupying.Start();
        var ctx = new InstallContext { SignalRPort = port, ExternalMcpPort = GetFreePort() };
        var check = new PortCheck(new FakeOwnerResolver("ClaudeDo.Worker"));

        var result = await check.RunAsync(ctx, CancellationToken.None);

        Assert.Equal(CheckStatus.Ok, result.Status);
        Assert.Contains("ClaudeDo Worker", result.Message);
    }
}
