using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.GalaxyRepository;

namespace ZB.MOM.WW.GalaxyRepository.Tests;

/// <summary>
///     Server-005 regression: the initial <c>RefreshAsync</c> call in
///     <see cref="GalaxyHierarchyRefreshService"/> must not let a transient,
///     non-cancellation first-load failure (e.g. a <see cref="TimeoutException"/>
///     or <see cref="System.ComponentModel.Win32Exception"/> from connection
///     establishment) escape and fault the host <c>BackgroundService</c>.
/// </summary>
public sealed class GalaxyHierarchyRefreshServiceTests
{
    /// <summary>Verifies that the background service does not fault when the first refresh throws a non-cancellation exception.</summary>
    [Fact]
    public async Task ExecuteAsync_WhenFirstRefreshThrowsNonCancellationException_DoesNotFaultBackgroundService()
    {
        ThrowingCache cache = new(new TimeoutException("connection establishment timed out"));
        GalaxyHierarchyRefreshService service = CreateService(cache);

        using CancellationTokenSource cts = new();

        await service.StartAsync(cts.Token);

        // Wait until the first RefreshAsync has actually been attempted (and
        // thrown) before cancelling, so cancellation cannot race ahead of the
        // first-load path under test — this is what made the test flaky under
        // parallel load.
        await cache.FirstRefreshAttempted.WaitAsync(TimeSpan.FromSeconds(10));

        await cts.CancelAsync();

        // The background loop must have stopped cleanly: ExecuteTask reaches a
        // terminal state that is not Faulted (RanToCompletion or Canceled)
        // rather than faulting on the first refresh. WhenAny is used so a
        // Canceled task does not rethrow before the IsFaulted assertion.
        Task? executeTask = service.ExecuteTask;
        Assert.NotNull(executeTask);
        Task completed = await Task.WhenAny(executeTask, Task.Delay(TimeSpan.FromSeconds(10)));
        Assert.Same(executeTask, completed);
        Assert.False(executeTask.IsFaulted);
        Assert.Equal(1, cache.RefreshCallCount);

        await service.StopAsync(CancellationToken.None);
    }

    private static GalaxyHierarchyRefreshService CreateService(IGalaxyHierarchyCache cache)
    {
        GalaxyRepositoryOptions options = new()
        {
            DashboardRefreshIntervalSeconds = 3600,
        };
        return new GalaxyHierarchyRefreshService(
            cache,
            Options.Create(options),
            NullLogger<GalaxyHierarchyRefreshService>.Instance);
    }

    private sealed class ThrowingCache(Exception toThrow) : IGalaxyHierarchyCache
    {
        private readonly TaskCompletionSource firstRefreshAttempted =
            new(TaskCreationOptions.RunContinuationsAsynchronously);

        /// <summary>Gets the number of refresh calls.</summary>
        public int RefreshCallCount { get; private set; }

        /// <summary>Gets a task that completes once refresh has been invoked at least once.</summary>
        public Task FirstRefreshAttempted => firstRefreshAttempted.Task;

        /// <summary>Gets the current cache entry.</summary>
        public GalaxyHierarchyCacheEntry Current => GalaxyHierarchyCacheEntry.Empty;

        /// <summary>Refreshes the cache asynchronously and throws the configured exception.</summary>
        /// <param name="cancellationToken">Token to observe for cancellation.</param>
        public Task RefreshAsync(CancellationToken cancellationToken)
        {
            RefreshCallCount++;
            firstRefreshAttempted.TrySetResult();
            throw toThrow;
        }

        /// <summary>Waits for the first load and completes immediately.</summary>
        /// <param name="cancellationToken">Token to observe for cancellation.</param>
        public Task WaitForFirstLoadAsync(CancellationToken cancellationToken) => Task.CompletedTask;
    }
}
