using EonaCat.LogStack.Core;
using EonaCat.LogStack.Flows;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EonaCat.LogStack.Flows
{
    // This file is part of the EonaCat project(s) which is released under the Apache License.
    // See the LICENSE file or go to https://EonaCat.com/License for full license details.

    public sealed class FailoverFlow : FlowBase
    {
        private readonly IFlow _primary;
        private readonly IFlow _secondary;
        private readonly TimeSpan _recoveryCheckInterval;
        private readonly int _failureThreshold;

        private IFlow _activeFlow;
        private long _consecutiveFailures;
        private DateTime _lastRecoveryCheck;
        private CancellationTokenSource _recoveryCancellation;
        private Task _recoveryTask;
        private readonly object _syncLock = new object();

        public FailoverFlow(
            IFlow primary, 
            IFlow secondary,
            TimeSpan? recoveryCheckInterval = null,
            int failureThreshold = 5)
            : base($"Failover({primary.Name})", primary.MinimumLevel)
        {
            _primary = primary ?? throw new ArgumentNullException(nameof(primary));
            _secondary = secondary ?? throw new ArgumentNullException(nameof(secondary));
            _recoveryCheckInterval = recoveryCheckInterval ?? TimeSpan.FromSeconds(10);
            _failureThreshold = failureThreshold;
            _activeFlow = _primary;
            _consecutiveFailures = 0;
            _lastRecoveryCheck = DateTime.UtcNow;
            _recoveryCancellation = new CancellationTokenSource();
            _recoveryTask = StartRecoveryDetectionAsync();
        }

        public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
        {
            var result = await _activeFlow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);

            if (result == WriteResult.Success)
            {
                // Reset failure counter on success
                Interlocked.Exchange(ref _consecutiveFailures, 0);
                return result;
            }

            // Only failover to secondary if using primary
            if (_activeFlow == _primary)
            {
                var failures = Interlocked.Increment(ref _consecutiveFailures);

                if (failures >= _failureThreshold)
                {
                    lock (_syncLock)
                    {
                        // Double-check inside lock
                        if (_activeFlow == _primary && Interlocked.Read(ref _consecutiveFailures) >= _failureThreshold)
                        {
                            _activeFlow = _secondary;
                            Interlocked.Exchange(ref _consecutiveFailures, 0);
                        }
                    }
                }
            }

            return result;
        }

        public override async Task FlushAsync(CancellationToken cancellationToken = default)
        {
            await _primary.FlushAsync(cancellationToken);
            await _secondary.FlushAsync(cancellationToken);
        }

        public override async ValueTask DisposeAsync()
        {
            _recoveryCancellation?.Cancel();

            if (_recoveryTask != null)
            {
                try
                {
                    await _recoveryTask.ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    // Expected when cancelling the recovery task
                }
            }

            _recoveryCancellation?.Dispose();
            await _primary.DisposeAsync();
            await _secondary.DisposeAsync();
        }

        private async Task StartRecoveryDetectionAsync()
        {
            try
            {
                while (!_recoveryCancellation.Token.IsCancellationRequested)
                {
                    await Task.Delay(_recoveryCheckInterval, _recoveryCancellation.Token).ConfigureAwait(false);

                    // Only check recovery if we've switched to secondary
                    if (_activeFlow == _secondary)
                    {
                        await CheckPrimaryRecoveryAsync().ConfigureAwait(false);
                    }
                }
            }
            catch (OperationCanceledException)
            {
                // Expected when shutting down
            }
        }

        private async Task CheckPrimaryRecoveryAsync()
        {
            try
            {
                // Try to write a minimal test log to check if primary is healthy
                var testEvent = new LogEvent
                {
                    Level = LogLevel.Trace,
                    Message = new ReadOnlyMemory<char>("recovery_check".ToCharArray()),
                    Timestamp = DateTime.UtcNow.Ticks,
                    Category = "RecoveryDetection",
                    ThreadId = Thread.CurrentThread.ManagedThreadId
                };

                var result = await _primary.BlastAsync(testEvent, _recoveryCancellation.Token).ConfigureAwait(false);

                if (result == WriteResult.Success)
                {
                    lock (_syncLock)
                    {
                        if (_activeFlow == _secondary)
                        {
                            _activeFlow = _primary;
                            Interlocked.Exchange(ref _consecutiveFailures, 0);
                            _lastRecoveryCheck = DateTime.UtcNow;
                        }
                    }
                }
            }
            catch
            {
                // Silently catch exceptions during recovery check
                // The primary is still unhealthy
            }
        }
    }
}
