using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

namespace EonaCat.LogStack.Exceptions;

// 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>
/// Advanced exception rendering with context tracking
/// </summary>
public sealed class AdvancedExceptionRenderer
{
    private readonly Dictionary<Exception, ExceptionContext> _contextMap = new();

    /// <summary>
    /// Registers context information for an exception
    /// </summary>
    public void RegisterContext(Exception ex, ExceptionContext context)
    {
        _contextMap[ex] = context;
    }

    /// <summary>
    /// Renders an exception with all available context
    /// </summary>
    public string RenderFull(Exception ex)
    {
        var sb = new StringBuilder();
        RenderException(sb, ex, 0, true);
        return sb.ToString();
    }

    /// <summary>
    /// Renders an exception with minimal detail
    /// </summary>
    public string RenderShort(Exception ex)
    {
        return $"{ex.GetType().Name}: {ex.Message}";
    }

    /// <summary>
    /// Renders just the stack trace with source information
    /// </summary>
    public string RenderStackTrace(Exception ex)
    {
        var sb = new StringBuilder();
        RenderStackTrace(sb, ex);
        return sb.ToString();
    }

    private void RenderException(StringBuilder sb, Exception ex, int depth, bool includeContext)
    {
        string indent = new string(' ', depth * 2);

        // Exception header
        sb.Append(indent).Append(ex.GetType().FullName).Append(": ").Append(ex.Message).AppendLine();

        // Stack trace
        if (!string.IsNullOrEmpty(ex.StackTrace))
        {
            RenderStackTrace(sb, ex);
        }

        // Context information
        if (includeContext && _contextMap.TryGetValue(ex, out var context))
        {
            sb.Append(indent).Append("Context:").AppendLine();
            if (context.CorrelationId != null)
            {
                sb.Append(indent).Append("  CorrelationId: ").Append(context.CorrelationId).AppendLine();
            }

            if (context.UserId != null)
            {
                sb.Append(indent).Append("  UserId: ").Append(context.UserId).AppendLine();
            }

            if (context.RequestPath != null)
            {
                sb.Append(indent).Append("  RequestPath: ").Append(context.RequestPath).AppendLine();
            }

            if (context.CustomData != null)
            {
                sb.Append(indent).Append("  CustomData:").AppendLine();
                foreach (var kvp in context.CustomData)
                {
                    sb.Append(indent).Append("    ").Append(kvp.Key).Append(": ").Append(kvp.Value).AppendLine();
                }
            }
        }

        // Inner exceptions
        if (ex.InnerException != null)
        {
            sb.Append(indent).Append("InnerException:").AppendLine();
            RenderException(sb, ex.InnerException, depth + 1, includeContext);
        }
    }

    private void RenderStackTrace(StringBuilder sb, Exception ex)
    {
        string indent = new string(' ', 4);
        var frames = new StackTrace(ex, true).GetFrames() ?? Array.Empty<StackFrame>();

        foreach (var frame in frames)
        {
            var method = frame.GetMethod();
            if (method == null)
            {
                continue;
            }

            sb.Append(indent).Append("at ");

            // Type and method
            if (method.DeclaringType != null)
            {
                sb.Append(method.DeclaringType.Name).Append(".");
            }
            sb.Append(method.Name);

            // Parameters
            var parameters = method.GetParameters();
            if (parameters.Length > 0)
            {
                sb.Append("(");
                for (int i = 0; i < parameters.Length; i++)
                {
                    if (i > 0)
                    {
                        sb.Append(", ");
                    }

                    sb.Append(parameters[i].ParameterType.Name);
                }
                sb.Append(")");
            }
            else
            {
                sb.Append("()");
            }

            // File and line number
            string fileName = frame.GetFileName();
            if (!string.IsNullOrEmpty(fileName))
            {
                int lineNumber = frame.GetFileLineNumber();
                sb.Append(" in ").Append(Path.GetFileName(fileName)).Append(":line ").Append(lineNumber);
            }

            sb.AppendLine();
        }
    }
}

/// <summary>
/// Context information associated with an exception
/// </summary>
public sealed class ExceptionContext
{
    public string? CorrelationId { get; set; }
    public string? UserId { get; set; }
    public string? RequestPath { get; set; }
    public Dictionary<string, object?>? CustomData { get; set; }
    public DateTime OccurredAt { get; set; }

    public ExceptionContext()
    {
        OccurredAt = DateTime.UtcNow;
        CustomData = new Dictionary<string, object?>();
    }

    /// <summary>
    /// Builder pattern for fluent context creation
    /// </summary>
    public static ExceptionContextBuilder Create()
    {
        return new ExceptionContextBuilder();
    }
}

/// <summary>
/// Builder for exception context
/// </summary>
public sealed class ExceptionContextBuilder
{
    private readonly ExceptionContext _context = new();

    public ExceptionContextBuilder WithCorrelationId(string id)
    {
        _context.CorrelationId = id;
        return this;
    }

    public ExceptionContextBuilder WithUserId(string userId)
    {
        _context.UserId = userId;
        return this;
    }

    public ExceptionContextBuilder WithRequestPath(string path)
    {
        _context.RequestPath = path;
        return this;
    }

    public ExceptionContextBuilder WithCustomData(string key, object? value)
    {
        _context.CustomData![key] = value;
        return this;
    }

    public ExceptionContextBuilder WithCustomData(Dictionary<string, object?> data)
    {
        if (data != null)
        {
            foreach (var kvp in data)
            {
                _context.CustomData![kvp.Key] = kvp.Value;
            }
        }
        return this;
    }

    public ExceptionContext Build()
    {
        return _context;
    }
}

/// <summary>
/// Global exception context manager
/// </summary>
public static class ExceptionContextManager
{
    private static readonly object _lock = new object();
    private static readonly Dictionary<Exception, ExceptionContext> _contextMap = new();

    /// <summary>
    /// Gets or creates context for an exception
    /// </summary>
    public static ExceptionContext GetOrCreateContext(Exception ex)
    {
        lock (_lock)
        {
            if (!_contextMap.TryGetValue(ex, out var context))
            {
                context = new ExceptionContext();
                _contextMap[ex] = context;
            }
            return context;
        }
    }

    /// <summary>
    /// Enriches an exception with context
    /// </summary>
    public static Exception WithContext(this Exception ex, Action<ExceptionContextBuilder> configure)
    {
        var builder = ExceptionContext.Create();
        configure(builder);
        lock (_lock)
        {
            _contextMap[ex] = builder.Build();
        }
        return ex;
    }

    /// <summary>
    /// Gets context for an exception, or null if none exists
    /// </summary>
    public static ExceptionContext? TryGetContext(Exception ex)
    {
        lock (_lock)
        {
            _contextMap.TryGetValue(ex, out var context);
            return context;
        }
    }

    /// <summary>
    /// Clears all stored contexts
    /// </summary>
    public static void Clear()
    {
        lock (_lock)
        {
            _contextMap.Clear();
        }
    }
}

/// <summary>
/// Exception aggregator for batch exception handling
/// </summary>
public sealed class ExceptionAggregator
{
    private readonly List<Exception> _exceptions = new();
    private readonly string _message;

    public ExceptionAggregator(string message = "One or more exceptions occurred")
    {
        _message = message;
    }

    public void Add(Exception ex)
    {
        if (ex != null)
        {
            _exceptions.Add(ex);
        }
    }

    public void Add(IEnumerable<Exception> exceptions)
    {
        if (exceptions != null)
        {
            _exceptions.AddRange(exceptions.Where(e => e != null));
        }
    }

    public bool HasExceptions => _exceptions.Count > 0;

    public int Count => _exceptions.Count;

    public void ThrowIfHasExceptions()
    {
        if (_exceptions.Count == 0)
        {
            return;
        }

        if (_exceptions.Count == 1)
        {
            throw _exceptions[0];
        }

        throw new AggregateException(_message, _exceptions);
    }

    public IReadOnlyList<Exception> GetExceptions()
    {
        return _exceptions.AsReadOnly();
    }

    public string GetSummary()
    {
        if (_exceptions.Count == 0)
        {
            return "No exceptions";
        }

        var sb = new StringBuilder();
        sb.Append(_message).Append(" (").Append(_exceptions.Count).AppendLine("):");

        for (int i = 0; i < _exceptions.Count && i < 10; i++)
        {
            var ex = _exceptions[i];
            sb.Append($"  [{i + 1}] {ex.GetType().Name}: {ex.Message}").AppendLine();
        }

        if (_exceptions.Count > 10)
        {
            sb.Append($"  ... and {_exceptions.Count - 10} more").AppendLine();
        }

        return sb.ToString();
    }

    public void Clear()
    {
        _exceptions.Clear();
    }
}
