using System.Net;
using System.Net.Sockets;
using ClaudeDo.Installer.Checks;
using ClaudeDo.Installer.Checks.Interfaces;
using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Tests.Pages.SystemCheckPage;
using Vm = ClaudeDo.Installer.Pages.DiagnosePage.DiagnosePageViewModel;

namespace ClaudeDo.Installer.Tests.Pages.DiagnosePage;

public sealed class DiagnosePageViewModelTests
{
    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 Loading_the_page_does_not_run_checks()
    {
        var check = FakeEnvironmentCheck.Ok("a");
        var vm = new Vm(
            new InstallContext(),
            () => new EnvironmentCheckService(new IEnvironmentCheck[] { check }),
            () => new InstallerWorkerConfig());

        await vm.LoadAsync();

        Assert.Equal(0, check.CallCount);
        Assert.False(vm.Checks.HasRun);
        Assert.Empty(vm.Checks.Rows);
    }

    [Fact]
    public async Task Clicking_check_runs_once_and_a_second_click_during_the_run_starts_no_new_run()
    {
        var gate = new TaskCompletionSource();
        var slow = new FakeEnvironmentCheck("slow", CheckSeverity.Error, async (_, _) =>
        {
            await gate.Task;
            return CheckResult.Ok("slow", CheckSeverity.Error, "checks.slow.title", "ok");
        });

        var factoryCalls = 0;
        var vm = new Vm(
            new InstallContext(),
            () =>
            {
                factoryCalls++;
                return new EnvironmentCheckService(new IEnvironmentCheck[] { slow });
            },
            () => new InstallerWorkerConfig());

        await vm.LoadAsync();

        var firstRun = vm.Checks.RunChecksCommand.ExecuteAsync(null);
        Assert.True(vm.Checks.IsRunning);

        await vm.Checks.RunChecksCommand.ExecuteAsync(null); // second click while the first run is still in flight

        Assert.Equal(1, factoryCalls);
        Assert.Equal(1, slow.CallCount);

        gate.SetResult();
        await firstRun;

        Assert.False(vm.Checks.IsRunning);
    }

    [Fact]
    public async Task Checks_run_against_the_installed_configuration_not_context_defaults()
    {
        InstallContext? capturedCtx = null;
        var probe = new FakeEnvironmentCheck("probe", CheckSeverity.Warning, (ctx, _) =>
        {
            capturedCtx = ctx;
            return Task.FromResult(CheckResult.Ok("probe", CheckSeverity.Warning, "checks.probe.title", "ok"));
        });

        var sharedContext = new InstallContext { InstallDirectory = @"C:\Program Files\ClaudeDo" };
        var vm = new Vm(
            sharedContext,
            () => new EnvironmentCheckService(new IEnvironmentCheck[] { probe }),
            () => new InstallerWorkerConfig { ClaudeBin = @"C:\installed\claude.exe", SignalRPort = 55555 });

        await vm.LoadAsync();
        await vm.Checks.RunChecksCommand.ExecuteAsync(null);

        Assert.NotNull(capturedCtx);
        Assert.Equal(@"C:\installed\claude.exe", capturedCtx!.ClaudeBin);
        Assert.Equal(55555, capturedCtx.SignalRPort);
        Assert.Equal(@"C:\Program Files\ClaudeDo", capturedCtx.InstallDirectory);
    }

    [Fact]
    public async Task Worker_already_running_on_the_installed_signalr_port_reports_ok()
    {
        var port = GetFreePort();
        using var occupying = new TcpListener(IPAddress.Loopback, port);
        occupying.Start();

        var sharedContext = new InstallContext { ExternalMcpPort = GetFreePort() };
        var vm = new Vm(
            sharedContext,
            () => new EnvironmentCheckService(new IEnvironmentCheck[] { new PortCheck(new FakeOwnerResolver("ClaudeDo.Worker")) }),
            () => new InstallerWorkerConfig { SignalRPort = port });

        await vm.LoadAsync();
        await vm.Checks.RunChecksCommand.ExecuteAsync(null);

        var row = Assert.Single(vm.Checks.Rows);
        Assert.Equal(CheckStatus.Ok, row.Status);
    }
}
