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

namespace NexusShell.Services;

public sealed class NexusPackageService
{
    private readonly NexusPackageRepository _repository;
    private readonly NexusPackageValidator _validator;
    private readonly INexusPackageUpdateCoordinator _updateCoordinator;
    private readonly INexusPackageDependencyResolver _dependencyResolver;
    private readonly NexusPackageRegistry? _registry;

    public NexusPackageService(
        NexusPackageRepository repository,
        NexusPackageValidator? validator = null,
        INexusPackageUpdateCoordinator? updateCoordinator = null,
        INexusPackageDependencyResolver? dependencyResolver = null,
        NexusPackageRegistry? registry = null)
    {
        _repository = repository;
        _validator = validator ?? new NexusPackageValidator();
        _updateCoordinator = updateCoordinator ?? new NexusPackageUpdateCoordinatorStub();
        _dependencyResolver = dependencyResolver ?? new NexusPackageDependencyResolverStub();
        _registry = registry;
    }

    public event EventHandler? CatalogChanged;

    public void Reload()
    {
        _repository.Load();
        CatalogChanged?.Invoke(this, EventArgs.Empty);
    }

    public string RepositoryRoot => _repository.RootDirectory;

    public NexusPackageIndex GetRepositoryIndex() => _repository.GetIndex();

    public IReadOnlyList<NexusPackageManifest> GetCatalogManifests() => _repository.GetAll();

    public NexusPackageManifest? GetManifest(string packageId) => _repository.GetById(packageId);

    public NexusPackageValidationResult ValidateManifest(NexusPackageManifest manifest) =>
        _validator.Validate(manifest);

    public NexusPackageState ResolveState(string packageId)
    {
        var manifest = _repository.GetById(packageId);
        if (manifest is null)
        {
            return NexusPackageState.Broken;
        }

        return ResolveState(manifest);
    }

    public NexusPackageState ResolveState(NexusPackageManifest manifest)
    {
        var activePhase = _registry?.GetActivePhase(manifest.Id) ?? NexusDeploymentPhase.None;
        if (activePhase is NexusDeploymentPhase.Failed)
        {
            return NexusPackageState.Broken;
        }

        var record = _repository.GetInstalledRecord(manifest.Id);
        if (record is null)
        {
            return NexusPackageState.Available;
        }

        if (record.State is NexusPackageState.Broken or NexusPackageState.Disabled)
        {
            return record.State;
        }

        if (string.IsNullOrWhiteSpace(record.InstalledVersion))
        {
            return NexusPackageState.Available;
        }

        if (NexusPackageVersionComparer.IsLessThan(record.InstalledVersion, manifest.Version))
        {
            return NexusPackageState.UpdateAvailable;
        }

        return NexusPackageState.Installed;
    }

    public NexusInstalledPackage? GetInstalledPackage(string packageId)
    {
        var manifest = _repository.GetById(packageId);
        if (manifest is null)
        {
            return null;
        }

        return BuildInstalledPackage(manifest);
    }

    public IReadOnlyList<NexusInstalledPackage> GetAllPackages() =>
        _repository.GetAll().Select(BuildInstalledPackage).ToList();

    public IReadOnlyList<NexusInstalledPackage> GetPackagesByState(NexusPackageState state) =>
        GetAllPackages().Where(p => p.State == state).ToList();

    public int CountUpdatesAvailable() =>
        GetAllPackages().Count(p => p.State == NexusPackageState.UpdateAvailable);

    public bool HasUpdateAvailable(string packageId) =>
        ResolveState(packageId) == NexusPackageState.UpdateAvailable;

    public IReadOnlyList<string> GetMissingDependencies(string packageId)
    {
        var manifest = _repository.GetById(packageId);
        if (manifest is null)
        {
            return [];
        }

        var installedIds = _repository.GetInstalledRecords()
            .Where(r => r.State is NexusPackageState.Installed or NexusPackageState.UpdateAvailable)
            .Select(r => r.Id)
            .ToHashSet(StringComparer.OrdinalIgnoreCase);

        return _dependencyResolver.GetMissingDependencies(manifest, installedIds);
    }

    public async Task<NexusPackageUpdatePlan?> PlanUpdateAsync(
        string packageId,
        CancellationToken cancellationToken = default)
    {
        var manifest = _repository.GetById(packageId);
        if (manifest is null)
        {
            return null;
        }

        var record = _repository.GetInstalledRecord(packageId);
        await _updateCoordinator.PlanUpdateAsync(packageId, cancellationToken);

        return new NexusPackageUpdatePlan
        {
            PackageId = packageId,
            FromVersion = record?.InstalledVersion ?? "0.0.0",
            ToVersion = manifest.Version,
            IsMandatory = _updateCoordinator.IsMandatoryUpdate(manifest, record?.InstalledVersion),
            RequiresRestart = true,
            DependencyPackageIds = _dependencyResolver.GetMissingDependencies(
                manifest,
                _repository.GetInstalledRecords().Select(r => r.Id).ToHashSet(StringComparer.OrdinalIgnoreCase)),
        };
    }

    public NexusDeploymentPhase GetDeploymentPhase(string packageId) =>
        _registry?.GetActivePhase(packageId) ?? NexusDeploymentPhase.None;

    public static string StatusLabelFor(NexusPackageState state, NexusDeploymentPhase phase = NexusDeploymentPhase.None) => phase switch
    {
        NexusDeploymentPhase.Downloading => "Downloader…",
        NexusDeploymentPhase.Installing => "Installerer…",
        NexusDeploymentPhase.Updating => "Opdaterer…",
        NexusDeploymentPhase.Removing => "Afinstallerer…",
        NexusDeploymentPhase.Failed => "Fejlet",
        _ => state switch
        {
            NexusPackageState.Installed => "Installeret",
            NexusPackageState.UpdateAvailable => "Opdatering tilgængelig",
            NexusPackageState.Available => "Tilgængelig",
            NexusPackageState.Broken => "Beskadiget",
            NexusPackageState.Disabled => "Deaktiveret",
            _ => "Ukendt",
        },
    };

    public static string StatusLabelFor(NexusPackageState state) => StatusLabelFor(state, NexusDeploymentPhase.None);

    private NexusInstalledPackage BuildInstalledPackage(NexusPackageManifest manifest)
    {
        var record = _repository.GetInstalledRecord(manifest.Id);
        var state = ResolveState(manifest);

        return new NexusInstalledPackage
        {
            Id = manifest.Id,
            Manifest = manifest,
            State = state,
            InstalledVersion = record?.InstalledVersion,
            InstalledAt = record?.InstalledAt,
            StateReason = record?.StateReason,
        };
    }
}
