using System.Globalization;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.Auth.Abstractions.ApiKeys;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Server.Configuration;
using ZB.MOM.WW.MxGateway.Server.Dashboard;
using ZB.MOM.WW.MxGateway.Server.Galaxy;
using ZB.MOM.WW.MxGateway.Server.Metrics;
using ZB.MOM.WW.MxGateway.Server.Security.Authentication;
using ZB.MOM.WW.MxGateway.Server.Security.Authorization;
using ZB.MOM.WW.MxGateway.Server.Sessions;
using ZB.MOM.WW.MxGateway.Server.Workers;

namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;

public sealed class DashboardSnapshotServiceTests
{
    /// <summary>
    /// Verifies snapshot returns empty collections and healthy status when registry is empty.
    /// </summary>
    [Fact]
    public void GetSnapshot_WhenRegistryEmpty_ReturnsEmptyOperationalState()
    {
        using GatewayMetrics metrics = new();
        DashboardSnapshotService service = CreateService(new SessionRegistry(), metrics);

        DashboardSnapshot snapshot = service.GetSnapshot();

        Assert.Empty(snapshot.Sessions);
        Assert.Empty(snapshot.Workers);
        Assert.Empty(snapshot.Faults);
        Assert.Contains(snapshot.Metrics, metric => metric.Name == "mxgateway.sessions.open" && metric.Value == 0);
        Assert.Equal("Healthy", snapshot.GatewayStatus);
        Assert.NotNull(snapshot.Configuration);
    }

    /// <summary>
    /// Verifies snapshot projects active, faulted, and closed session states with worker and metrics data.
    /// </summary>
    [Fact]
    public void GetSnapshot_ProjectsActiveAndFaultedSessionsWorkersMetricsAndFaults()
    {
        SessionRegistry registry = new();
        GatewaySession activeSession = CreateSession(
            "session-active",
            "client-one",
            DateTimeOffset.Parse("2026-04-26T10:00:00Z", CultureInfo.InvariantCulture));
        activeSession.AttachWorkerClient(new FakeWorkerClient("session-active", 1201, WorkerClientState.Ready));
        activeSession.MarkReady();
        GatewaySession faultedSession = CreateSession(
            "session-faulted",
            "client-two",
            DateTimeOffset.Parse("2026-04-26T10:01:00Z", CultureInfo.InvariantCulture));
        faultedSession.AttachWorkerClient(new FakeWorkerClient("session-faulted", 1202, WorkerClientState.Faulted));
        faultedSession.MarkFaulted("worker pipe disconnected");
        GatewaySession closedSession = CreateSession(
            "session-closed",
            "client-three",
            DateTimeOffset.Parse("2026-04-26T09:59:00Z", CultureInfo.InvariantCulture));
        closedSession.AttachWorkerClient(new FakeWorkerClient("session-closed", 1203, WorkerClientState.Closed));
        closedSession.TransitionTo(SessionState.Closed);
        registry.TryAdd(activeSession);
        registry.TryAdd(faultedSession);
        registry.TryAdd(closedSession);
        using GatewayMetrics metrics = new();
        metrics.SessionOpened();
        metrics.SessionOpened();
        metrics.CommandStarted("Register");
        metrics.CommandFailed("Register", "WorkerFaulted", TimeSpan.FromMilliseconds(7));
        metrics.EventReceived("session-active", "OnDataChange");
        metrics.Fault("WorkerFaulted");
        DashboardSnapshotService service = CreateService(registry, metrics);

        DashboardSnapshot snapshot = service.GetSnapshot();

        Assert.Equal(3, snapshot.Sessions.Count);
        Assert.Equal("session-faulted", snapshot.Sessions[0].SessionId);
        Assert.Equal(SessionState.Faulted, snapshot.Sessions[0].State);
        DashboardSessionSummary activeSummary = Assert.Single(
            snapshot.Sessions,
            session => session.SessionId == "session-active");
        Assert.Equal(1, activeSummary.EventsReceived);
        Assert.Equal(2, snapshot.Workers.Count);
        Assert.DoesNotContain(snapshot.Workers, worker => worker.SessionId == "session-closed");
        Assert.Contains(snapshot.Metrics, metric => metric.Name == "mxgateway.commands.started" && metric.Value == 1);
        Assert.Contains(
            snapshot.Metrics,
            metric => metric.Name == "mxgateway.events.received"
                && metric.Dimension == "OnDataChange"
                && metric.Value == 1);
        DashboardFaultSummary fault = Assert.Single(snapshot.Faults);
        Assert.Equal("Worker", fault.Source);
        Assert.Equal("session-faulted", fault.SessionId);
        Assert.Equal("worker pipe disconnected", fault.Message);
    }

    /// <summary>
    /// Verifies snapshot metrics include the cumulative alarm provider switch count.
    /// </summary>
    [Fact]
    public void GetSnapshot_IncludesAlarmProviderSwitchCountMetric()
    {
        SessionRegistry registry = new();
        using GatewayMetrics metrics = new();
        metrics.AlarmProviderSwitched(1, 2, AlarmProviderSwitchReason.Failover);
        DashboardSnapshotService service = CreateService(registry, metrics);

        DashboardSnapshot snapshot = service.GetSnapshot();

        Assert.Contains(
            snapshot.Metrics,
            metric => metric.Name == "mxgateway.alarms.provider_switches" && metric.Value == 1);
    }

    /// <summary>
    /// Verifies snapshot redacts sensitive values from client identity, session name, and fault messages.
    /// </summary>
    [Fact]
    public void GetSnapshot_RedactsSecretsFromSessionAndFaultFields()
    {
        SessionRegistry registry = new();
        GatewaySession session = CreateSession(
            "session-redacted",
            "Bearer mxgw_admin_super-secret",
            DateTimeOffset.Parse("2026-04-26T10:00:00Z", CultureInfo.InvariantCulture),
            clientSessionName: "password=hunter2",
            clientCorrelationId: "token=abc123");
        session.MarkFaulted("secret=credential-value");
        registry.TryAdd(session);
        using GatewayMetrics metrics = new();
        DashboardSnapshotService service = CreateService(registry, metrics);

        DashboardSnapshot snapshot = service.GetSnapshot();

        DashboardSessionSummary summary = Assert.Single(snapshot.Sessions);
        Assert.Equal("Bearer mxgw_admin_[redacted]", summary.ClientIdentity);
        Assert.Equal("[redacted]", summary.ClientSessionName);
        Assert.Equal("[redacted]", summary.ClientCorrelationId);
        Assert.Equal("[redacted]", summary.LastFault);
        Assert.Equal("[redacted]", Assert.Single(snapshot.Faults).Message);
        Assert.Equal("[redacted]", snapshot.Configuration.Authentication.PepperSecretName);
    }

    /// <summary>
    /// Verifies snapshot generation does not mutate session or worker client state.
    /// </summary>
    [Fact]
    public void GetSnapshot_DoesNotMutateSessionOrWorkerState()
    {
        SessionRegistry registry = new();
        GatewaySession session = CreateSession(
            "session-active",
            "client-one",
            DateTimeOffset.Parse("2026-04-26T10:00:00Z", CultureInfo.InvariantCulture));
        FakeWorkerClient workerClient = new("session-active", 1201, WorkerClientState.Ready);
        session.AttachWorkerClient(workerClient);
        session.MarkReady();
        registry.TryAdd(session);
        using GatewayMetrics metrics = new();
        DashboardSnapshotService service = CreateService(registry, metrics);

        service.GetSnapshot();
        service.GetSnapshot();

        Assert.Equal(1, registry.ActiveCount);
        Assert.Equal(SessionState.Ready, session.State);
        Assert.Equal(WorkerClientState.Ready, workerClient.State);
        Assert.Equal(0, workerClient.StartCount);
        Assert.Equal(0, workerClient.ShutdownCount);
        Assert.Equal(0, workerClient.KillCount);
    }

    /// <summary>
    /// Verifies snapshot respects configured limits for recent sessions and faults.
    /// </summary>
    [Fact]
    public void GetSnapshot_AppliesRecentSessionAndFaultLimits()
    {
        SessionRegistry registry = new();
        GatewaySession olderSession = CreateSession(
            "session-older",
            "client-one",
            DateTimeOffset.Parse("2026-04-26T10:00:00Z", CultureInfo.InvariantCulture));
        GatewaySession newerSession = CreateSession(
            "session-newer",
            "client-two",
            DateTimeOffset.Parse("2026-04-26T10:01:00Z", CultureInfo.InvariantCulture));
        olderSession.MarkFaulted("older fault");
        newerSession.MarkFaulted("newer fault");
        registry.TryAdd(olderSession);
        registry.TryAdd(newerSession);
        using GatewayMetrics metrics = new();
        DashboardSnapshotService service = CreateService(
            registry,
            metrics,
            new GatewayOptions
            {
                Dashboard = new DashboardOptions
                {
                    SnapshotIntervalMilliseconds = 1,
                    RecentSessionLimit = 1,
                    RecentFaultLimit = 1,
                },
            });

        DashboardSnapshot snapshot = service.GetSnapshot();

        Assert.Equal("session-newer", Assert.Single(snapshot.Sessions).SessionId);
        Assert.Equal("session-newer", Assert.Single(snapshot.Faults).SessionId);
    }

    /// <summary>
    /// Verifies snapshot projects Galaxy hierarchy cache data including templates and categories.
    /// </summary>
    [Fact]
    public void GetSnapshot_ProjectsGalaxySummaryFromHierarchyCache()
    {
        GalaxyHierarchyCacheEntry entry = GalaxyHierarchyCacheEntry.Empty with
        {
            Status = GalaxyCacheStatus.Healthy,
            Sequence = 7,
            LastQueriedAt = DateTimeOffset.Parse("2026-04-28T11:30:00Z", CultureInfo.InvariantCulture),
            LastSuccessAt = DateTimeOffset.Parse("2026-04-28T11:30:00Z", CultureInfo.InvariantCulture),
            LastDeployTime = DateTimeOffset.Parse("2026-04-28T09:00:00Z", CultureInfo.InvariantCulture),
            DashboardSummary = new DashboardGalaxySummary(
                DashboardGalaxyStatus.Healthy,
                LastQueriedAt: DateTimeOffset.Parse("2026-04-28T11:30:00Z", CultureInfo.InvariantCulture),
                LastSuccessAt: DateTimeOffset.Parse("2026-04-28T11:30:00Z", CultureInfo.InvariantCulture),
                LastDeployTime: DateTimeOffset.Parse("2026-04-28T09:00:00Z", CultureInfo.InvariantCulture),
                LastError: null,
                ObjectCount: 3,
                AreaCount: 1,
                AttributeCount: 2,
                HistorizedAttributeCount: 1,
                AlarmAttributeCount: 1,
                TopTemplates:
                [
                    new DashboardGalaxyTemplateUsage("$Pump", 2),
                    new DashboardGalaxyTemplateUsage("$Area", 1),
                ],
                ObjectCategories:
                [
                    new DashboardGalaxyCategoryCount(10, "UserDefined", 2),
                    new DashboardGalaxyCategoryCount(13, "Area", 1),
                ]),
            ObjectCount = 3,
            AreaCount = 1,
            AttributeCount = 2,
            HistorizedAttributeCount = 1,
            AlarmAttributeCount = 1,
        };
        using GatewayMetrics metrics = new();
        DashboardSnapshotService service = CreateService(
            new SessionRegistry(),
            metrics,
            galaxyHierarchyCache: new StubGalaxyHierarchyCache(entry));

        DashboardSnapshot snapshot = service.GetSnapshot();

        Assert.Equal(DashboardGalaxyStatus.Healthy, snapshot.Galaxy.Status);
        Assert.Equal(3, snapshot.Galaxy.ObjectCount);
        Assert.Equal(1, snapshot.Galaxy.AreaCount);
        Assert.Equal(2, snapshot.Galaxy.AttributeCount);
        Assert.Equal("$Pump", Assert.Single(snapshot.Galaxy.TopTemplates, t => t.TemplateName == "$Pump").TemplateName);
        Assert.Equal(2, snapshot.Galaxy.TopTemplates.First(t => t.TemplateName == "$Pump").InstanceCount);
        Assert.Contains(snapshot.Galaxy.ObjectCategories, c => c.CategoryName == "UserDefined" && c.ObjectCount == 2);
        Assert.Contains(snapshot.Galaxy.ObjectCategories, c => c.CategoryName == "Area" && c.ObjectCount == 1);
    }

    /// <summary>
    /// Verifies snapshot watcher cancels cleanly when subscriber cancels.
    /// </summary>
    [Fact]
    public void GetSnapshot_DoesNotSynchronouslyListApiKeys()
    {
        using GatewayMetrics metrics = new();
        CountingApiKeyAdminStore apiKeyAdminStore = new();
        DashboardSnapshotService service = CreateService(
            new SessionRegistry(),
            metrics,
            apiKeyAdminStore: apiKeyAdminStore);

        DashboardSnapshot snapshot = service.GetSnapshot();

        Assert.Empty(snapshot.ApiKeys);
        Assert.Equal(0, apiKeyAdminStore.ListCount);
    }

    /// <summary>Verifies that snapshot service refreshes API key summaries before each snapshot.</summary>
    [Fact]
    public async Task WatchSnapshotsAsync_RefreshesApiKeySummariesBeforeSnapshot()
    {
        using GatewayMetrics metrics = new();
        CountingApiKeyAdminStore apiKeyAdminStore = new(
            new ApiKeyListItem(
                KeyId: "operator01",
                KeyPrefix: "mxgw",
                DisplayName: "Operator",
                Scopes: new HashSet<string>([GatewayScopes.MetadataRead], StringComparer.Ordinal),
                ConstraintsJson: ApiKeyConstraintSerializer.Serialize(ApiKeyConstraints.Empty with
                {
                    BrowseSubtrees = ["Area1/*"],
                }),
                CreatedUtc: DateTimeOffset.Parse("2026-04-28T12:00:00Z", CultureInfo.InvariantCulture),
                LastUsedUtc: null,
                RevokedUtc: null));
        DashboardSnapshotService service = CreateService(
            new SessionRegistry(),
            metrics,
            apiKeyAdminStore: apiKeyAdminStore);
        using CancellationTokenSource cancellation = new(TimeSpan.FromSeconds(2));
        await using IAsyncEnumerator<DashboardSnapshot> enumerator = service
            .WatchSnapshotsAsync(cancellation.Token)
            .GetAsyncEnumerator(cancellation.Token);

        Assert.True(await enumerator.MoveNextAsync());
        DashboardSnapshot snapshot = enumerator.Current;

        DashboardApiKeySummary key = Assert.Single(snapshot.ApiKeys);
        Assert.Equal("operator01", key.KeyId);
        Assert.Equal(["Area1/*"], key.Constraints.BrowseSubtrees);
        Assert.Equal(1, apiKeyAdminStore.ListCount);
    }

    /// <summary>Verifies that snapshot service reuses previous summaries when API key refresh fails.</summary>
    [Fact]
    public async Task WatchSnapshotsAsync_WhenApiKeyRefreshFails_ReusesPreviousSummaries()
    {
        using GatewayMetrics metrics = new();
        SequencedApiKeyAdminStore apiKeyAdminStore = new(
            new ApiKeyListItem(
                KeyId: "operator01",
                KeyPrefix: "mxgw",
                DisplayName: "Operator",
                Scopes: new HashSet<string>([GatewayScopes.MetadataRead], StringComparer.Ordinal),
                ConstraintsJson: null,
                CreatedUtc: DateTimeOffset.Parse("2026-04-28T12:00:00Z", CultureInfo.InvariantCulture),
                LastUsedUtc: null,
                RevokedUtc: null));
        DashboardSnapshotService service = CreateService(
            new SessionRegistry(),
            metrics,
            new GatewayOptions
            {
                Dashboard = new DashboardOptions
                {
                    SnapshotIntervalMilliseconds = 1,
                },
            },
            apiKeyAdminStore: apiKeyAdminStore);
        using CancellationTokenSource cancellation = new(TimeSpan.FromSeconds(2));
        await using IAsyncEnumerator<DashboardSnapshot> enumerator = service
            .WatchSnapshotsAsync(cancellation.Token)
            .GetAsyncEnumerator(cancellation.Token);

        Assert.True(await enumerator.MoveNextAsync());
        DashboardSnapshot first = enumerator.Current;
        apiKeyAdminStore.FailNext = true;

        Assert.True(await enumerator.MoveNextAsync());
        DashboardSnapshot second = enumerator.Current;

        Assert.Equal("operator01", Assert.Single(first.ApiKeys).KeyId);
        Assert.Equal("operator01", Assert.Single(second.ApiKeys).KeyId);
        Assert.Equal(2, apiKeyAdminStore.ListCount);
    }

    /// <summary>Verifies that snapshot service disposes cleanly when subscriber cancels.</summary>
    [Fact]
    public async Task WatchSnapshotsAsync_WhenSubscriberCancels_DisposesCleanly()
    {
        using GatewayMetrics metrics = new();
        DashboardSnapshotService service = CreateService(
            new SessionRegistry(),
            metrics,
            new GatewayOptions
            {
                Dashboard = new DashboardOptions
                {
                    SnapshotIntervalMilliseconds = 1,
                },
            });
        using CancellationTokenSource cancellation = new();
        await using IAsyncEnumerator<DashboardSnapshot> enumerator = service
            .WatchSnapshotsAsync(cancellation.Token)
            .GetAsyncEnumerator();

        Assert.True(await enumerator.MoveNextAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(1)));
        await cancellation.CancelAsync();
        bool hasNext = await enumerator.MoveNextAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(1));

        Assert.False(hasNext);
    }

    private static DashboardSnapshotService CreateService(
        SessionRegistry registry,
        GatewayMetrics metrics,
        GatewayOptions? options = null,
        IGalaxyHierarchyCache? galaxyHierarchyCache = null,
        IApiKeyAdminStore? apiKeyAdminStore = null)
    {
        GatewayOptions resolvedOptions = options ?? new GatewayOptions
        {
            Dashboard = new DashboardOptions
            {
                SnapshotIntervalMilliseconds = 1,
            },
        };
        GatewayConfigurationProvider configurationProvider = new(Options.Create(resolvedOptions));

        return new DashboardSnapshotService(
            registry,
            metrics,
            configurationProvider,
            galaxyHierarchyCache ?? new StubGalaxyHierarchyCache(GalaxyHierarchyCacheEntry.Empty),
            apiKeyAdminStore ?? new FakeApiKeyAdminStore(),
            Options.Create(resolvedOptions));
    }

    private sealed class StubGalaxyHierarchyCache(GalaxyHierarchyCacheEntry current) : IGalaxyHierarchyCache
    {
        /// <summary>
        /// Gets the current Galaxy hierarchy cache entry.
        /// </summary>
        public GalaxyHierarchyCacheEntry Current { get; } = current;

        /// <summary>
        /// Refreshes the cache asynchronously.
        /// </summary>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>Completed task.</returns>
        public Task RefreshAsync(CancellationToken cancellationToken) => Task.CompletedTask;

        /// <summary>
        /// Waits for the first cache load asynchronously.
        /// </summary>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>Completed task.</returns>
        public Task WaitForFirstLoadAsync(CancellationToken cancellationToken) => Task.CompletedTask;
    }

    private class FakeApiKeyAdminStore : IApiKeyAdminStore
    {
        /// <inheritdoc />
        public Task CreateAsync(ApiKeyRecord record, CancellationToken ct)
        {
            return Task.CompletedTask;
        }

        /// <inheritdoc />
        public virtual Task<IReadOnlyList<ApiKeyListItem>> ListAsync(CancellationToken ct)
        {
            return Task.FromResult<IReadOnlyList<ApiKeyListItem>>([]);
        }

        /// <inheritdoc />
        public Task<bool> RevokeAsync(string keyId, DateTimeOffset whenUtc, CancellationToken ct)
        {
            return Task.FromResult(false);
        }

        /// <inheritdoc />
        public Task<bool> RotateAsync(string keyId, byte[] newSecretHash, CancellationToken ct)
        {
            return Task.FromResult(false);
        }

        /// <inheritdoc />
        public Task<bool> DeleteAsync(string keyId, CancellationToken ct)
        {
            return Task.FromResult(false);
        }
    }

    private class CountingApiKeyAdminStore(params ApiKeyListItem[] records) : FakeApiKeyAdminStore
    {
        /// <summary>Gets the count of list operations performed.</summary>
        public int ListCount { get; protected set; }

        /// <inheritdoc />
        public override Task<IReadOnlyList<ApiKeyListItem>> ListAsync(CancellationToken ct)
        {
            ListCount++;
            return Task.FromResult<IReadOnlyList<ApiKeyListItem>>(records);
        }
    }

    private sealed class SequencedApiKeyAdminStore(ApiKeyListItem record) : CountingApiKeyAdminStore(record)
    {
        /// <summary>Gets or sets a value indicating whether the next list operation should fail.</summary>
        public bool FailNext { get; set; }

        /// <inheritdoc />
        public override Task<IReadOnlyList<ApiKeyListItem>> ListAsync(CancellationToken ct)
        {
            if (FailNext)
            {
                FailNext = false;
                ListCount++;
                throw new InvalidOperationException("Simulated SQLite failure.");
            }

            return base.ListAsync(ct);
        }
    }

    private static GatewaySession CreateSession(
        string sessionId,
        string? clientIdentity,
        DateTimeOffset openedAt,
        string? clientSessionName = "test-session",
        string? clientCorrelationId = "client-correlation")
    {
        return new GatewaySession(
            sessionId,
            "mxaccess",
            $"mxaccess-gateway-1-{sessionId}",
            "nonce",
            clientIdentity,
            clientSessionName,
            clientCorrelationId,
            TimeSpan.FromSeconds(30),
            TimeSpan.FromSeconds(5),
            TimeSpan.FromSeconds(5),
            openedAt);
    }

    private sealed class FakeWorkerClient(
        string sessionId,
        int? processId,
        WorkerClientState state) : IWorkerClient
    {
        /// <summary>
        /// Gets the session identifier.
        /// </summary>
        public string SessionId { get; } = sessionId;

        /// <summary>
        /// Gets the process identifier.
        /// </summary>
        public int? ProcessId { get; } = processId;

        /// <summary>
        /// Gets the current worker client state.
        /// </summary>
        public WorkerClientState State { get; private set; } = state;

        /// <summary>
        /// Gets the timestamp of the last heartbeat.
        /// </summary>
        public DateTimeOffset LastHeartbeatAt { get; } = DateTimeOffset.Parse("2026-04-26T10:02:00Z", CultureInfo.InvariantCulture);

        /// <summary>
        /// Gets the count of start invocations.
        /// </summary>
        public int StartCount { get; private set; }

        /// <summary>
        /// Gets the count of shutdown invocations.
        /// </summary>
        public int ShutdownCount { get; private set; }

        /// <summary>
        /// Gets the count of kill invocations.
        /// </summary>
        public int KillCount { get; private set; }

        /// <summary>
        /// Starts the worker client asynchronously.
        /// </summary>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>Completed task.</returns>
        public Task StartAsync(CancellationToken cancellationToken)
        {
            StartCount++;
            return Task.CompletedTask;
        }

        /// <summary>
        /// Invokes a worker command asynchronously.
        /// </summary>
        /// <param name="command">The command to invoke.</param>
        /// <param name="timeout">Command timeout.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>Command reply.</returns>
        public Task<WorkerCommandReply> InvokeAsync(
            WorkerCommand command,
            TimeSpan timeout,
            CancellationToken cancellationToken)
        {
            return Task.FromResult(new WorkerCommandReply());
        }

        /// <summary>
        /// Reads events from the worker asynchronously.
        /// </summary>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>Async enumerable of worker events.</returns>
        public async IAsyncEnumerable<WorkerEvent> ReadEventsAsync(
            [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
        {
            await Task.CompletedTask;
            yield break;
        }

        /// <summary>
        /// Shuts down the worker client asynchronously.
        /// </summary>
        /// <param name="timeout">Shutdown timeout.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>Completed task.</returns>
        public Task ShutdownAsync(
            TimeSpan timeout,
            CancellationToken cancellationToken)
        {
            ShutdownCount++;
            State = WorkerClientState.Closed;
            return Task.CompletedTask;
        }

        /// <summary>
        /// Terminates the worker client.
        /// </summary>
        /// <param name="reason">Reason for termination.</param>
        public void Kill(string reason)
        {
            KillCount++;
            State = WorkerClientState.Faulted;
        }

        /// <summary>
        /// Releases resources used by this worker client.
        /// </summary>
        /// <returns>Completed value task.</returns>
        public ValueTask DisposeAsync()
        {
            return ValueTask.CompletedTask;
        }
    }
}
