using System.Diagnostics;
using Newtonsoft.Json;
using RobloxLegacy.AppData;
using RobloxLegacy.Utilities;
using ICSharpCode.SharpZipLib.Zip;

namespace RobloxLegacy;

public class VersionData
{
    [JsonProperty("version")]
    public required string Version { get; set; }

    [JsonProperty("clientVersionUpload")]
    public required string UploadHash { get; set; }
}

public class VersionManager : IDisposable
{
    private static readonly HttpClient Client = new();
    private const string CdnUrl = "https://setup.rbxcdn.com";
    private const string DllName = "lptch.dll";
    private string? _currentVersion;
    
    private readonly string _tempPath = Path.Combine(Path.GetTempPath(), $"RobloxLegacy.{Guid.NewGuid().ToString()}");
    private readonly IAppData _appData;

    public VersionManager(IAppData appData)
    {
        Directory.CreateDirectory(_tempPath);
        _currentVersion = Registry.GetVersion(appData.PackageName);
        _appData = appData;
    }

    private static string GetVersionPath(string version)
    {
        var localAppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
        return Path.Combine(localAppDataPath, "Roblox", "Versions", version);
    }

    private async Task<VersionData?> GetLatestVersion()
    {
        var url = $"https://clientsettings.roblox.com/v2/client-version/{_appData.PackageName}/channel/LIVE";
        var response = await Client.GetAsync(url);
        if (!response.IsSuccessStatusCode) // just to be safe
            return null;
        var version = await response.Content.ReadAsAsync<VersionData>();
        return version;
    }

    private static void ExtractBundle(string version, string folder, string tempFile)
    {
        var fastZip = new FastZip();
        var extractPath = Path.Combine(GetVersionPath(version), folder);
        fastZip.ExtractZip(tempFile, extractPath, null);
    }
    
    private void WriteAppSettings()
    {
        var appSettings = new Resource("AppSettings.xml").GetBytes();
        if (appSettings == null)
            throw new Exception("Failed to get AppSettings resource");
        File.WriteAllBytesAsync(Path.Combine(GetVersionPath(_currentVersion), "AppSettings.xml"), appSettings);
    }
    
    private void PatchStudio()
    {
        Logger.Info("Patching application...");
        var exePath = Path.Combine(GetVersionPath(_currentVersion), _appData.ExecutableName);
        
        // rename imports first
        Patcher.RenameImports(exePath, ["KERNEL32.dll", "MFPlat.DLL"], DllName);
        
        // now we can write the dll to the folder
        var dllContents = new Resource(DllName).GetBytes();
        if (dllContents == null)
            throw new Exception("Failed to get dll resource");
        File.WriteAllBytesAsync(Path.Combine(GetVersionPath(_currentVersion), DllName), dllContents);
    }

    public async Task InstallPackage()
    {
        var version = await GetLatestVersion();
        if (version == null)
            throw new Exception("No version data found");
        if (version.UploadHash == _currentVersion)
            return;

        Logger.Info($"Installing {_appData.PackageName} version {version.Version}...");
        foreach (var file in _appData.PackageFiles)
        {
            try
            {
                var fileName = $"{version.UploadHash}-{file.Key}";
                var fileBytes = await Client.GetByteArrayAsync($"{CdnUrl}/{fileName}");

                var zipPath = Path.Combine(_tempPath, fileName);
                await File.WriteAllBytesAsync(zipPath, fileBytes);

                ExtractBundle(version.UploadHash, file.Value, zipPath);
            }
            catch (HttpRequestException)
            {
                Logger.Error($"Failed to download {file.Key}");
            }
        }
        
        if(_currentVersion != null)
            Directory.Delete(GetVersionPath(_currentVersion), true);

        _currentVersion = version.UploadHash;
        WriteAppSettings();
        Registry.SaveVersion(_appData.PackageName, version.UploadHash);

        // need to patch the executable
        if (_appData.PackageName == "WindowsStudio64")
            PatchStudio();
    }

    public void LaunchApp()
    {
        Logger.Info($"Launching {_appData.PackageName}...");
        if(string.IsNullOrEmpty(_currentVersion))
            throw new Exception("No version data found");
        
        var startInfo = new ProcessStartInfo()
        {
            FileName = _appData.ExecutableName,
            WorkingDirectory = GetVersionPath(_currentVersion),
            UseShellExecute = true
        };
        
        Process.Start(startInfo);
    }

    public void Dispose()
    {
        Directory.Delete(_tempPath, true);
        GC.SuppressFinalize(this);
    }
}