using System.Linq;
using System.Security.Cryptography;
using CliWrap;
using DTLib.Extensions;
using Mlaumcherb.Client.Avalonia.зримое;
using Mlaumcherb.Client.Avalonia.классы;
using Mlaumcherb.Client.Avalonia.сеть;
using Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
using Mlaumcherb.Client.Avalonia.холопы;
using static Mlaumcherb.Client.Avalonia.холопы.PathHelper;

namespace Mlaumcherb.Client.Avalonia;

public class GameVersion
{
    private readonly InstalledGameVersionProps _props;
    public string Id => _props.Id;
    public IOPath WorkingDirectory { get; }

    private IOPath JavaExecutableFilePath;

    private GameVersionDescriptor _descriptor;
    private JavaArguments _javaArgs;
    private GameArguments _gameArgs;
    private Libraries _libraries;
    private CancellationTokenSource? _gameCts;
    private CommandTask<CommandResult>? _commandTask;

    internal GameVersion(InstalledGameVersionProps props, GameVersionDescriptor descriptor)
    {
        _props = props;
        _descriptor = descriptor;
        _javaArgs = new JavaArguments(_descriptor);
        _gameArgs = new GameArguments(_descriptor);
        _libraries = new Libraries(_descriptor);
        WorkingDirectory = GetVersionDir(_descriptor.id);
        JavaExecutableFilePath = GetJavaExecutablePath(
            _descriptor.javaVersion.component, LauncherApp.Config.redirect_game_output);
    }

    public async Task Download(bool update, Action<NetworkTask> networkTaskCreatedCallback)
    {
        LauncherApp.Logger.LogInfo(Id, $"started updating version {Id}");
        
        List<INetworkTaskFactory> taskFactories =
        [
            new AssetsDownloadTaskFactory(_descriptor),
            new LibrariesDownloadTaskFactory(_descriptor, _libraries),
        ];
        if (LauncherApp.Config.download_java) 
            taskFactories.Add(new JavaDownloadTaskFactory(_descriptor));
        if (_descriptor.modpack != null) 
            taskFactories.Add(new ModpackDownloadTaskFactory(_descriptor));
        // has to be downloaded last because it is used to check if version is installed
        taskFactories.Add(new VersionJarDownloadTaskFactory(_descriptor));
        
        var networkTasks = new List<NetworkTask>();
        for (int i = 0; i < taskFactories.Count; i++)
        {
            var nt = await taskFactories[i].CreateAsync(update);
            if (nt != null)
            {
                networkTasks.Add(nt);
                networkTaskCreatedCallback.Invoke(nt);
            }
        }

        foreach (var nt in networkTasks)
        {
            await nt.StartAsync();
        }
        
        // create log4j config file
        if (!File.Exists(GetLog4jConfigFilePath()))
        {
            await using var res = EmbeddedResources.GetResourceStream(
                $"Mlaumcherb.Client.Avalonia.встроенное.{GetLog4jConfigFileName()}");
            await using var file = File.OpenWrite(GetLog4jConfigFilePath());
            await res.CopyToAsync(file);
        }
        
        _props.IsInstalled = true;
        LauncherApp.Logger.LogInfo(Id, $"finished updating version {Id}");
    }
    
    //minecraft player uuid explanation
    //https://gist.github.com/CatDany/0e71ca7cd9b42a254e49/
    //java uuid generation in c#
    //https://stackoverflow.com/questions/18021808/uuid-interop-with-c-sharp-code
    public static string GetPlayerUUID(string name)
    {
        byte[] name_bytes = Encoding.UTF8.GetBytes("OfflinePlayer:" + name);
        var md5 = MD5.Create();
        byte[] hash = md5.ComputeHash(name_bytes);
        hash[6] &= 0x0f;
        hash[6] |= 0x30;
        hash[8] &= 0x3f;
        hash[8] |= 0x80;
        
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < hash.Length; i++)
            sb.Append(hash[i].ToString("x2"));
        sb.Insert(8, '-').Insert(13, '-').Insert(18, '-').Insert(23, '-');
        return sb.ToString();
    }

    public async Task Launch()
    {
        if (string.IsNullOrWhiteSpace(LauncherApp.Config.player_name))
            throw new Exception("invalid player name");
        
        string classSeparator = PlatformHelper.GetOs() == "windows" ? ";" : ":";
        Directory.Create(WorkingDirectory);
        string uuid = GetPlayerUUID(LauncherApp.Config.player_name);
        Dictionary<string, string> placeholder_values = new() {
            { "resolution_width", "1600" },
            { "resolution_height", "900" },
            { "xms", LauncherApp.Config.max_memory.ToString() },
            { "xmx", LauncherApp.Config.max_memory.ToString() },
            { "auth_player_name", LauncherApp.Config.player_name },
            { "auth_access_token", uuid },
            { "auth_session", $"token:{uuid}:{uuid}" },
            { "auth_xuid", "" },
            { "auth_uuid", uuid },
            { "clientid", "" },
            { "user_properties", "{}" },
            { "user_type", "userType" },
            { "launcher_name", "java-minecraft-launcher" },
            { "launcher_version", "1.6.84-j" },
            { "classpath", _libraries.Libs.Select(l => l.jarFilePath)
                .Append(GetVersionJarFilePath(_descriptor.id))
                .MergeToString(classSeparator) },
            { "assets_index_name", _descriptor.assets },
            { "assets_root", GetAssetsDir().ToString() },
            { "game_assets", GetAssetsDir().ToString() },
            { "game_directory", WorkingDirectory.ToString() },
            { "natives_directory", GetNativeLibrariesDir(_descriptor.id).ToString() },
            { "library_directory", GetLibrariesDir().ToString() },
            { "classpath_separator", classSeparator},
            { "path", GetLog4jConfigFilePath().ToString() },
            { "version_name", _descriptor.id },
            { "version_type", _descriptor.type },
            { "arch", PlatformHelper.GetArchOld() },
            // { "quickPlayMultiplayer", "" },
            // { "quickPlayPath", "" },
            // { "quickPlayRealms", "" },
            // { "quickPlaySingleplayer", "" },
            { "client_jar", GetVersionJarFilePath(_descriptor.id).ToString() },
        };
        
        List<string> argsList = new();
        argsList.AddRange(_javaArgs.FillPlaceholders(placeholder_values));
        argsList.Add(_descriptor.mainClass);
        argsList.AddRange(_gameArgs.FillPlaceholders(placeholder_values));
        var command = Cli.Wrap(JavaExecutableFilePath.ToString())
            .WithWorkingDirectory(WorkingDirectory.ToString())
            .WithArguments(argsList)
            .WithValidation(CommandResultValidation.None);
        if (LauncherApp.Config.redirect_game_output)
        {
            command = command
                .WithStandardOutputPipe(PipeTarget.ToDelegate(LogGameOut))
                .WithStandardErrorPipe(PipeTarget.ToDelegate(LogGameError));
        }
        LauncherApp.Logger.LogInfo(Id, "launching the game");
        LauncherApp.Logger.LogDebug(Id, "java: " + command.TargetFilePath);
        LauncherApp.Logger.LogDebug(Id, "working_dir: " + command.WorkingDirPath);
        LauncherApp.Logger.LogDebug(Id, "arguments: \n\t" + argsList.MergeToString("\n\t"));
        _gameCts = new();
        _commandTask = command.ExecuteAsync(_gameCts.Token);
        var result = await _commandTask;
        LauncherApp.Logger.LogInfo(Id, $"game exited with code {result.ExitCode}");
    }

    private void LogGameOut(string line)
    {
        LauncherApp.Logger.LogInfo(Id, line);
    }
    private void LogGameError(string line)
    {
        LauncherApp.Logger.LogWarn(Id, line);
    }
    
    public void Close()
    {
        _gameCts?.Cancel();
    }


    public override string ToString() => Id;
}
