using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using BepInEx.Logging;
using MonoMod.Utils;

namespace BepInEx.Preloader.Core.Logging;

public static class ChainloaderLogHelper
{
    private static Dictionary<string, string> MacOSVersions { get; } = new()
    {
        // https://en.wikipedia.org/wiki/Darwin_%28operating_system%29#Release_history
        ["16.0.0"] = "10.12",
        ["16.5.0"] = "10.12.4",
        ["16.6.0"] = "10.12.6",
        ["17.5.0"] = "10.13.4",
        ["17.6.0"] = "10.13.5",
        ["17.7.0"] = "10.13.6",
        ["18.2.0"] = "10.14.1",
        ["19.2.0"] = "10.15.2",
        ["19.3.0"] = "10.15.3",
        ["19.5.0"] = "10.15.5.1",
        ["20.1.0"] = "11.0",
        ["20.2.0"] = "11.1",
        ["20.3.0"] = "11.2",
        ["20.4.0"] = "11.3",
        ["20.5.0"] = "11.4",
        ["21.0.1"] = "12.0",
        ["21.1.0"] = "12.0.1",
        ["21.2.0"] = "12.1",
    };

    public static void PrintLogInfo(ManualLogSource log)
    {
        var consoleTitle = $"BepInEx {Paths.DisplayBepInExVersion} - {Paths.ProcessName}";
        log.Log(LogLevel.Message, $"{consoleTitle} ({File.GetLastWriteTime(Paths.ExecutablePath)})");

        if (ConsoleManager.ConsoleActive)
            ConsoleManager.SetConsoleTitle(consoleTitle);

        if (!string.IsNullOrEmpty(Paths.BepInExVersion.Build))
            log.Log(LogLevel.Message, $"Built from commit {Paths.BepInExVersion.Build}");

        Logger.Log(LogLevel.Info, $"System platform: {GetPlatformString()}");
        Logger.Log(LogLevel.Info,
                   $"Process bitness: {(PlatformUtils.ProcessIs64Bit ? "64-bit (x64)" : "32-bit (x86)")}");
    }

    private static string GetPlatformString()
    {
        var builder = new StringBuilder();

        var osVersion = Environment.OSVersion.Version;

        // NOTE: this logic needs to be different for .NET 5.
        // We don't use it and I don't think we will for a long time (possibly ever), but upgrading will break Environment.OSVersion
        // https://docs.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/5.0/environment-osversion-returns-correct-version#change-description

        // Some additional notes
        // On .NET Framework and .NET Core platforms before 5, Environment.OSVersion does not work as you would expect.

        // On Windows, it returns at maximum 6.3 (Windows 8) if you don't specify that your application is specifically compatible with Windows 10, due to compatibility layer stuff.
        // So we have to call RtlGetVersion which bypasses that and gets the values for us. This is done in PlatformUtils

        // On macOS it returns the Darwin kernel version. I've included a mapping of most versions, but there's definitely some missing versions

        // Not sure what it does on Linux. I think it returns the kernel version there too, but we already get the utsname structure from SetPlatform() regardless

        if (PlatformHelper.Is(Platform.Windows))
        {
            osVersion = PlatformUtils.WindowsVersion;

            builder.Append("Windows ");

            // https://stackoverflow.com/a/2819962

            if (osVersion.Major >= 10 && osVersion.Build >= 22000)
                builder.Append("11");
            else if (osVersion.Major >= 10)
                builder.Append("10");
            else if (osVersion.Major == 6 && osVersion.Minor == 3)
                builder.Append("8.1");
            else if (osVersion.Major == 6 && osVersion.Minor == 2)
                builder.Append("8");
            else if (osVersion.Major == 6 && osVersion.Minor == 1)
                builder.Append("7");
            else if (osVersion.Major == 6 && osVersion.Minor == 0)
                builder.Append("Vista");
            else if (osVersion.Major <= 5)
                builder.Append("XP");

            if (PlatformHelper.Is(Platform.Wine))
                builder.AppendFormat(" (Wine {0})", PlatformUtils.WineVersion);
        }
        else if (PlatformHelper.Is(Platform.MacOS))
        {
            builder.Append("macOS ");


            var osxVersion = osVersion.ToString(3);

            if (MacOSVersions.TryGetValue(osxVersion, out var macOsVersion))
            {
                builder.Append(macOsVersion);
            }
            else
            {
                builder.AppendFormat("Unknown (kernel {0})", osVersion);
            }
        }
        else if (PlatformHelper.Is(Platform.Linux))
        {
            builder.Append("Linux");

            if (PlatformUtils.LinuxKernelVersion != null)
            {
                builder.AppendFormat(" (kernel {0})", PlatformUtils.LinuxKernelVersion);
            }
        }

        builder.Append(PlatformHelper.Is(Platform.Bits64) ? " 64-bit" : " 32-bit");

        if (PlatformHelper.Is(Platform.Android))
        {
            builder.Append(" Android");
        }

        if (PlatformHelper.Is(Platform.ARM))
        {
            builder.Append(" ARM");

            if (PlatformHelper.Is(Platform.Bits64))
                builder.Append("64");
        }

        return builder.ToString();
    }

    public static void RewritePreloaderLogs()
    {
        if (PreloaderConsoleListener.LogEvents == null || PreloaderConsoleListener.LogEvents.Count == 0)
            return;

        // Temporarily disable the console log listener (if there is one from preloader) as we replay the preloader logs
        var logListener = Logger.Listeners.FirstOrDefault(logger => logger is ConsoleLogListener);

        if (logListener != null)
            Logger.Listeners.Remove(logListener);

        foreach (var preloaderLogEvent in PreloaderConsoleListener.LogEvents)
            Logger.InternalLogEvent(PreloaderLogger.Log, preloaderLogEvent);

        if (logListener != null)
            Logger.Listeners.Add(logListener);
    }
}
