using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Logging;

namespace DragonISO.Engine.Logging;

/// <summary>
/// Convenience factory for an <see cref="ILoggerFactory"/> wired to Serilog. Two flavors:
/// <see cref="CreateConsole"/> for headless / Console-mode use (writes only to stdout),
/// and <see cref="CreateDefault"/> for the WPF host (writes to stdout AND to a rolling
/// daily file under <c>%LOCALAPPDATA%\Dragon-ISO\Logs</c> so support has something to ask for
/// when things break).
/// </summary>
public static class EngineLogging
{
    /// <summary>
    /// Default filename for the rolling-file sink. Serilog rotates on size and date with
    /// the suffix it inserts before the extension, so "DragonISO.log" becomes
    /// "Dragon-ISO20260508.log", "Dragon-ISO20260508_001.log", etc.
    /// </summary>
    private const string LogFileName = "DragonISO.log";

    /// <summary>
    /// Default directory for the rolling-file sink:
    /// <c>%LOCALAPPDATA%\Dragon-ISO\Logs\</c>. Created on first write if it doesn't exist.
    /// </summary>
    public static string DefaultLogDirectory =>
        Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "Dragon-ISO",
            "Logs");

    /// <summary>
    /// Console-only factory. Used by DragonISO.Console where stdout is the right surface
    /// and a file sink would be redundant with shell redirection.
    /// </summary>
    public static ILoggerFactory CreateConsole(LogLevel minimum = LogLevel.Information)
    {
        var serilog = new LoggerConfiguration()
            .MinimumLevel.Is(MapLevel(minimum))
            .Enrich.WithProperty("Component", "DragonISO.Engine")
            .WriteTo.Console(outputTemplate:
                "[{Timestamp:HH:mm:ss} {Level:u3}] [{Component}] {Message:lj}{NewLine}{Exception}")
            .CreateLogger();
        return new SerilogLoggerFactory(serilog, dispose: true);
    }

    /// <summary>
    /// Default factory for the desktop host. Writes to the console (visible when launched
    /// from a console attach) and to a rolling daily file at <see cref="DefaultLogDirectory"/>.
    /// File path is logged at startup to the console sink so users can find it.
    /// </summary>
    /// <param name="minimum">Minimum log level for both sinks.</param>
    /// <param name="logDirectoryOverride">Optional alternate log directory; null uses the default.</param>
    public static ILoggerFactory CreateDefault(
        LogLevel minimum = LogLevel.Information,
        string? logDirectoryOverride = null)
    {
        var dir = logDirectoryOverride ?? DefaultLogDirectory;
        Directory.CreateDirectory(dir); // idempotent; safe under contention
        var logPath = Path.Combine(dir, LogFileName);

        var serilog = new LoggerConfiguration()
            .MinimumLevel.Is(MapLevel(minimum))
            .Enrich.WithProperty("Component", "DragonISO.Engine")
            .WriteTo.Console(outputTemplate:
                "[{Timestamp:HH:mm:ss} {Level:u3}] [{Component}] {Message:lj}{NewLine}{Exception}")
            .WriteTo.File(
                path: logPath,
                outputTemplate:
                    "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] [{Component}] {Message:lj}{NewLine}{Exception}",
                rollingInterval: Serilog.RollingInterval.Day,
                retainedFileCountLimit: 14,           // two weeks of history is plenty
                fileSizeLimitBytes: 10 * 1024 * 1024, // 10 MB per file before roll-over
                rollOnFileSizeLimit: true,
                shared: false,
                flushToDiskInterval: TimeSpan.FromMilliseconds(250)) // flush often so support tools see live tails
            .CreateLogger();

        // Note: deliberately NOT setting Serilog.Log.Logger here. xUnit can run tests
        // in parallel and two factories from different tests would otherwise race on
        // the static singleton. SerilogLoggerFactory writes through the explicit
        // logger we pass in, so the static fallback isn't needed; engine code uses
        // the injected ILogger<T>, not Serilog.Log.* directly.
        var factory = new SerilogLoggerFactory(serilog, dispose: true);
        // Surface the path at startup so support has it without digging.
        factory.CreateLogger("DragonISO.Engine")
            .LogInformation("Diagnostic logs writing to: {LogDirectory}", dir);
        return factory;
    }

    private static Serilog.Events.LogEventLevel MapLevel(LogLevel level) => level switch
    {
        LogLevel.Trace => Serilog.Events.LogEventLevel.Verbose,
        LogLevel.Debug => Serilog.Events.LogEventLevel.Debug,
        LogLevel.Information => Serilog.Events.LogEventLevel.Information,
        LogLevel.Warning => Serilog.Events.LogEventLevel.Warning,
        LogLevel.Error => Serilog.Events.LogEventLevel.Error,
        LogLevel.Critical => Serilog.Events.LogEventLevel.Fatal,
        _ => Serilog.Events.LogEventLevel.Information
    };
}
