using MBN_STOCK_WEBVIEW.Infrastructure;
using MBN_STOCK_WEBVIEW.Infrastructure.Diagnostics;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.LegacyParityApp;

public sealed partial class MainWindow
{
    private static readonly TimeSpan DatabaseHealthPollingInterval =
        TimeSpan.FromSeconds(10);

    private readonly SemaphoreSlim _databaseActivityGate = new(1, 1);
    private readonly INativeOperatorLogWriter _databaseHealthLogWriter =
        new NativeOperatorLogWriter();
    private readonly NativeOperatorLogTransitionTracker _databaseHealthTransitions =
        new();
    private int _databaseActivityPriorityWaiterCount;
    private int _oracleDatabaseHealthState = -1;
    private int _mariaDatabaseHealthState = -1;
    private CancellationTokenSource? _databaseHealthCheckCancellation;
    private Task? _databaseHealthMonitorTask;

    private void StartDatabaseHealthMonitor()
    {
        _databaseHealthMonitorTask ??=
            MonitorDatabaseHealthAsync(_lifetimeCancellation.Token);
    }

    private async Task MonitorDatabaseHealthAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                // WinForms timerAliveChecker first fired after its 10-second interval.
                await Task.Delay(DatabaseHealthPollingInterval, cancellationToken)
                    .ConfigureAwait(false);
            }
            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
            {
                return;
            }

            try
            {
                await CheckAndLogDatabaseHealthAsync(cancellationToken)
                    .ConfigureAwait(false);
            }
            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
            {
                return;
            }
        }
    }

    private async Task CheckAndLogDatabaseHealthAsync(CancellationToken cancellationToken)
    {
        if (Volatile.Read(ref _playoutBusy) != 0 ||
            Volatile.Read(ref _databaseActivityPriorityWaiterCount) != 0 ||
            !await _databaseActivityGate.WaitAsync(0, cancellationToken)
                .ConfigureAwait(false))
        {
            return;
        }

        CancellationTokenSource? healthCancellation = null;
        try
        {
            if (Volatile.Read(ref _playoutBusy) != 0)
            {
                return;
            }

            if (_databaseRuntime is null)
            {
                ObserveDatabaseHealthState(
                    DataSourceKind.Oracle,
                    DatabaseHealthState.NotConfigured);
                ObserveDatabaseHealthState(
                    DataSourceKind.MariaDb,
                    DatabaseHealthState.NotConfigured);
                return;
            }

            healthCancellation = CancellationTokenSource.CreateLinkedTokenSource(
                cancellationToken);
            Interlocked.Exchange(
                ref _databaseHealthCheckCancellation,
                healthCancellation);

            // Close the race where an operator command starts after this monitor
            // acquired the activity gate but before the provider probes begin.
            if (Volatile.Read(ref _playoutBusy) != 0 ||
                Volatile.Read(ref _databaseActivityPriorityWaiterCount) != 0)
            {
                healthCancellation.Cancel();
                return;
            }

            var statuses = await _databaseRuntime.HealthService
                .CheckAllAsync(healthCancellation.Token)
                .ConfigureAwait(false);
            foreach (var status in statuses)
            {
                ObserveDatabaseHealthState(status.Source, status.State);
            }
        }
        catch (OperationCanceledException) when (
            cancellationToken.IsCancellationRequested ||
            healthCancellation?.IsCancellationRequested == true)
        {
            // Shutdown and operator work both have priority over diagnostics.
        }
        catch
        {
            ObserveDatabaseHealthState(
                DataSourceKind.Oracle,
                DatabaseHealthState.Unhealthy);
            ObserveDatabaseHealthState(
                DataSourceKind.MariaDb,
                DatabaseHealthState.Unhealthy);
        }
        finally
        {
            if (healthCancellation is not null)
            {
                Interlocked.CompareExchange(
                    ref _databaseHealthCheckCancellation,
                    null,
                    healthCancellation);
                healthCancellation.Dispose();
            }

            _databaseActivityGate.Release();
        }
    }

    private async Task AcquirePriorityDatabaseActivityAsync(
        CancellationToken cancellationToken)
    {
        // Signal priority before inspecting the active probe. This closes both
        // sides of the acquisition/publish race:
        //  * a probe that has not acquired the gate sees the waiter and yields;
        //  * a probe that owns the gate either sees the waiter after publishing
        //    its CTS or is cancelled through that already-published CTS.
        Interlocked.Increment(ref _databaseActivityPriorityWaiterCount);
        try
        {
            CancelActiveDatabaseHealthCheck();
            await _databaseActivityGate.WaitAsync(cancellationToken)
                .ConfigureAwait(false);
        }
        finally
        {
            Interlocked.Decrement(ref _databaseActivityPriorityWaiterCount);
        }
    }

    private void CancelActiveDatabaseHealthCheck()
    {
        var healthCancellation = Volatile.Read(ref _databaseHealthCheckCancellation);
        try
        {
            healthCancellation?.Cancel();
        }
        catch (ObjectDisposedException)
        {
            // Probe completion won the race after the reference was read.
        }
    }

    private void ObserveDatabaseHealthState(
        DataSourceKind source,
        DatabaseHealthState state)
    {
        ref int currentState = ref (source == DataSourceKind.Oracle
            ? ref _oracleDatabaseHealthState
            : ref _mariaDatabaseHealthState);
        var previous = Interlocked.Exchange(ref currentState, (int)state);
        if (previous != (int)state)
        {
            QueueOperatorState();
        }

        if (!_databaseHealthTransitions.TryObserveDatabaseState(
                source,
                state,
                out var record))
        {
            return;
        }

        try
        {
            _ = _databaseHealthLogWriter.TryWrite(record);
        }
        catch
        {
            // Logging is best effort and cannot affect DB or playout work.
        }
    }
}
