using System.IO;
using NexusShell.Models;

namespace NexusShell.Services.Deployment;

/// <summary>
/// Orchestrates install, update, and remove via the serial install queue.
/// </summary>
public sealed class NexusDeploymentService
{
    private readonly NexusInstallQueue _queue;
    private readonly NexusPackageRegistry _registry;
    private readonly NexusPackageInstaller _installer;
    private readonly NexusPackageUpdater _updater;
    private readonly NexusRollbackService _rollback;
    private readonly NexusPackageRepository _repository;
    private readonly NexusInstalledAppsService _installedApps;

    public NexusDeploymentService(
        NexusInstallQueue queue,
        NexusPackageRegistry registry,
        NexusPackageInstaller installer,
        NexusPackageUpdater updater,
        NexusRollbackService rollback,
        NexusPackageRepository repository,
        NexusInstalledAppsService installedApps)
    {
        _queue = queue;
        _registry = registry;
        _installer = installer;
        _updater = updater;
        _rollback = rollback;
        _repository = repository;
        _installedApps = installedApps;
    }

    public event EventHandler<NexusDeploymentProgress>? ProgressChanged;
    public event EventHandler<NexusDeploymentResult>? OperationCompleted;

    public NexusDeploymentPhase GetPhase(string packageId) => _registry.GetActivePhase(packageId);

    public bool IsBusy(string packageId) =>
        _registry.GetActivePhase(packageId) is not NexusDeploymentPhase.None and not NexusDeploymentPhase.Failed;

    public Task<NexusDeploymentResult> InstallAsync(string packageId, CancellationToken cancellationToken = default) =>
        _queue.EnqueueAsync(ct => ExecuteInstallAsync(packageId, ct), cancellationToken);

    public Task<NexusDeploymentResult> UpdateAsync(string packageId, CancellationToken cancellationToken = default) =>
        _queue.EnqueueAsync(ct => ExecuteUpdateAsync(packageId, ct), cancellationToken);

    public Task<NexusDeploymentResult> RemoveAsync(string packageId, CancellationToken cancellationToken = default) =>
        _queue.EnqueueAsync(ct => ExecuteRemoveAsync(packageId, ct), cancellationToken);

    private async Task<NexusDeploymentResult> ExecuteInstallAsync(string packageId, CancellationToken cancellationToken)
    {
        var manifest = _repository.GetById(packageId);
        if (manifest is null)
        {
            return Fail(packageId, "Pakken findes ikke i kataloget.");
        }

        if (_registry.IsInstalled(packageId))
        {
            return Fail(packageId, "Pakken er allerede installeret.");
        }

        var tempDir = Path.Combine(_repository.RootDirectory, "temp");
        Directory.CreateDirectory(tempDir);
        var tempPackage = Path.Combine(tempDir, $"{packageId}-{Guid.NewGuid():N}.nxp");

        try
        {
            Report(packageId, NexusDeploymentOperation.Install, NexusDeploymentPhase.Downloading, "Henter pakke…", 10);
            _registry.SetActivePhase(packageId, NexusDeploymentPhase.Downloading);
            _installedApps.QueueDownload(packageId);

            var progress = CreateProgressReporter(packageId, NexusDeploymentOperation.Install);
            await _installer.CopyFromRepositoryAsync(packageId, tempPackage, progress, cancellationToken);

            Report(packageId, NexusDeploymentOperation.Install, NexusDeploymentPhase.Installing, "Installerer…", 50);
            _registry.SetActivePhase(packageId, NexusDeploymentPhase.Installing);

            var extracted = await _installer.ExtractAsync(tempPackage, packageId, progress, cancellationToken);

            _registry.SaveInstalled(packageId, extracted.Version);
            _installedApps.Install(packageId, extracted.Name, extracted.Version);

            var result = Success(packageId, extracted.Version, "Installation fuldført.");
            Report(packageId, NexusDeploymentOperation.Install, NexusDeploymentPhase.None, result.Message!, 100);
            return result;
        }
        catch (Exception ex)
        {
            _registry.SetFailed(packageId, ex.Message);
            return Fail(packageId, ex.Message, NexusDeploymentOperation.Install);
        }
        finally
        {
            if (File.Exists(tempPackage))
            {
                File.Delete(tempPackage);
            }
        }
    }

    private async Task<NexusDeploymentResult> ExecuteUpdateAsync(string packageId, CancellationToken cancellationToken)
    {
        var manifest = _repository.GetById(packageId);
        if (manifest is null)
        {
            return Fail(packageId, "Pakken findes ikke i kataloget.");
        }

        var previousVersion = _registry.GetInstalledVersion(packageId);
        if (string.IsNullOrWhiteSpace(previousVersion))
        {
            return Fail(packageId, "Pakken er ikke installeret.");
        }

        if (!NexusPackageVersionComparer.IsLessThan(previousVersion, manifest.Version))
        {
            return Fail(packageId, "Ingen opdatering tilgængelig.");
        }

        var tempDir = Path.Combine(_repository.RootDirectory, "temp");
        Directory.CreateDirectory(tempDir);
        var tempPackage = Path.Combine(tempDir, $"{packageId}-update-{Guid.NewGuid():N}.nxp");
        var snapshotTaken = false;

        try
        {
            Report(packageId, NexusDeploymentOperation.Update, NexusDeploymentPhase.Downloading, "Henter opdatering…", 10);
            _registry.SetActivePhase(packageId, NexusDeploymentPhase.Downloading);
            _installedApps.QueueDownload(packageId);

            var progress = CreateProgressReporter(packageId, NexusDeploymentOperation.Update);
            await _installer.CopyFromRepositoryAsync(packageId, tempPackage, progress, cancellationToken);

            Report(packageId, NexusDeploymentOperation.Update, NexusDeploymentPhase.Updating, "Opdaterer…", 50);
            _registry.SetActivePhase(packageId, NexusDeploymentPhase.Updating);

            var extracted = await _updater.ApplyFullUpdateAsync(
                packageId, tempPackage, previousVersion, progress, cancellationToken);
            snapshotTaken = true;

            _registry.SaveInstalled(packageId, extracted.Version);
            _installedApps.Update(packageId, extracted.Name, extracted.Version);

            var result = Success(packageId, extracted.Version, "Opdatering fuldført.");
            Report(packageId, NexusDeploymentOperation.Update, NexusDeploymentPhase.None, result.Message!, 100);
            return result;
        }
        catch (Exception ex)
        {
            if (snapshotTaken && _rollback.RestoreLatest(packageId))
            {
                _registry.SaveInstalled(packageId, previousVersion!);
            }
            else
            {
                _registry.SetFailed(packageId, ex.Message);
            }

            return Fail(packageId, ex.Message, NexusDeploymentOperation.Update);
        }
        finally
        {
            if (File.Exists(tempPackage))
            {
                File.Delete(tempPackage);
            }
        }
    }

    private async Task<NexusDeploymentResult> ExecuteRemoveAsync(string packageId, CancellationToken cancellationToken)
    {
        var manifest = _repository.GetById(packageId);
        var name = manifest?.Name ?? packageId;

        if (!_registry.IsInstalled(packageId))
        {
            return Fail(packageId, "Pakken er ikke installeret.");
        }

        try
        {
            Report(packageId, NexusDeploymentOperation.Remove, NexusDeploymentPhase.Removing, "Afinstallerer…", 30);
            _registry.SetActivePhase(packageId, NexusDeploymentPhase.Removing);

            await Task.Run(() =>
            {
                cancellationToken.ThrowIfCancellationRequested();
                var installDir = _registry.GetInstalledDirectory(packageId);
                if (Directory.Exists(installDir))
                {
                    Directory.Delete(installDir, recursive: true);
                }
            }, cancellationToken);

            _registry.Remove(packageId);
            _installedApps.Uninstall(packageId, name);
            _rollback.ClearSnapshots(packageId);

            var result = Success(packageId, null, "Afinstallation fuldført.");
            Report(packageId, NexusDeploymentOperation.Remove, NexusDeploymentPhase.None, result.Message!, 100);
            return result;
        }
        catch (Exception ex)
        {
            _registry.SetFailed(packageId, ex.Message);
            return Fail(packageId, ex.Message, NexusDeploymentOperation.Remove);
        }
    }

    private IProgress<string> CreateProgressReporter(string packageId, NexusDeploymentOperation operation) =>
        new Progress<string>(message =>
        {
            var phase = operation switch
            {
                NexusDeploymentOperation.Update => NexusDeploymentPhase.Updating,
                NexusDeploymentOperation.Remove => NexusDeploymentPhase.Removing,
                _ => NexusDeploymentPhase.Installing,
            };
            Report(packageId, operation, phase, message, 60);
        });

    private void Report(
        string packageId,
        NexusDeploymentOperation operation,
        NexusDeploymentPhase phase,
        string message,
        double percent)
    {
        ProgressChanged?.Invoke(this, new NexusDeploymentProgress
        {
            PackageId = packageId,
            Operation = operation,
            Phase = phase,
            Message = message,
            Percent = percent,
        });
    }

    private NexusDeploymentResult Success(string packageId, string? version, string message)
    {
        var result = new NexusDeploymentResult
        {
            PackageId = packageId,
            Success = true,
            InstalledVersion = version,
            Message = message,
        };
        OperationCompleted?.Invoke(this, result);
        return result;
    }

    private NexusDeploymentResult Fail(
        string packageId,
        string message,
        NexusDeploymentOperation operation = NexusDeploymentOperation.Install)
    {
        Report(packageId, operation, NexusDeploymentPhase.Failed, message, 0);
        var result = new NexusDeploymentResult
        {
            PackageId = packageId,
            Success = false,
            Message = message,
        };
        OperationCompleted?.Invoke(this, result);
        return result;
    }
}
