using EonaCat.LogStack.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Channels;
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.

    /// <summary>
    /// LogFilterPresetFlow provides commonly-used filter combinations as reusable presets.
    /// Reduces boilerplate for common scenarios like error tracking, performance monitoring, etc.
    /// </summary>
    public sealed class LogFilterPresetFlow : FlowBase
    {
        private readonly Channel<LogEvent> _channel;
        private readonly IFlow _targetFlow;
        private readonly LogFilterPreset _preset;
        private Task _processingTask;

        /// <summary>
        /// Creates a LogFilterPresetFlow with a specific preset
        /// </summary>
        /// <param name="targetFlow">The flow to send filtered events to</param>
        /// <param name="preset">The filter preset to apply</param>
        /// <param name="minimumLevel">Minimum log level</param>
        public LogFilterPresetFlow(
            IFlow targetFlow,
            LogFilterPreset preset,
            LogLevel minimumLevel = LogLevel.Trace)
            : base($"FilterPreset_{preset.Name}", minimumLevel)
        {
            _targetFlow = targetFlow ?? throw new ArgumentNullException(nameof(targetFlow));
            _preset = preset ?? throw new ArgumentNullException(nameof(preset));
            _channel = Channel.CreateUnbounded<LogEvent>(new UnboundedChannelOptions 
            { 
                SingleWriter = false,
                SingleReader = true
            });

            StartProcessing();
        }

        private void StartProcessing()
        {
            _processingTask = ProcessChannelAsync();
        }

        private async Task ProcessChannelAsync()
        {
            try
            {
                await foreach (var logEvent in _channel.Reader.ReadAllAsync())
                {
                    var filtered = _preset.Filter(logEvent);
                    if (filtered)
                    {
                        await _targetFlow.BlastAsync(logEvent);
                    }
                }
            }
            catch (OperationCanceledException)
            {
                // Expected when channel is closed
            }
        }

        public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
        {
            if (!IsEnabled || !IsLogLevelEnabled(logEvent))
                return WriteResult.Success;

            try
            {
                await _channel.Writer.WriteAsync(logEvent, cancellationToken);
                return WriteResult.Success;
            }
            catch (Exception ex)
            {
                return WriteResult.Failed;
            }
        }

        public override async Task<WriteResult> BlastBatchAsync(ReadOnlyMemory<LogEvent> logEvents, CancellationToken cancellationToken = default)
        {
            var events = logEvents.ToArray(); // Copy to array to avoid span issues across await
            try
            {
                foreach (var logEvent in events)
                {
                    if (IsLogLevelEnabled(logEvent))
                    {
                        await _channel.Writer.WriteAsync(logEvent, cancellationToken);
                    }
                }
                return WriteResult.Success;
            }
            catch (Exception ex)
            {
                return WriteResult.Failed;
            }
        }

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

        public override async ValueTask DisposeAsync()
        {
            _channel.Writer.TryComplete();
            if (_processingTask != null)
                await _processingTask;
            await _targetFlow.DisposeAsync();
            await base.DisposeAsync();
        }
    }

    /// <summary>
    /// Base class for filter presets
    /// </summary>
    public abstract class LogFilterPreset
    {
        public string Name { get; set; }

        protected LogFilterPreset(string name)
        {
            Name = name ?? "Custom";
        }

        public abstract bool Filter(LogEvent logEvent);
    }

    /// <summary>
    /// Preset: Only errors and exceptions
    /// </summary>
    public class ErrorOnlyPreset : LogFilterPreset
    {
        public ErrorOnlyPreset() : base("ErrorOnly") { }

        public override bool Filter(LogEvent logEvent)
        {
            return logEvent.Level >= LogLevel.Error || logEvent.Exception != null;
        }
    }

    /// <summary>
    /// Preset: Errors with full stack traces
    /// </summary>
    public class ErrorWithStackTracePreset : LogFilterPreset
    {
        public ErrorWithStackTracePreset() : base("ErrorWithStackTrace") { }

        public override bool Filter(LogEvent logEvent)
        {
            return (logEvent.Level >= LogLevel.Error || logEvent.Exception != null) &&
                   logEvent.Exception?.StackTrace != null;
        }
    }

    /// <summary>
    /// Preset: Performance-related logs (duration, latency, timeout)
    /// </summary>
    public class PerformanceLogsPreset : LogFilterPreset
    {
        private readonly string[] _keywords = { "duration", "latency", "timeout", "slow", "performance", "elapsed", "ms" };

        public PerformanceLogsPreset() : base("PerformanceLogs") { }

        public override bool Filter(LogEvent logEvent)
        {
            var message = logEvent.Message.ToString();
            return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase)) ||
                   (logEvent.Properties != null && logEvent.Properties.Keys.Any(k => k.Contains("duration", StringComparison.OrdinalIgnoreCase)));
        }
    }

    /// <summary>
    /// Preset: Authentication and security-related logs
    /// </summary>
    public class SecurityLogsPreset : LogFilterPreset
    {
        private readonly string[] _keywords = { "auth", "security", "permission", "token", "password", "encrypt", "decrypt", "unauthorized", "forbidden" };

        public SecurityLogsPreset() : base("SecurityLogs") { }

        public override bool Filter(LogEvent logEvent)
        {
            var message = logEvent.Message.ToString();
            return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase));
        }
    }

    /// <summary>
    /// Preset: Health check and monitoring logs
    /// </summary>
    public class HealthCheckPreset : LogFilterPreset
    {
        private readonly string[] _keywords = { "health", "check", "monitor", "alive", "heartbeat", "ping", "status", "ready" };

        public HealthCheckPreset() : base("HealthCheck") { }

        public override bool Filter(LogEvent logEvent)
        {
            var message = logEvent.Message.ToString();
            var category = logEvent.Category ?? string.Empty;
            return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
                                       category.Contains(kw, StringComparison.OrdinalIgnoreCase));
        }
    }

    /// <summary>
    /// Preset: Data access and database-related logs
    /// </summary>
    public class DatabaseLogsPreset : LogFilterPreset
    {
        private readonly string[] _keywords = { "database", "sql", "query", "transaction", "connection", "db:", "entity", "datacontext" };

        public DatabaseLogsPreset() : base("DatabaseLogs") { }

        public override bool Filter(LogEvent logEvent)
        {
            var message = logEvent.Message.ToString();
            var category = logEvent.Category ?? string.Empty;
            return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
                                       category.Contains(kw, StringComparison.OrdinalIgnoreCase));
        }
    }

    /// <summary>
    /// Preset: Business logic and domain events
    /// </summary>
    public class DomainEventsPreset : LogFilterPreset
    {
        private readonly string[] _keywords = { "event", "domain", "aggregate", "business", "order", "payment", "transaction" };

        public DomainEventsPreset() : base("DomainEvents") { }

        public override bool Filter(LogEvent logEvent)
        {
            var message = logEvent.Message.ToString();
            var category = logEvent.Category ?? string.Empty;
            return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
                                       category.Contains(kw, StringComparison.OrdinalIgnoreCase));
        }
    }

    /// <summary>
    /// Preset: External service calls and integrations
    /// </summary>
    public class ExternalServicePreset : LogFilterPreset
    {
        private readonly string[] _keywords = { "http", "api", "external", "service", "remote", "call", "webhook", "webhook", "third-party" };

        public ExternalServicePreset() : base("ExternalService") { }

        public override bool Filter(LogEvent logEvent)
        {
            var message = logEvent.Message.ToString();
            var category = logEvent.Category ?? string.Empty;
            return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
                                       category.Contains(kw, StringComparison.OrdinalIgnoreCase));
        }
    }

    /// <summary>
    /// Preset: Critical warnings and errors (anything that might need attention)
    /// </summary>
    public class CriticalAlertsPreset : LogFilterPreset
    {
        public CriticalAlertsPreset() : base("CriticalAlerts") { }

        public override bool Filter(LogEvent logEvent)
        {
            return logEvent.Level >= LogLevel.Warning ||
                   logEvent.Exception != null ||
                   (logEvent.Properties?.ContainsKey("Alert") ?? false);
        }
    }

    /// <summary>
    /// Preset: Development and debugging logs
    /// </summary>
    public class DebugLogsPreset : LogFilterPreset
    {
        private readonly string[] _keywords = { "debug", "trace", "verbose", "diagnostic", "test", "dev" };

        public DebugLogsPreset() : base("DebugLogs") { }

        public override bool Filter(LogEvent logEvent)
        {
            var msg = logEvent.Message.ToString();
            var cat = logEvent.Category ?? string.Empty;
            return logEvent.Level <= LogLevel.Debug ||
                   _keywords.Any(kw => msg.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
                                       cat.Contains(kw, StringComparison.OrdinalIgnoreCase));
        }
    }

    /// <summary>
    /// Preset: Custom filter based on predicate
    /// </summary>
    public class CustomPreset : LogFilterPreset
    {
        private readonly Func<LogEvent, bool> _predicate;

        public CustomPreset(string name, Func<LogEvent, bool> predicate) 
            : base(name)
        {
            _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
        }

        public override bool Filter(LogEvent logEvent)
        {
            return _predicate(logEvent);
        }
    }

    /// <summary>
    /// Preset Registry for managing and creating presets
    /// </summary>
    public static class LogFilterPresetRegistry
    {
        private static readonly Dictionary<string, LogFilterPreset> _presets = new()
        {
            ["ErrorOnly"] = new ErrorOnlyPreset(),
            ["ErrorWithStackTrace"] = new ErrorWithStackTracePreset(),
            ["Performance"] = new PerformanceLogsPreset(),
            ["Security"] = new SecurityLogsPreset(),
            ["HealthCheck"] = new HealthCheckPreset(),
            ["Database"] = new DatabaseLogsPreset(),
            ["DomainEvents"] = new DomainEventsPreset(),
            ["ExternalService"] = new ExternalServicePreset(),
            ["CriticalAlerts"] = new CriticalAlertsPreset(),
            ["Debug"] = new DebugLogsPreset()
        };

        public static LogFilterPreset? Get(string presetName)
        {
            return _presets.TryGetValue(presetName, out var preset) ? preset : null;
        }

        public static IEnumerable<string> GetAvailablePresets()
        {
            return _presets.Keys;
        }

        public static void Register(string name, LogFilterPreset preset)
        {
            _presets[name] = preset;
        }
    }
}
