using ZB.MOM.WW.MxGateway.Server.Workers;

namespace ZB.MOM.WW.MxGateway.Tests.TestSupport;

/// <summary>
/// Lightweight in-process stand-in for <see cref="IWorkerProcess"/> used by fake worker
/// launchers and lifecycle tests.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="WaitForExitAsync"/> awaits a <see cref="TaskCompletionSource"/> that is
/// completed only by <see cref="Kill"/> or <see cref="MarkExited"/>, so a caller observing
/// completion can trust that exit actually happened rather than passing spuriously.
/// </para>
/// <para>
/// The disposal and kill bookkeeping is exposed under several aliases
/// (<see cref="IsDisposed"/>/<see cref="DisposeCalled"/>/<see cref="Disposed"/>;
/// <see cref="KillCount"/>/<see cref="KillCalled"/>) so the various lifecycle tests can
/// keep their existing assertion vocabulary while sharing one definition.
/// </para>
/// </remarks>
public sealed class FakeWorkerProcess(int processId) : IWorkerProcess
{
    private readonly TaskCompletionSource _exited = new(TaskCreationOptions.RunContinuationsAsynchronously);

    /// <inheritdoc />
    public int Id { get; } = processId;

    /// <inheritdoc />
    public bool HasExited { get; set; }

    /// <inheritdoc />
    public int? ExitCode { get; set; }

    /// <summary>Gets the number of times <see cref="Kill"/> was called.</summary>
    public int KillCount { get; private set; }

    /// <summary>Gets a value indicating whether <see cref="Kill"/> was called at least once.</summary>
    public bool KillCalled => KillCount > 0;

    /// <summary>Gets the <c>entireProcessTree</c> flag from the most recent <see cref="Kill"/> call.</summary>
    public bool KillEntireProcessTree { get; private set; }

    /// <summary>Gets a value indicating whether <see cref="Dispose"/> was called.</summary>
    public bool IsDisposed { get; private set; }

    /// <summary>Gets a value indicating whether <see cref="Dispose"/> was called.</summary>
    public bool DisposeCalled => IsDisposed;

    /// <summary>Gets a value indicating whether <see cref="Dispose"/> was called.</summary>
    public bool Disposed => IsDisposed;

    /// <inheritdoc />
    public ValueTask WaitForExitAsync(CancellationToken cancellationToken) =>
        new(_exited.Task.WaitAsync(cancellationToken));

    /// <inheritdoc />
    public void Kill(bool entireProcessTree)
    {
        KillCount++;
        KillEntireProcessTree = entireProcessTree;
        MarkExited(-1);
    }

    /// <summary>Marks this fake as disposed so tests can assert <see cref="Dispose"/> was called.</summary>
    public void Dispose() => IsDisposed = true;

    /// <summary>
    /// Marks the process as exited with the specified exit code and unblocks
    /// any callers of <see cref="WaitForExitAsync"/>.
    /// </summary>
    /// <param name="exitCode">The process exit code.</param>
    public void MarkExited(int exitCode)
    {
        HasExited = true;
        ExitCode = exitCode;
        _exited.TrySetResult();
    }
}
