using EonaCat.LogStack.Flows;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace EonaCat.LogStack.Chaining;

// 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.

/// <summary>
/// Implementation of ILoggerChain that enables chaining multiple logger instances together.
/// Logs propagate through the primary logger to all chained loggers, allowing multiple
/// logging backends to work in concert.
/// </summary>
public sealed class LoggerChain : ILoggerChain
{
    private readonly EonaCatLogStack _primary;
    private readonly List<EonaCatLogStack> _chainedLoggers = new();
    private readonly Dictionary<EonaCatLogStack, bool> _loggerStates = new();
    private readonly object _chainLock = new object();
    private int _disposed;

    /// <summary>
    /// Event raised when a logger is added to the chain
    /// </summary>
    public event EventHandler<LoggerChainEventArgs>? LoggerAdded;

    /// <summary>
    /// Event raised when a logger is removed from the chain
    /// </summary>
    public event EventHandler<LoggerChainEventArgs>? LoggerRemoved;

    /// <summary>
    /// Event raised when a logger fails
    /// </summary>
    public event EventHandler<LoggerChainEventArgs>? LoggerFailed;

    public string Name { get; set; }
    public EonaCatLogStack Primary => _primary;
    public IReadOnlyList<EonaCatLogStack> ChainedLoggers
    {
        get
        {
            lock (_chainLock)
            {
                return new ReadOnlyCollection<EonaCatLogStack>(_chainedLoggers.ToList());
            }
        }
    }

    public LoggerChainErrorBehavior ErrorBehavior { get; set; } = LoggerChainErrorBehavior.ContinueOnError;

    /// <summary>
    /// Creates a new logger chain with the given primary logger
    /// </summary>
    /// <param name="primary">The primary logger that forms the base of the chain</param>
    /// <param name="name">The name of the logger chain</param>
    public LoggerChain(EonaCatLogStack primary, string name = "LoggerChain")
    {
        _primary = primary ?? throw new ArgumentNullException(nameof(primary));
        Name = name ?? throw new ArgumentNullException(nameof(name));
    }

    /// <summary>
    /// Adds a logger to the chain
    /// </summary>
    public ILoggerChain Add(EonaCatLogStack logger, bool chainAsFlow = true)
    {
        ThrowIfDisposed();

        if (logger == null)
            throw new ArgumentNullException(nameof(logger));

        if (logger == _primary)
            throw new InvalidOperationException("Cannot chain the primary logger to itself");

        lock (_chainLock)
        {
            if (_chainedLoggers.Contains(logger))
                return this; // Already in chain

            _chainedLoggers.Add(logger);
            _loggerStates[logger] = true; // Enabled by default

            if (chainAsFlow)
            {
                // Add a delegating flow to the primary logger that forwards to this logger
                var delegatingFlow = new DelegatingLoggerFlow(logger);
                _primary.AddFlow(delegatingFlow);
            }
        }

        LoggerAdded?.Invoke(this, new LoggerChainEventArgs
        {
            Logger = logger,
            Message = $"Logger added to chain: {logger.GetType().Name}"
        });

        return this;
    }

    /// <summary>
    /// Adds multiple loggers to the chain
    /// </summary>
    public ILoggerChain AddRange(params EonaCatLogStack[] loggers)
    {
        if (loggers == null)
            throw new ArgumentNullException(nameof(loggers));

        foreach (var logger in loggers)
        {
            Add(logger);
        }

        return this;
    }

    /// <summary>
    /// Removes a logger from the chain
    /// </summary>
    public bool Remove(EonaCatLogStack logger)
    {
        ThrowIfDisposed();

        if (logger == null)
            return false;

        lock (_chainLock)
        {
            bool removed = _chainedLoggers.Remove(logger);
            if (removed)
            {
                _loggerStates.Remove(logger);
                LoggerRemoved?.Invoke(this, new LoggerChainEventArgs
                {
                    Logger = logger,
                    Message = $"Logger removed from chain: {logger.GetType().Name}"
                });
            }

            return removed;
        }
    }

    /// <summary>
    /// Clears all chained loggers (keeps the primary)
    /// </summary>
    public void Clear()
    {
        ThrowIfDisposed();

        lock (_chainLock)
        {
            _chainedLoggers.Clear();
            _loggerStates.Clear();
        }
    }

    /// <summary>
    /// Flushes all loggers in the chain
    /// </summary>
    public async Task FlushAllAsync(CancellationToken cancellationToken = default)
    {
        ThrowIfDisposed();

        List<EonaCatLogStack> loggers;
        lock (_chainLock)
        {
            loggers = new List<EonaCatLogStack>(_chainedLoggers);
        }

        // Flush primary logger
        await FlushLoggerAsync(_primary, cancellationToken).ConfigureAwait(false);

        // Flush all chained loggers
        foreach (var logger in loggers)
        {
            if (IsLoggerEnabled(logger))
            {
                await FlushLoggerAsync(logger, cancellationToken).ConfigureAwait(false);
            }
        }
    }

    /// <summary>
    /// Disables a logger in the chain temporarily
    /// </summary>
    public void DisableLogger(EonaCatLogStack logger)
    {
        if (logger == null)
            return;

        lock (_chainLock)
        {
            if (_loggerStates.ContainsKey(logger))
            {
                _loggerStates[logger] = false;
            }
        }
    }

    /// <summary>
    /// Enables a disabled logger
    /// </summary>
    public void EnableLogger(EonaCatLogStack logger)
    {
        if (logger == null)
            return;

        lock (_chainLock)
        {
            if (_loggerStates.ContainsKey(logger))
            {
                _loggerStates[logger] = true;
            }
        }
    }

    /// <summary>
    /// Gets whether a logger is enabled
    /// </summary>
    public bool IsLoggerEnabled(EonaCatLogStack logger)
    {
        if (logger == null)
            return false;

        lock (_chainLock)
        {
            if (_loggerStates.TryGetValue(logger, out var enabled))
            {
                return enabled;
            }

            return false;
        }
    }

    /// <summary>
    /// Gets a snapshot of the current chain state
    /// </summary>
    public LoggerChainSnapshot GetSnapshot()
    {
        lock (_chainLock)
        {
            return new LoggerChainSnapshot
            {
                Name = Name,
                PrimaryLoggerType = Primary?.GetType().Name,
                ChainedLoggerCount = _chainedLoggers.Count,
                EnabledLoggerCount = _loggerStates.Values.Count(s => s),
                DisabledLoggerCount = _loggerStates.Values.Count(s => !s),
                ErrorBehavior = ErrorBehavior,
                ChainedLoggers = _chainedLoggers.Select(l => new LoggerChainLoggerInfo
                {
                    LoggerType = l.GetType().Name,
                    IsEnabled = _loggerStates.TryGetValue(l, out var enabled) ? enabled : false
                }).ToList()
            };
        }
    }

    private async Task FlushLoggerAsync(EonaCatLogStack logger, CancellationToken cancellationToken)
    {
        try
        {
            if (logger is IAsyncDisposable disposable)
            {
                // Note: We flush through the logger's internal mechanisms
                // This is handled by the logger's own async pipeline
                await Task.CompletedTask.ConfigureAwait(false);
            }
        }
        catch (Exception ex)
        {
            LoggerFailed?.Invoke(this, new LoggerChainEventArgs
            {
                Logger = logger,
                Message = "Failed to flush logger",
                Exception = ex
            });

            if (ErrorBehavior == LoggerChainErrorBehavior.StopOnError)
                throw;
        }
    }

    private void ThrowIfDisposed()
    {
        if (Interlocked.CompareExchange(ref _disposed, 0, 0) != 0)
            throw new ObjectDisposedException(nameof(LoggerChain));
    }

    public async ValueTask DisposeAsync()
    {
        if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
            return;

        // Flush all loggers before disposing
        await FlushAllAsync().ConfigureAwait(false);

        lock (_chainLock)
        {
            _chainedLoggers.Clear();
            _loggerStates.Clear();
        }

        GC.SuppressFinalize(this);
    }
}

/// <summary>
/// Represents a snapshot of the logger chain state
/// </summary>
public sealed class LoggerChainSnapshot
{
    /// <summary>
    /// The name of the chain
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// The type name of the primary logger
    /// </summary>
    public string PrimaryLoggerType { get; set; }

    /// <summary>
    /// Total number of chained loggers
    /// </summary>
    public int ChainedLoggerCount { get; set; }

    /// <summary>
    /// Number of enabled loggers
    /// </summary>
    public int EnabledLoggerCount { get; set; }

    /// <summary>
    /// Number of disabled loggers
    /// </summary>
    public int DisabledLoggerCount { get; set; }

    /// <summary>
    /// The error behavior mode
    /// </summary>
    public LoggerChainErrorBehavior ErrorBehavior { get; set; }

    /// <summary>
    /// Information about each chained logger
    /// </summary>
    public List<LoggerChainLoggerInfo> ChainedLoggers { get; set; } = new();
}

/// <summary>
/// Information about a single logger in the chain
/// </summary>
public sealed class LoggerChainLoggerInfo
{
    /// <summary>
    /// The type name of the logger
    /// </summary>
    public string LoggerType { get; set; }

    /// <summary>
    /// Whether the logger is currently enabled
    /// </summary>
    public bool IsEnabled { get; set; }
}
