using NucleicBroker;

namespace NucleicBroker.Tests;

/// <summary>
/// In-memory IWslc for the broker contract tests (docs/WINDOWS_PORT.md §14): records calls,
/// serves canned state, and exposes the event sink so tests can emit process output/exit and
/// session-death exactly as the wslc facade would — from arbitrary threads, asynchronously.
/// </summary>
public sealed class FakeWslc : IWslc
{
    public IBrokerEvents? Events;
    public List<string> Calls = [];
    public Dictionary<string, string> ContainerStates = [];
    public List<ImageInfo> Images = [];
    public List<string> MissingComponents = [];
    public string Gateway = "172.28.0.1";
    public SessionSpec? LastSession;
    public ContainerCreateSpec? LastCreate;
    public (string Name, int Signal, int GraceMs)? LastStop;
    public FakeProcess? LastProcess;
    public long LastProcId;
    public Exception? NextError;

    /// <summary>Set before a proc.exec to make that process emit during StartAsync — i.e. behave
    /// like a command that finishes instantly.</summary>
    public Action? NextProcessOnStart;

    public string? WslcVersion => "9.9-test";

    /// The fake is deliberately fully-capable: it stands in for a facade with BOTH wslc surfaces
    /// bound, so the broker's happy path is what these tests exercise. Degradation is the
    /// facade's business (docs/WINDOWS_PORT.md D13), and tests that care set this directly.
    public List<string> CapabilityList = ["enumerate", "reattach", "tty", "stats"];
    public IReadOnlyList<string> Capabilities => CapabilityList;

    public void SetEvents(IBrokerEvents events) => Events = events;

    private void Record(string call)
    {
        Calls.Add(call);
        if (NextError is { } error)
        {
            NextError = null;
            throw error;
        }
    }

    public Task<IReadOnlyList<string>> MissingComponentsAsync(CancellationToken ct)
    {
        Record("components.missing");
        return Task.FromResult<IReadOnlyList<string>>(MissingComponents);
    }

    public Task InstallComponentsAsync(CancellationToken ct)
    {
        Record("components.install");
        Events?.InstallProgress("installing", 50);
        Events?.InstallProgress("done", 100);
        return Task.CompletedTask;
    }

    public Task<string> EnsureSessionAsync(SessionSpec spec, CancellationToken ct)
    {
        Record("session.ensure");
        LastSession = spec;
        return Task.FromResult(Gateway);
    }

    public Task TerminateSessionAsync(CancellationToken ct)
    {
        Record("session.terminate");
        return Task.CompletedTask;
    }

    public Task PullImageAsync(string reference, RegistryAuth? auth, CancellationToken ct)
    {
        Record($"image.pull:{reference}");
        Events?.PullProgress(reference, "downloading", 10, 100);
        Events?.PullProgress(reference, "done", 100, 100);
        Images.Add(new ImageInfo(reference, "sha256:fake", 1234));
        return Task.CompletedTask;
    }

    public Task<IReadOnlyList<ImageInfo>> ListImagesAsync(CancellationToken ct)
    {
        Record("image.list");
        return Task.FromResult<IReadOnlyList<ImageInfo>>(Images);
    }

    public Task DeleteImageAsync(string reference, CancellationToken ct)
    {
        Record($"image.delete:{reference}");
        Images.RemoveAll(i => i.Ref == reference);
        return Task.CompletedTask;
    }

    public Task<ImageInfo?> InspectImageAsync(string reference, CancellationToken ct)
    {
        Record($"image.inspect:{reference}");
        return Task.FromResult(Images.FirstOrDefault(i => i.Ref == reference));
    }

    public Task CreateContainerAsync(ContainerCreateSpec spec, CancellationToken ct)
    {
        Record($"container.create:{spec.Name}");
        LastCreate = spec;
        ContainerStates[spec.Name] = "stopped";
        return Task.CompletedTask;
    }

    public Task StartContainerAsync(string name, CancellationToken ct)
    {
        Record($"container.start:{name}");
        if (!ContainerStates.ContainsKey(name))
            throw new WslcError(WslcError.NotFound, $"no container named {name}");
        ContainerStates[name] = "running";
        return Task.CompletedTask;
    }

    public Task StopContainerAsync(string name, int signal, int graceMs, CancellationToken ct)
    {
        Record($"container.stop:{name}");
        LastStop = (name, signal, graceMs);
        ContainerStates[name] = "stopped";
        return Task.CompletedTask;
    }

    public Task DeleteContainerAsync(string name, bool force, CancellationToken ct)
    {
        Record($"container.delete:{name}");
        ContainerStates.Remove(name);
        return Task.CompletedTask;
    }

    public Task<IReadOnlyList<ContainerInfo>> ListContainersAsync(CancellationToken ct)
    {
        Record("container.list");
        return Task.FromResult<IReadOnlyList<ContainerInfo>>(
            ContainerStates.Select(kv => new ContainerInfo(kv.Key, "img", kv.Value)).ToList());
    }

    public Task<string> ContainerStateAsync(string name, CancellationToken ct)
    {
        Record($"container.state:{name}");
        return Task.FromResult(ContainerStates.GetValueOrDefault(name, "absent"));
    }

    public Task<ContainerStatsInfo?> ContainerStatsAsync(string name, CancellationToken ct)
    {
        Record($"container.stats:{name}");
        return Task.FromResult<ContainerStatsInfo?>(
            ContainerStates.GetValueOrDefault(name) == "running"
                ? new ContainerStatsInfo(111, 222, 333, 0)
                : null);
    }

    public Task<IWslcProcess> ExecAsync(long procId, ProcSpec spec, CancellationToken ct)
    {
        Record($"proc.exec:{spec.Container}:{string.Join(' ', spec.Argv)}");
        if (ContainerStates.GetValueOrDefault(spec.Container) != "running")
            throw new WslcError(WslcError.NotRunning, $"container {spec.Container} isn't running");
        LastProcId = procId;
        // Mirror the real facade: create and attach, but do NOT run — BrokerService runs it after
        // the procId response is on the wire.
        LastProcess = new FakeProcess { OnStart = NextProcessOnStart };
        NextProcessOnStart = null;
        return Task.FromResult<IWslcProcess>(LastProcess);
    }

    public sealed class FakeProcess : IWslcProcess
    {
        public MemoryStream Stdin = new();
        public bool StdinClosed;
        public List<int> Signals = [];
        public (int Cols, int Rows)? LastResize;
        public bool Started;

        /// <summary>Lets a test stand in for a process that finishes DURING start — an `echo`,
        /// which is what exposed the response-ordering bug on hardware.</summary>
        public Action? OnStart;

        public Task StartAsync(CancellationToken ct)
        {
            Started = true;
            OnStart?.Invoke();
            return Task.CompletedTask;
        }

        public Task WriteStdinAsync(ReadOnlyMemory<byte> data, CancellationToken ct)
        {
            Stdin.Write(data.Span);
            return Task.CompletedTask;
        }

        public Task CloseStdinAsync(CancellationToken ct)
        {
            StdinClosed = true;
            return Task.CompletedTask;
        }

        public Task SignalAsync(int signal, CancellationToken ct)
        {
            Signals.Add(signal);
            return Task.CompletedTask;
        }

        public Task ResizeAsync(int cols, int rows, CancellationToken ct)
        {
            LastResize = (cols, rows);
            return Task.CompletedTask;
        }
    }
}
