using NexusShell.Models;
using NexusShell.Services.Deployment;
using NexusShell.Services.Policy;

namespace NexusShell.Services;

public sealed class NexusStoreService
{
    private readonly NexusPackageService _packageService;
    private readonly NexusInstalledAppsService _installed;
    private readonly NexusDeploymentService _deployment;
    private readonly UpdateService _updateService;
    private readonly ClientVersionInfo _clientVersion;
    private readonly NexusPolicyService _policy;
    private bool _suppressCatalogNotify;

    public NexusStoreService(
        NexusPackageService packageService,
        NexusInstalledAppsService installed,
        NexusDeploymentService deployment,
        UpdateService updateService,
        ClientVersionInfo clientVersion,
        NexusPolicyService policy)
    {
        _packageService = packageService;
        _installed = installed;
        _deployment = deployment;
        _updateService = updateService;
        _clientVersion = clientVersion;
        _policy = policy;

        _installed.InstalledStateChanged += (_, _) => NotifyCatalogChanged();
        _deployment.OperationCompleted += (_, _) => NotifyCatalogChanged();
    }

    public event EventHandler? CatalogChanged;
    public event EventHandler<NexusDeploymentProgress>? DeploymentProgress;

    public NexusDeploymentService Deployment => _deployment;

    public string ShellVersionLabel => $"{_clientVersion.Version} (build {_clientVersion.Build})";

    public string UpdateChannelLabel => _updateService.StatusSummary;

    public string RepositoryPath => _packageService.RepositoryRoot;

    public int UpdateCount => _packageService.CountUpdatesAvailable();

    public void Reload()
    {
        _suppressCatalogNotify = true;
        try
        {
            _packageService.Reload();
        }
        finally
        {
            _suppressCatalogNotify = false;
        }

        NotifyCatalogChanged();
    }

    public Task<NexusDeploymentResult> InstallAsync(string packageId, CancellationToken cancellationToken = default)
    {
        SubscribeDeploymentProgress();
        return _deployment.InstallAsync(packageId, cancellationToken);
    }

    public Task<NexusDeploymentResult> UpdateAsync(string packageId, CancellationToken cancellationToken = default)
    {
        SubscribeDeploymentProgress();
        return _deployment.UpdateAsync(packageId, cancellationToken);
    }

    public Task<NexusDeploymentResult> RemoveAsync(string packageId, CancellationToken cancellationToken = default)
    {
        SubscribeDeploymentProgress();
        return _deployment.RemoveAsync(packageId, cancellationToken);
    }

    public IReadOnlyList<NexusStoreNavItem> GetNavItems()
    {
        var updates = UpdateCount;
        return
        [
            new NexusStoreNavItem { Section = NexusStoreNavSection.Home, Label = "Forside", IconPath = NexusLucideIcons.Home },
            new NexusStoreNavItem { Section = NexusStoreNavSection.Installed, Label = "Installeret", IconPath = NexusLucideIcons.CheckCircle },
            new NexusStoreNavItem { Section = NexusStoreNavSection.Available, Label = "Tilgængelige Apps", IconPath = NexusLucideIcons.LayoutGrid },
            new NexusStoreNavItem { Section = NexusStoreNavSection.Updates, Label = "Opdateringer", IconPath = NexusLucideIcons.Refresh, BadgeCount = updates },
            new NexusStoreNavItem { Section = NexusStoreNavSection.Favorites, Label = "Favoritter", IconPath = NexusLucideIcons.Star },
            new NexusStoreNavItem { Section = NexusStoreNavSection.NexusApps, Label = "Nexus Apps", IconPath = NexusLucideIcons.Hexagon },
            new NexusStoreNavItem { Section = NexusStoreNavSection.SystemApps, Label = "System Apps", IconPath = NexusLucideIcons.Monitor },
            new NexusStoreNavItem { Section = NexusStoreNavSection.Downloads, Label = "Downloads", IconPath = NexusLucideIcons.Download },
            new NexusStoreNavItem { Section = NexusStoreNavSection.History, Label = "Historik", IconPath = NexusLucideIcons.Clock },
        ];
    }

    public IReadOnlyList<NexusStoreHomeSection> GetHomeSections()
    {
        var all = GetAllItems();
        return
        [
            Section("Anbefalede", all.OrderByDescending(p => p.Manifest.IsNexusApp).ThenByDescending(p => p.Manifest.Popularity).Take(6)),
            Section("Senest opdateret", all.OrderByDescending(p => ParseDate(p.Manifest.UpdatedAt)).Take(6)),
            Section("Populære", all.OrderByDescending(p => p.Manifest.Popularity).Take(6)),
            Section("Nye apps", all.Where(p => p.Manifest.IsNew).Take(6)),
        ];
    }

    public IReadOnlyList<NexusStorePackageItem> GetItemsForSection(NexusStoreNavSection section) => section switch
    {
        NexusStoreNavSection.Home => [],
        NexusStoreNavSection.Installed => GetAllItems().Where(i =>
            i.PackageState is NexusPackageState.Installed or NexusPackageState.UpdateAvailable).ToList(),
        NexusStoreNavSection.Available => GetAllItems().Where(i => i.PackageState == NexusPackageState.Available).ToList(),
        NexusStoreNavSection.Updates => GetAllItems().Where(i => i.PackageState == NexusPackageState.UpdateAvailable).ToList(),
        NexusStoreNavSection.Favorites => GetAllItems().Where(i => i.IsFavorite).ToList(),
        NexusStoreNavSection.NexusApps => GetAllItems().Where(i => i.Manifest.IsNexusApp).ToList(),
        NexusStoreNavSection.SystemApps => GetAllItems().Where(i =>
            string.Equals(i.Manifest.Category, "System", StringComparison.OrdinalIgnoreCase) && !i.Manifest.IsNexusApp).ToList(),
        NexusStoreNavSection.Downloads => GetAllItems().Where(i =>
            i.DeploymentPhase is NexusDeploymentPhase.Downloading or NexusDeploymentPhase.Installing
                or NexusDeploymentPhase.Updating).ToList(),
        NexusStoreNavSection.History => [],
        _ => GetAllItems(),
    };

    public IReadOnlyList<NexusStoreHistoryEntry> GetHistory() => _installed.GetHistory();

    public NexusStorePackageItem? GetItem(string packageId)
    {
        var package = _packageService.GetInstalledPackage(packageId);
        return package is null ? null : CreateItem(package);
    }

    public NexusStorePackageItem BuildItem(NexusPackageManifest manifest)
    {
        var package = _packageService.GetInstalledPackage(manifest.Id);
        return package is not null
            ? CreateItem(package)
            : CreateItem(new NexusInstalledPackage
            {
                Id = manifest.Id,
                Manifest = manifest,
                State = NexusPackageState.Available,
            });
    }

    public void ToggleFavorite(string packageId) => _installed.ToggleFavorite(packageId);

    public string? ResolveLaunchArguments(string entryPoint)
    {
        if (!entryPoint.StartsWith("nexus:", StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        var parts = entryPoint.Split('?', 2);
        return parts.Length > 1 ? parts[1] : null;
    }

    public string ResolveLaunchScheme(string entryPoint)
    {
        if (!entryPoint.StartsWith("nexus:", StringComparison.OrdinalIgnoreCase))
        {
            return entryPoint;
        }

        var uri = entryPoint.Split('?', 2)[0];
        return uri.ToLowerInvariant();
    }

    private IReadOnlyList<NexusStorePackageItem> GetAllItems() =>
        _packageService.GetAllPackages()
            .Where(p => _policy.CanAccessStore() && _policy.IsPackageAllowed(p.Manifest.Id))
            .Select(CreateItem)
            .ToList();

    private NexusStorePackageItem CreateItem(NexusInstalledPackage package)
    {
        var manifest = package.Manifest;
        var isFavorite = _installed.IsFavorite(manifest.Id);
        var deploymentPhase = _packageService.GetDeploymentPhase(manifest.Id);
        var isBusy = deploymentPhase is not NexusDeploymentPhase.None and not NexusDeploymentPhase.Failed;
        var installState = MapInstallState(package.State, deploymentPhase);

        var canInstall = !isBusy && package.State == NexusPackageState.Available;
        var canUpdate = !isBusy && package.State == NexusPackageState.UpdateAvailable;
        var canRemove = !isBusy && package.State is NexusPackageState.Installed
            or NexusPackageState.UpdateAvailable or NexusPackageState.Broken;
        var canOpen = !isBusy && package.State is NexusPackageState.Installed
            or NexusPackageState.UpdateAvailable && manifest.IsNexusApp;

        return new NexusStorePackageItem
        {
            Manifest = manifest,
            PackageState = package.State,
            InstalledVersion = package.InstalledVersion,
            InstallState = installState,
            IsFavorite = isFavorite,
            IconPath = NexusLucideIcons.ForStoreIconKey(manifest.ResolvedIconKey),
            StatusLabel = NexusPackageService.StatusLabelFor(package.State, deploymentPhase),
            DeploymentPhase = deploymentPhase,
            CanInstall = canInstall,
            CanOpen = canOpen,
            CanUpdate = canUpdate,
            CanRemove = canRemove,
            InstallActionLabel = ResolveInstallActionLabel(package.State, deploymentPhase),
        };
    }

    private static string ResolveInstallActionLabel(NexusPackageState state, NexusDeploymentPhase phase) => phase switch
    {
        NexusDeploymentPhase.Downloading => "Downloader…",
        NexusDeploymentPhase.Installing => "Installerer…",
        NexusDeploymentPhase.Updating => "Opdaterer…",
        NexusDeploymentPhase.Removing => "Afinstallerer…",
        NexusDeploymentPhase.Failed => "Prøv igen",
        _ => state switch
        {
            NexusPackageState.Available => "Installer",
            NexusPackageState.UpdateAvailable => "Opdater",
            NexusPackageState.Installed => "Installeret",
            _ => "Installer",
        },
    };

    private static NexusStoreInstallState MapInstallState(NexusPackageState state, NexusDeploymentPhase phase) => phase switch
    {
        NexusDeploymentPhase.Downloading => NexusStoreInstallState.Downloading,
        NexusDeploymentPhase.Installing => NexusStoreInstallState.Installing,
        NexusDeploymentPhase.Updating => NexusStoreInstallState.Updating,
        NexusDeploymentPhase.Removing => NexusStoreInstallState.Removing,
        NexusDeploymentPhase.Failed => NexusStoreInstallState.Failed,
        _ => state switch
        {
            NexusPackageState.Installed => NexusStoreInstallState.Installed,
            NexusPackageState.UpdateAvailable => NexusStoreInstallState.UpdateAvailable,
            NexusPackageState.Available => NexusStoreInstallState.NotInstalled,
            NexusPackageState.Broken => NexusStoreInstallState.Broken,
            NexusPackageState.Disabled => NexusStoreInstallState.Disabled,
            _ => NexusStoreInstallState.NotInstalled,
        },
    };

    private void SubscribeDeploymentProgress()
    {
        _deployment.ProgressChanged -= OnDeploymentProgress;
        _deployment.ProgressChanged += OnDeploymentProgress;
    }

    private void OnDeploymentProgress(object? sender, NexusDeploymentProgress e)
    {
        DeploymentProgress?.Invoke(this, e);
    }

    private void NotifyCatalogChanged()
    {
        if (_suppressCatalogNotify)
        {
            return;
        }

        CatalogChanged?.Invoke(this, EventArgs.Empty);
    }

    private static NexusStoreHomeSection Section(string title, IEnumerable<NexusStorePackageItem> packages) =>
        new() { Title = title, Packages = packages.ToList() };

    private static DateTimeOffset ParseDate(string value) =>
        DateTimeOffset.TryParse(value, out var parsed) ? parsed : DateTimeOffset.MinValue;
}
