// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Platform.Helpers;

namespace Microsoft.Testing.Platform.Services;

internal sealed class StopPoliciesService : IStopPoliciesService
{
    private readonly ITestApplicationCancellationTokenSource _testApplicationCancellationTokenSource;

    private readonly ConcurrentQueue<Func<int, CancellationToken, Task>> _maxFailedTestsCallbacks = new();
    private readonly ConcurrentQueue<Func<Task>> _abortCallbacks = new();
    private int _lastMaxFailedTests;

    public StopPoliciesService(ITestApplicationCancellationTokenSource testApplicationCancellationTokenSource)
    {
        _testApplicationCancellationTokenSource = testApplicationCancellationTokenSource;

#pragma warning disable VSTHRD101 // Avoid unsupported async delegates
        // Note: If cancellation already requested, Register will still invoke the callback.
        testApplicationCancellationTokenSource.CancellationToken.Register(async () => await ExecuteAbortCallbacksAsync().ConfigureAwait(false));
#pragma warning restore VSTHRD101 // Avoid unsupported async delegates
    }

    internal TestProcessRole? ProcessRole { get; set; }

    public bool IsMaxFailedTestsTriggered { get; private set; }

    public bool IsAbortTriggered { get; private set; }

    public async Task ExecuteMaxFailedTestsCallbacksAsync(int maxFailedTests, CancellationToken cancellationToken)
    {
        _lastMaxFailedTests = maxFailedTests;
        IsMaxFailedTestsTriggered = true;
        if (_maxFailedTestsCallbacks is null)
        {
            return;
        }

        foreach (Func<int, CancellationToken, Task> callback in _maxFailedTestsCallbacks)
        {
            // For now, we are fine if the callback crashed us. It shouldn't happen for our
            // current usage anyway and the APIs around this are all internal for now.
            await callback.Invoke(maxFailedTests, cancellationToken).ConfigureAwait(false);
        }
    }

    public async Task ExecuteAbortCallbacksAsync()
    {
        IsAbortTriggered = true;

        if (_abortCallbacks is null)
        {
            return;
        }

        foreach (Func<Task> callback in _abortCallbacks)
        {
            // For now, we are fine if the callback crashed us. It shouldn't happen for our
            // current usage anyway and the APIs around this are all internal for now.
            await callback.Invoke().ConfigureAwait(false);
        }
    }

    public async Task RegisterOnMaxFailedTestsCallbackAsync(Func<int, CancellationToken, Task> callback)
    {
        if (ProcessRole != TestProcessRole.TestHost)
        {
            throw ApplicationStateGuard.Unreachable();
        }

        if (IsMaxFailedTestsTriggered)
        {
            await callback(_lastMaxFailedTests, _testApplicationCancellationTokenSource.CancellationToken).ConfigureAwait(false);
        }

        _maxFailedTestsCallbacks.Enqueue(callback);
    }

    public async Task RegisterOnAbortCallbackAsync(Func<Task> callback)
    {
        if (IsAbortTriggered)
        {
            await callback().ConfigureAwait(false);
        }

        _abortCallbacks.Enqueue(callback);
    }
}
