using System.IO;
using System.IO.Compression;
using NexusShell.Models;

namespace NexusShell.Services.Deployment;

/// <summary>
/// Extracts a local .nxp archive to packages/installed/{packageId}/ and writes metadata.
/// </summary>
public sealed class NexusPackageInstaller
{
    private readonly NexusPackageRepository _repository;
    private readonly NexusPackageReader _reader;
    private readonly NexusPackageValidator _validator;

    public NexusPackageInstaller(
        NexusPackageRepository repository,
        NexusPackageReader reader,
        NexusPackageValidator validator)
    {
        _repository = repository;
        _reader = reader;
        _validator = validator;
    }

    public NexusPackageValidationResult ValidatePackageFile(string packagePath)
    {
        var manifest = _reader.ReadManifestFromPackage(packagePath);
        return _validator.Validate(manifest);
    }

    public async Task<NexusPackageManifest> ExtractAsync(
        string packagePath,
        string packageId,
        IProgress<string>? progress = null,
        CancellationToken cancellationToken = default)
    {
        if (!File.Exists(packagePath))
        {
            throw new FileNotFoundException($"Package file not found: {packagePath}");
        }

        var manifest = _reader.ReadManifestFromPackage(packagePath);
        var validation = _validator.Validate(manifest);
        if (!validation.IsValid || manifest is null)
        {
            throw new InvalidOperationException(
                validation.Errors.Count > 0
                    ? string.Join("; ", validation.Errors)
                    : "Invalid package manifest.");
        }

        if (!string.Equals(manifest.Id, packageId, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException(
                $"Package id mismatch: expected '{packageId}', manifest has '{manifest.Id}'.");
        }

        var targetDir = Path.Combine(_repository.InstalledDirectory, packageId);
        var stagingDir = targetDir + ".staging-" + Guid.NewGuid().ToString("N");

        try
        {
            progress?.Report("Udpakker .nxp…");
            await Task.Run(() =>
            {
                cancellationToken.ThrowIfCancellationRequested();
                Directory.CreateDirectory(stagingDir);
                ZipFile.ExtractToDirectory(packagePath, stagingDir, overwriteFiles: true);
            }, cancellationToken);

            if (Directory.Exists(targetDir))
            {
                Directory.Delete(targetDir, recursive: true);
            }

            Directory.Move(stagingDir, targetDir);
            stagingDir = string.Empty;

            progress?.Report("Skriver manifest…");
            var manifestPath = Path.Combine(targetDir, "manifest.json");
            if (!File.Exists(manifestPath))
            {
                var json = System.Text.Json.JsonSerializer.Serialize(manifest, new System.Text.Json.JsonSerializerOptions
                {
                    WriteIndented = true,
                });
                await File.WriteAllTextAsync(manifestPath, json, cancellationToken);
            }

            return manifest;
        }
        finally
        {
            if (!string.IsNullOrEmpty(stagingDir) && Directory.Exists(stagingDir))
            {
                Directory.Delete(stagingDir, recursive: true);
            }
        }
    }

    public async Task CopyFromRepositoryAsync(
        string packageId,
        string targetPackagePath,
        IProgress<string>? progress = null,
        CancellationToken cancellationToken = default)
    {
        var sourcePath = _repository.ResolvePackageFilePath(packageId);
        if (sourcePath is null || !File.Exists(sourcePath))
        {
            throw new FileNotFoundException($"No .nxp found in repository for '{packageId}'.");
        }

        progress?.Report("Kopierer pakke fra lokal repository…");
        Directory.CreateDirectory(Path.GetDirectoryName(targetPackagePath)!);

        await Task.Run(() =>
        {
            cancellationToken.ThrowIfCancellationRequested();
            File.Copy(sourcePath, targetPackagePath, overwrite: true);
        }, cancellationToken);
    }
}
