using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Security.AccessControl;
using AyCode.Core.Consts;
using AyCode.Core.Enums;
using AyCode.Utils.Extensions;
using Microsoft.Extensions.Logging;
using static System.Net.Mime.MediaTypeNames;
using AcLogLevel = AyCode.Core.Loggers.LogLevel;
using MsLogLevel = Microsoft.Extensions.Logging.LogLevel;

namespace AyCode.Core.Loggers;

public abstract class AcLoggerBase : IAcLoggerBase
{
    protected readonly List<IAcLogWriterBase> LogWriters = [];

    public AcLogLevel LogLevel { get; set; } = AcLogLevel.Error;
    public AppType AppType { get; set; } = AppType.Server;

    public string? CategoryName { get; set; }

    /// <summary>
    /// If true, long category names (e.g., "Microsoft.AspNetCore.SignalR.HubConnectionHandler")
    /// will be shortened to just the class name (e.g., "HubConnectionHandler").
    /// Default is true for consistency with typeof(T).Name usage.
    /// </summary>
    public bool ShortenCategoryNames { get; set; } = true;

    /// <summary>
    /// If true, ILogger bridge calls without an EventId name resolve the real caller from the stack trace
    /// (async state machines unwrapped) instead of showing "Log". Costs a few µs per enabled bridge log;
    /// named AC methods (Debug/Info/...) are unaffected. Set false to restore the plain "Log" fallback.
    /// </summary>
    public bool ResolveBridgeCallerNames { get; set; } = true;

    protected AcLoggerBase() : this(null)
    {
    }

    protected AcLoggerBase(string? categoryName)
    {
        CategoryName = categoryName ?? "...";

        AppType = AcEnv.AppConfiguration.GetEnum<AppType>("AyCode:Logger:AppType");
        LogLevel = AcEnv.AppConfiguration.GetEnum<AcLogLevel>("AyCode:Logger:LogLevel");

        foreach (var logWriterSection in AcEnv.AppConfiguration.GetSection("AyCode:Logger:LogWriters").GetChildren())
        {
            try
            {
                Type? logWriterType;
                var logWriterTypeString = logWriterSection["LogWriterType"];
                
                if (logWriterTypeString.IsNullOrWhiteSpace()|| (logWriterType = Type.GetType(logWriterTypeString)) == null)
                {
                    Console.Error.WriteLine($"{GetType().Name}; logWriterTypeString not valid; logWriterTypeString: {logWriterTypeString};");
                    continue;
                }
                
                var logWriterLogLevel = logWriterSection.GetEnum<AcLogLevel>("LogLevel");

                if (Activator.CreateInstance(logWriterType, AppType, logWriterLogLevel, CategoryName) is IAcLogWriterBase logWriter) LogWriters.Add(logWriter);
                else Console.Error.WriteLine($"{GetType().Name}; Can't create logWriterType instance; logWriterType: {logWriterType?.AssemblyQualifiedName};");
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"{GetType().Name}; {ex}");
            }
        }
    }

    protected AcLoggerBase(string? categoryName, params IAcLogWriterBase[] logWriters) :
        this(AcEnv.AppConfiguration.GetEnum<AppType>("AyCode:Logger:AppType"), AcEnv.AppConfiguration.GetEnum<AcLogLevel>("AyCode:Logger:LogLevel"), categoryName, logWriters)
    {
    }

    protected AcLoggerBase(AppType appType, AcLogLevel logLevel, string? categoryName, params IAcLogWriterBase[] logWriters)
    {
        AppType = appType;
        LogLevel = logLevel;

        CategoryName = categoryName ?? "...";

        foreach (var acLogWriterBase in logWriters)
            LogWriters.Add(acLogWriterBase);
    }

    public List<IAcLogWriterBase> GetWriters => [.. LogWriters];
    public TLogWriter? Writer<TLogWriter>() where TLogWriter : IAcLogWriterBase => LogWriters.OfType<TLogWriter>().FirstOrDefault();

    #region IAcLogWriterBase Implementation

    public virtual void Detail(string? text, string? categoryName = null, [CallerMemberName] string? memberName = null)
    {
        if (LogLevel <= AcLogLevel.Detail) LogWriters.ForEach(x => x.Detail(text, categoryName ?? CategoryName, memberName));
    }

    [Conditional("DEBUG")]
    public void DetailConditional(string? text, string? categoryName = null, [CallerMemberName] string? memberName = null)
        => Detail(text, categoryName, memberName);

    public virtual void Debug(string? text, string? categoryName = null, [CallerMemberName] string? memberName = null)
    {
        if (LogLevel <= AcLogLevel.Debug) LogWriters.ForEach(x => x.Debug(text, categoryName ?? CategoryName, memberName));
    }

    [Conditional("DEBUG")]
    public void DebugConditional(string? text, string? categoryName = null, [CallerMemberName] string? memberName = null)
        => Debug(text, categoryName, memberName);

    public virtual void Info(string? text, string? categoryName = null, [CallerMemberName] string? memberName = null)
    {
        if (LogLevel <= AcLogLevel.Info) LogWriters.ForEach(x => x.Info(text, categoryName ?? CategoryName, memberName));
    }

    [Conditional("DEBUG")]
    public void InfoConditional(string? text, string? categoryName = null, [CallerMemberName] string? memberName = null)
        => Info(text, categoryName, memberName);

    public virtual void Warning(string? text, string? categoryName = null, [CallerMemberName] string? memberName = null)
    {
        if (LogLevel <= AcLogLevel.Warning) LogWriters.ForEach(x => x.Warning(text, categoryName ?? CategoryName, memberName));
    }

    [Conditional("DEBUG")]
    public void WarningConditional(string? text, string? categoryName = null, [CallerMemberName] string? memberName = null)
        => Warning(text, categoryName, memberName);

    public virtual void Suggest(string? text, string? categoryName = null, [CallerMemberName] string? memberName = null)
    {
        if (LogLevel <= AcLogLevel.Suggest) LogWriters.ForEach(x => x.Suggest(text, categoryName ?? CategoryName, memberName));
    }

    [Conditional("DEBUG")]
    public void SuggestConditional(string? text, string? categoryName = null, [CallerMemberName] string? memberName = null)
        => Suggest(text, categoryName, memberName);

    public virtual void Error(string? text, Exception? ex = null, string? categoryName = null, [CallerMemberName] string? memberName = null)
    {
        if (LogLevel <= AcLogLevel.Error) LogWriters.ForEach(x => x.Error(text, ex, categoryName ?? CategoryName, memberName));
    }

    [Conditional("DEBUG")]
    public void ErrorConditional(string? text, Exception? ex = null, string? categoryName = null, [CallerMemberName] string? memberName = null)
        => Error(text, ex, categoryName, memberName);

    public void Write(AppType appType, AcLogLevel logLevel, string? logText, string? callerMemberName, string? categoryName)
        => Write(appType, logLevel, logText, callerMemberName, categoryName, null, null);

    [Conditional("DEBUG")]
    public void WriteConditional(AppType appType, AcLogLevel logLevel, string? logText, string? callerMemberName, string? categoryName)
        => Write(appType, logLevel, logText, callerMemberName, categoryName, null, null);

    public void Write(AppType appType, AcLogLevel logLevel, string? logText, string? callerMemberName, string? categoryName, Exception? ex)
        => Write(appType, logLevel, logText, callerMemberName, categoryName, ex?.GetType().Name, ex?.ToString());

    [Conditional("DEBUG")]
    public void WriteConditional(AppType appType, AcLogLevel logLevel, string? logText, string? callerMemberName, string? categoryName, Exception? ex)
        => Write(appType, logLevel, logText, callerMemberName, categoryName, ex);

    public virtual void Write(AppType appType, AcLogLevel logLevel, string? logText, string? callerMemberName, string? categoryName, string? errorType, string? exMessage)
    {
        if (LogLevel <= logLevel) LogWriters.ForEach(x => x.Write(appType, logLevel, logText, callerMemberName, categoryName ?? CategoryName, errorType, exMessage));
    }

    [Conditional("DEBUG")]
    public void WriteConditional(AppType appType, AcLogLevel logLevel, string? logText, string? callerMemberName, string? categoryName, string? errorType, string? exMessage)
        => Write(appType, logLevel, logText, callerMemberName, categoryName, errorType, exMessage);

    public void Write(IAcLogItemClient logItem)
    {
        if (LogLevel <= logItem.LogLevel) LogWriters.ForEach(x => x.Write(logItem));
    }

    [Conditional("DEBUG")]
    public void WriteConditional(IAcLogItemClient logItem) => Write(logItem);

    #endregion

    #region ILogger Implementation

    /// <summary>
    /// ILogger.BeginScope - AcLoggerBase doesn't support scopes, returns no-op disposable.
    /// </summary>
    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;

    /// <summary>
    /// ILogger.IsEnabled - Checks if the specified Microsoft log level is enabled.
    /// </summary>
    public bool IsEnabled(MsLogLevel logLevel)
    {
        var acLogLevel = MapFromMsLogLevel(logLevel);
        return LogLevel <= acLogLevel;
    }

    /// <summary>
    /// ILogger.Log - Main logging method called by Microsoft services.
    /// </summary>
    public void Log<TState>(MsLogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel))
            return;

        var message = formatter(state, exception);

        if (string.IsNullOrEmpty(message) && exception == null)
            return;

        //var fullMessage = eventId.Id != 0
        //    ? $"[{eventId.Id}:{eventId.Name}] {message}"
        //    : message;

        var fullMessage = message;
        var category = ShortenCategoryNames ? GetShortCategoryName(CategoryName) : CategoryName;
        
        // Explicit EventId name wins; otherwise resolve the real caller from the stack (opt-out: ResolveBridgeCallerNames).
        // EventId.Id 0 means "no id" — show the bare name without the ":0" suffix.
        var memberName = !string.IsNullOrEmpty(eventId.Name)
            ? eventId.Id != 0 ? $"{eventId.Name}:{eventId.Id}" : eventId.Name
            : ResolveBridgeCallerNames ? ResolveCallerFromStackTrace() : "Log";

        switch (logLevel)
        {
            case MsLogLevel.Trace:
                Detail(fullMessage, category, memberName);
                break;
            case MsLogLevel.Debug:
                Debug(fullMessage, category, memberName);
                break;
            case MsLogLevel.Information:
                Info(fullMessage, category, memberName);
                break;
            case MsLogLevel.Warning:
                Warning(fullMessage, category, memberName);
                break;
            case MsLogLevel.Error:
                Error(fullMessage, exception, category, memberName);
                break;
            case MsLogLevel.Critical:
                Error($"[CRITICAL] {fullMessage}", exception, category, memberName);
                break;
            case MsLogLevel.None:
            default:
                break;
        }
    }

    /// <summary>
    /// Shortens a fully qualified type name to just the class name.
    /// E.g., "Microsoft.AspNetCore.SignalR.HubConnectionHandler" -> "HubConnectionHandler"
    /// </summary>
    private static string? GetShortCategoryName(string? categoryName)
    {
        if (string.IsNullOrEmpty(categoryName))
            return categoryName;

        var lastDot = categoryName.LastIndexOf('.');
        return lastDot >= 0 ? categoryName[(lastDot + 1)..] : categoryName;
    }

    /// <summary>
    /// Finds the first stack frame outside the logging infrastructure so ILogger bridge calls
    /// (LogDebug/LogInformation/... extensions — these cannot carry CallerMemberName) still show
    /// the real caller. Async state machines and lambdas are unwrapped (&lt;Method&gt;d__N.MoveNext -> Method).
    /// Only runs when the level is enabled and no EventId name was provided.
    /// </summary>
    private static string ResolveCallerFromStackTrace()
    {
        var stackTrace = new StackTrace(skipFrames: 1, fNeedFileInfo: false);
        var frameCount = Math.Min(stackTrace.FrameCount, 15);

        for (var i = 0; i < frameCount; i++)
        {
            var method = stackTrace.GetFrame(i)?.GetMethod();
            var declaringType = method?.DeclaringType;
            if (method == null || declaringType == null) continue;

            if (declaringType.Namespace?.StartsWith("Microsoft.Extensions.Logging", StringComparison.Ordinal) == true) continue;
            if (typeof(AcLoggerBase).IsAssignableFrom(declaringType)) continue;

            // Async state machine: compiler-generated "<Method>d__N" type, method is MoveNext.
            var typeName = declaringType.Name;
            if (typeName.Length > 2 && typeName[0] == '<')
            {
                var end = typeName.IndexOf('>');
                if (end > 1) return typeName[1..end];
            }

            // Lambda / local function: "<Caller>b__0"-style method name — unwrap to the containing method.
            var methodName = method.Name;
            if (methodName.Length > 2 && methodName[0] == '<')
            {
                var end = methodName.IndexOf('>');
                if (end > 1) return methodName[1..end];
            }

            return methodName;
        }

        return "Log";
    }

    /// <summary>
    /// Maps Microsoft.Extensions.Logging.LogLevel to AyCode.Core.Loggers.LogLevel.
    /// </summary>
    private static AcLogLevel MapFromMsLogLevel(MsLogLevel msLogLevel)
    {
        return msLogLevel switch
        {
            MsLogLevel.Trace => AcLogLevel.Detail,
            MsLogLevel.Debug => AcLogLevel.Debug,
            MsLogLevel.Information => AcLogLevel.Info,
            MsLogLevel.Warning => AcLogLevel.Warning,
            MsLogLevel.Error => AcLogLevel.Error,
            MsLogLevel.Critical => AcLogLevel.Error,
            MsLogLevel.None => AcLogLevel.Disabled,
            _ => AcLogLevel.Info
        };
    }

    /// <summary>
    /// No-op scope implementation for ILogger.BeginScope.
    /// </summary>
    private sealed class NullScope : IDisposable
    {
        public static NullScope Instance { get; } = new();
        private NullScope() { }
        public void Dispose() { }
    }

    #endregion
}