// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Globalization;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Management.Deployment;
using Windows.Foundation;

namespace Microsoft.CmdPal.Ext.WinGet.Pages;

public partial class InstallPackageCommand : InvokableCommand
{
    private readonly CatalogPackage _package;
    private readonly StatusMessage _installBanner = new();
    private IAsyncOperationWithProgress<InstallResult, InstallProgress>? _installAction;
    private IAsyncOperationWithProgress<UninstallResult, UninstallProgress>? _unInstallAction;
    private Task? _installTask;

    public PackageInstallCommandState InstallCommandState { get; private set; }

    public event EventHandler<InstallPackageCommand>? InstallStateChanged;

    private static readonly CompositeFormat UninstallingPackage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstalling_package);
    private static readonly CompositeFormat InstallingPackage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_installing_package);
    private static readonly CompositeFormat InstallPackageFinished = System.Text.CompositeFormat.Parse(Properties.Resources.winget_install_package_finished);
    private static readonly CompositeFormat UninstallPackageFinished = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstall_package_finished);
    private static readonly CompositeFormat QueuedPackageDownload = System.Text.CompositeFormat.Parse(Properties.Resources.winget_queued_package_download);
    private static readonly CompositeFormat InstallPackageFinishing = System.Text.CompositeFormat.Parse(Properties.Resources.winget_install_package_finishing);
    private static readonly CompositeFormat QueuedPackageUninstall = System.Text.CompositeFormat.Parse(Properties.Resources.winget_queued_package_uninstall);
    private static readonly CompositeFormat UninstallPackageFinishing = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstall_package_finishing);
    private static readonly CompositeFormat DownloadProgress = System.Text.CompositeFormat.Parse(Properties.Resources.winget_download_progress);

    internal bool SkipDependencies { get; set; }

    public InstallPackageCommand(CatalogPackage package, PackageInstallCommandState isInstalled)
    {
        _package = package;
        InstallCommandState = isInstalled;
        UpdateAppearance();
    }

    internal void FakeChangeStatus()
    {
        InstallCommandState = InstallCommandState switch
        {
            PackageInstallCommandState.Install => PackageInstallCommandState.Uninstall,
            PackageInstallCommandState.Update => PackageInstallCommandState.Uninstall,
            PackageInstallCommandState.Uninstall => PackageInstallCommandState.Install,
            _ => throw new NotImplementedException(),
        };
        UpdateAppearance();
    }

    private void UpdateAppearance()
    {
        Icon = InstallCommandState switch
        {
            PackageInstallCommandState.Install => Icons.DownloadIcon,
            PackageInstallCommandState.Update => Icons.UpdateIcon,
            PackageInstallCommandState.Uninstall => Icons.DeleteIcon,
            _ => throw new NotImplementedException(),
        };
        Name = InstallCommandState switch
        {
            PackageInstallCommandState.Install => Properties.Resources.winget_install_name,
            PackageInstallCommandState.Update => Properties.Resources.winget_update_name,
            PackageInstallCommandState.Uninstall => Properties.Resources.winget_uninstall_name,
            _ => throw new NotImplementedException(),
        };
    }

    public override ICommandResult Invoke()
    {
        // TODO: LOCK in here, so this can only be invoked once until the
        // install / uninstall is done. Just use like, an atomic
        if (_installTask is not null)
        {
            return CommandResult.KeepOpen();
        }

        if (InstallCommandState == PackageInstallCommandState.Uninstall)
        {
            // Uninstall
            _installBanner.State = MessageState.Info;
            _installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallingPackage, _package.Name);
            WinGetExtensionHost.Instance.ShowStatus(_installBanner, StatusContext.Extension);

            var installOptions = WinGetStatics.WinGetFactory.CreateUninstallOptions();
            installOptions.PackageUninstallScope = PackageUninstallScope.Any;
            _unInstallAction = WinGetStatics.Manager.UninstallPackageAsync(_package, installOptions);

            var handler = new AsyncOperationProgressHandler<UninstallResult, UninstallProgress>(OnUninstallProgress);
            _unInstallAction.Progress = handler;

            _installTask = Task.Run(() => TryDoInstallOperation(_unInstallAction));
        }
        else if (InstallCommandState is PackageInstallCommandState.Install or
                 PackageInstallCommandState.Update)
        {
            // Install
            _installBanner.State = MessageState.Info;
            _installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallingPackage, _package.Name);
            WinGetExtensionHost.Instance.ShowStatus(_installBanner, StatusContext.Extension);

            var installOptions = WinGetStatics.WinGetFactory.CreateInstallOptions();
            installOptions.PackageInstallScope = PackageInstallScope.Any;

            installOptions.SkipDependencies = SkipDependencies;

            _installAction = WinGetStatics.Manager.InstallPackageAsync(_package, installOptions);

            var handler = new AsyncOperationProgressHandler<InstallResult, InstallProgress>(OnInstallProgress);
            _installAction.Progress = handler;

            _installTask = Task.Run(() => TryDoInstallOperation(_installAction));
        }

        return CommandResult.KeepOpen();
    }

    private async void TryDoInstallOperation<T_Operation, T_Progress>(
        IAsyncOperationWithProgress<T_Operation, T_Progress> action)
    {
        try
        {
            await action.AsTask();

            _installBanner.Message = InstallCommandState == PackageInstallCommandState.Uninstall ?
                string.Format(CultureInfo.CurrentCulture, UninstallPackageFinished, _package.Name) :
                string.Format(CultureInfo.CurrentCulture, InstallPackageFinished, _package.Name);

            _installBanner.Progress = null;
            _installBanner.State = MessageState.Success;
            _installTask = null;

            _ = Task.Run(async () =>
            {
                await Task.Delay(2500).ConfigureAwait(false);

                if (_installTask is null)
                {
                    WinGetExtensionHost.Instance.HideStatus(_installBanner);
                }
            });
            InstallStateChanged?.Invoke(this, this);
        }
        catch (Exception ex)
        {
            _installBanner.State = MessageState.Error;
            _installBanner.Progress = null;
            _installBanner.Message = ex.Message;
            _installTask = null;
        }
    }

    private static string FormatBytes(ulong bytes)
    {
        const long KB = 1024;
        const long MB = KB * 1024;
        const long GB = MB * 1024;

        return bytes >= GB
            ? $"{bytes / (double)GB:F2} GB"
            : bytes >= MB ?
                $"{bytes / (double)MB:F2} MB"
                : bytes >= KB
                    ? $"{bytes / (double)KB:F2} KB"
                    : $"{bytes} bytes";
    }

    private void OnInstallProgress(
        IAsyncOperationWithProgress<InstallResult, InstallProgress> operation,
        InstallProgress progress)
    {
        switch (progress.State)
        {
            case PackageInstallProgressState.Queued:
                _installBanner.Message = string.Format(CultureInfo.CurrentCulture, QueuedPackageDownload, _package.Name);
                break;
            case PackageInstallProgressState.Downloading:
                if (progress.BytesRequired > 0)
                {
                    var downloadText = string.Format(CultureInfo.CurrentCulture, DownloadProgress, FormatBytes(progress.BytesDownloaded), FormatBytes(progress.BytesRequired));
                    _installBanner.Progress ??= new ProgressState() { IsIndeterminate = false };
                    var downloaded = progress.BytesDownloaded / (float)progress.BytesRequired;
                    var percent = downloaded * 100.0f;
                    ((ProgressState)_installBanner.Progress).ProgressPercent = (uint)percent;
                    _installBanner.Message = downloadText;
                }

                break;
            case PackageInstallProgressState.Installing:
                _installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallingPackage, _package.Name);
                _installBanner.Progress = new ProgressState() { IsIndeterminate = true };
                break;
            case PackageInstallProgressState.PostInstall:
                _installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallPackageFinishing, _package.Name);
                break;
            case PackageInstallProgressState.Finished:
                _installBanner.Message = Properties.Resources.winget_install_finished;

                // progressBar.IsIndeterminate(false);
                _installBanner.Progress = null;
                _installBanner.State = MessageState.Success;
                break;
            default:
                _installBanner.Message = string.Empty;
                break;
        }
    }

    private void OnUninstallProgress(
        IAsyncOperationWithProgress<UninstallResult, UninstallProgress> operation,
        UninstallProgress progress)
    {
        switch (progress.State)
        {
            case PackageUninstallProgressState.Queued:
                _installBanner.Message = string.Format(CultureInfo.CurrentCulture, QueuedPackageUninstall, _package.Name);
                break;

            case PackageUninstallProgressState.Uninstalling:
                _installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallingPackage, _package.Name);
                _installBanner.Progress = new ProgressState() { IsIndeterminate = true };
                break;
            case PackageUninstallProgressState.PostUninstall:
                _installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallPackageFinishing, _package.Name);
                break;
            case PackageUninstallProgressState.Finished:
                _installBanner.Message = Properties.Resources.winget_uninstall_finished;
                _installBanner.Progress = null;
                _installBanner.State = MessageState.Success;
                break;
            default:
                _installBanner.Message = string.Empty;
                break;
        }
    }
}

public enum PackageInstallCommandState
{
    Uninstall = 0,
    Update = 1,
    Install = 2,
}
