using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using System.Threading;
using System.Threading.Tasks;
using EonaCat.Logger.Managers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace EonaCat.Logger.EonaCatCoreLogger.Internal;
// 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 abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
{
    private readonly int _batchSize;

    private readonly List<LogMessage> _currentBatch = new();
    private CancellationTokenSource _cancellationTokenSource;
    private LoggerSettings _loggerSettings;

    private ConcurrentQueue<LogMessage> _messageQueue;
    private Task _outputTask;
    private object _writeLock = new object();
    private bool _isDisposing;
    protected string Category;

    protected BatchingLoggerProvider(IOptions<BatchingLoggerOptions> options)
    {
        var loggerOptions = options.Value;

        if (loggerOptions.FlushPeriod <= TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod),
                $"{nameof(loggerOptions.FlushPeriod)} must be longer than zero.");
        }

        if (options.Value is FileLoggerOptions fileLoggerOptions)
        {
            UseLocalTime = fileLoggerOptions.UseLocalTime;
            UseMask = fileLoggerOptions.UseMask;
        }

        _batchSize = loggerOptions.BatchSize;

        StartAsync().ConfigureAwait(false);
    }

    protected DateTimeOffset CurrentDateTimeOffset => UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;
    protected DateTime CurrentDateTme => UseLocalTime ? DateTime.Now : DateTime.UtcNow;

    protected bool UseLocalTime { get; set; }

    protected LoggerSettings LoggerSettings
    {
        get
        {
            if (_loggerSettings != null)
            {
                return _loggerSettings;
            }

            _loggerSettings = new LoggerSettings();
            _loggerSettings.UseLocalTime = UseLocalTime;
            _loggerSettings.UseMask = UseMask;
            return _loggerSettings;
        }

        set => _loggerSettings = value;
    }

    public bool IsStarted { get; set; }
    public bool UseMask { get; set; }

    public void Dispose()
    {
        while (!_messageQueue.IsEmpty)
        {
            Task.Delay(10);
        }

        _isDisposing = true;
        StopAsync().GetAwaiter().GetResult();
        GC.SuppressFinalize(this);
    }

    public ILogger CreateLogger(string categoryName)
    {
        Category = categoryName;
        return new BatchingLogger(this, categoryName, LoggerSettings);
    }

    protected abstract Task WriteMessagesAsync(IEnumerable<LogMessage> messages, CancellationToken token);
    private async Task ProcessLogQueueAsync(object state)
    {
        while (!_cancellationTokenSource.IsCancellationRequested)
        {
            var limit = _batchSize <= 0 ? int.MaxValue : _batchSize;
            while (limit > 0 && _messageQueue.TryDequeue(out var message))
            {
                _currentBatch.Add(message);
                limit--;
            }

            if (_currentBatch.Count > 0)
            {
                try
                {
                    if (_isDisposing)
                    {
                        return;
                    }

                    lock (_writeLock)
                    {
                        WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token).ConfigureAwait(false);
                        _currentBatch.Clear();
                    }
                }
                catch
                {
                    // ignored
                }
            }
            Thread.Sleep(10);
        }
    }

    protected async Task WriteStartMessage()
    {
        var message = LogHelper.GetStartupMessage();
        await WriteMessagesAsync(new List<LogMessage> { CreateLoggerMessage(message, CurrentDateTimeOffset) }, _cancellationTokenSource.Token).ConfigureAwait(false);
    }

    private LogMessage CreateLoggerMessage(string message, DateTimeOffset currentDateTimeOffset)
    {
        var result = new LogMessage() { Message = message, Timestamp = currentDateTimeOffset };

        if (LoggerSettings != null && LoggerSettings.UseMask)
        {
            // Masking sensitive information
            result.Message = MaskSensitiveInformation(result.Message);
        }

        return result;
    }

    /// <summary>
    /// Masks sensitive information within the provided message string.
    /// This method is virtual and can be overridden to customize masking behavior.
    /// </summary>
    /// <param name="message">The log message potentially containing sensitive information.</param>
    /// <returns>The masked log message.</returns>
    protected virtual string MaskSensitiveInformation(string message)
    {
        if (string.IsNullOrEmpty(message))
        {
            return message;
        }

        if (LoggerSettings != null && LoggerSettings.UseDefaultMasking)
        {
            // Mask IP addresses
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b(?:\d{1,3}\.){3}\d{1,3}\b(?!\d)",
                LoggerSettings.Mask);

            // Mask MAC addresses
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b(?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}\b",
                LoggerSettings.Mask);

            // Mask emails
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b",
                LoggerSettings.Mask,
                System.Text.RegularExpressions.RegexOptions.IgnoreCase);

            // Mask passwords
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"(?i)(password\s*[:= ]\s*|pwd\s*[:= ]\s*)[^\s]+",
                $"password={LoggerSettings.Mask}");

            // Mask credit card numbers
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b(?:\d{4}[ -]?){3}\d{4}\b",
                LoggerSettings.Mask);

            // Mask social security numbers (SSN) and BSN (Dutch Citizen Service Number)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b\d{3}-\d{2}-\d{4}\b|\b\d{9}\b",
                LoggerSettings.Mask); // SSN (USA)

            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b\d{9}\b",
                LoggerSettings.Mask); // BSN (Dutch)

            // Mask passwords (Dutch)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"(?i)(wachtwoord\s*[:= ]\s*|ww\s*=\s*)[^\s]+",
                $"wachtwoord={LoggerSettings.Mask}");

            // Mask API keys/tokens
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b[A-Za-z0-9-_]{20,}\b",
                LoggerSettings.Mask);

            // Mask phone numbers (generic and Dutch specific)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b(\+?\d{1,4}[-.\s]?)?(\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}\b",
                LoggerSettings.Mask); // Generic

            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b(\+31|0031|0|06)[-\s]?\d{8}\b",
                LoggerSettings.Mask); // Dutch

            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b(\+32|0032|0|06)[-\s]?\d{8}\b",
                LoggerSettings.Mask); // Belgium

            // Mask dates of birth (DOB) or other date formats
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b\d{2}[/-]\d{2}[/-]\d{4}\b",
                LoggerSettings.Mask);

            // Mask Dutch postal codes
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b\d{4}\s?[A-Z]{2}\b",
                LoggerSettings.Mask);

            // Mask IBAN/Bank account numbers (generic and Dutch specific)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b[A-Z]{2}\d{2}[A-Z0-9]{1,30}\b",
                LoggerSettings.Mask); // Generic for EU and other country IBANs

            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\bNL\d{2}[A-Z]{4}\d{10}\b",
                LoggerSettings.Mask); // Dutch IBAN

            // Mask JWT Tokens
            message = System.Text.RegularExpressions.Regex.Replace(message,
             @"\b[A-Za-z0-9-_]{16,}\.[A-Za-z0-9-_]{16,}\.[A-Za-z0-9-_]{16,}\b",
             LoggerSettings.Mask);

            // Mask URLs with sensitive query strings
            message = System.Text.RegularExpressions.Regex.Replace(message,
            @"\bhttps?:\/\/[^\s?]+(\?[^\s]+?[\&=](password|key|token|wachtwoord|sleutel))[^&\s]*",
            LoggerSettings.Mask,
            System.Text.RegularExpressions.RegexOptions.IgnoreCase);

            // Mask license keys
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}\b",
                LoggerSettings.Mask);

            // Mask public and private keys (e.g., PEM format)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"-----BEGIN [A-Z ]+KEY-----[\s\S]+?-----END [A-Z ]+KEY-----",
                LoggerSettings.Mask);

            // Mask Dutch KVK number (8 or 12 digits)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b\d{8}|\d{12}\b",
                LoggerSettings.Mask);

            // Mask Dutch BTW-nummer (VAT number)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\bNL\d{9}B\d{2}\b",
                LoggerSettings.Mask);

            // Mask Dutch driving license number (10-12 characters)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b[A-Z0-9]{10,12}\b",
                LoggerSettings.Mask);

            // Mask Dutch health insurance number (Zorgnummer)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b\d{9}\b",
                LoggerSettings.Mask);

            // Mask other Dutch Bank Account numbers (9-10 digits)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b\d{9,10}\b",
                LoggerSettings.Mask);

            // Mask Dutch Passport Numbers (9 alphanumeric characters)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b[A-Z0-9]{9}\b",
                LoggerSettings.Mask);

            // Mask Dutch Identification Document Numbers (varying formats)
            message = System.Text.RegularExpressions.Regex.Replace(message,
                @"\b[A-Z]{2}\d{6,7}\b",
                LoggerSettings.Mask);
        }

        // Mask custom keywords specified in LoggerSettings
        if (LoggerSettings?.MaskedKeywords != null)
        {
            foreach (var keyword in LoggerSettings.MaskedKeywords)
            {
                if (string.IsNullOrWhiteSpace(keyword))
                {
                    continue;
                }
                message = message.Replace(keyword, LoggerSettings.Mask);
            }
        }

        return message;
    }

    internal string AddMessage(DateTimeOffset timestamp, string message)
    {
        var result = CreateLoggerMessage(message, timestamp);
        _messageQueue.Enqueue(result);
        return result.Message;
    }

    private Task StartAsync()
    {
        IsStarted = true;
        _messageQueue = new ConcurrentQueue<LogMessage>();
        _cancellationTokenSource = new CancellationTokenSource();

        _outputTask = Task.Factory.StartNew(
            ProcessLogQueueAsync,
            null,
            TaskCreationOptions.LongRunning);
        return Task.CompletedTask;
    }

    private async Task StopAsync()
    {
        _cancellationTokenSource.Cancel();

        try
        {
            while (_outputTask.Status != TaskStatus.RanToCompletion && _outputTask.Status != TaskStatus.Canceled)
            {
                await _outputTask.ConfigureAwait(false);
                await Task.Delay(100);
            }
        }
        catch (TaskCanceledException)
        {
        }
        catch (AggregateException exception) when (exception.InnerExceptions.Count == 1 &&
                                                   exception.InnerExceptions[0] is TaskCanceledException)
        {
        }
    }

    ~BatchingLoggerProvider()
    {
        Dispose();
    }
}