using System.IO;

namespace NexusShell.Services.Deployment;

/// <summary>
/// Keeps previous installed versions under packages/rollback/{id}/ for restore on failure.
/// </summary>
public sealed class NexusRollbackService
{
    private const int MaxSnapshots = 3;
    private readonly NexusPackageRepository _repository;

    public NexusRollbackService(NexusPackageRepository repository)
    {
        _repository = repository;
    }

    public string RollbackRoot => _repository.RollbackDirectory;

    public bool HasSnapshot(string packageId) => GetSnapshotVersions(packageId).Count > 0;

    public IReadOnlyList<string> GetSnapshotVersions(string packageId)
    {
        var dir = GetRollbackPackageDir(packageId);
        if (!Directory.Exists(dir))
        {
            return [];
        }

        return Directory.GetDirectories(dir)
            .Select(Path.GetFileName)
            .Where(v => !string.IsNullOrWhiteSpace(v))
            .Cast<string>()
            .OrderByDescending(v => v, StringComparer.OrdinalIgnoreCase)
            .ToList();
    }

    public void CreateSnapshot(string packageId, string? versionLabel = null)
    {
        var sourceDir = Path.Combine(_repository.InstalledDirectory, packageId);
        if (!Directory.Exists(sourceDir))
        {
            return;
        }

        var rollbackPackageDir = GetRollbackPackageDir(packageId);
        Directory.CreateDirectory(rollbackPackageDir);

        var label = string.IsNullOrWhiteSpace(versionLabel)
            ? DateTimeOffset.UtcNow.ToString("yyyyMMddHHmmss")
            : versionLabel;
        var targetDir = Path.Combine(rollbackPackageDir, label);
        if (Directory.Exists(targetDir))
        {
            Directory.Delete(targetDir, recursive: true);
        }

        CopyDirectory(sourceDir, targetDir);

        var excess = Directory.GetDirectories(rollbackPackageDir)
            .OrderByDescending(d => d, StringComparer.OrdinalIgnoreCase)
            .Skip(MaxSnapshots)
            .ToList();

        foreach (var old in excess)
        {
            Directory.Delete(old, recursive: true);
        }
    }

    public bool RestoreLatest(string packageId)
    {
        var versions = GetSnapshotVersions(packageId);
        return versions.Count > 0 && Restore(packageId, versions[0]);
    }

    public bool Restore(string packageId, string versionLabel)
    {
        var snapshotDir = Path.Combine(GetRollbackPackageDir(packageId), versionLabel);
        if (!Directory.Exists(snapshotDir))
        {
            return false;
        }

        var targetDir = Path.Combine(_repository.InstalledDirectory, packageId);
        if (Directory.Exists(targetDir))
        {
            Directory.Delete(targetDir, recursive: true);
        }

        CopyDirectory(snapshotDir, targetDir);
        return true;
    }

    public void ClearSnapshots(string packageId)
    {
        var dir = GetRollbackPackageDir(packageId);
        if (Directory.Exists(dir))
        {
            Directory.Delete(dir, recursive: true);
        }
    }

    private string GetRollbackPackageDir(string packageId) =>
        Path.Combine(_repository.RollbackDirectory, packageId);

    private static void CopyDirectory(string sourceDir, string targetDir)
    {
        Directory.CreateDirectory(targetDir);

        foreach (var file in Directory.EnumerateFiles(sourceDir))
        {
            var dest = Path.Combine(targetDir, Path.GetFileName(file));
            File.Copy(file, dest, overwrite: true);
        }

        foreach (var subDir in Directory.EnumerateDirectories(sourceDir))
        {
            var destSub = Path.Combine(targetDir, Path.GetFileName(subDir));
            CopyDirectory(subDir, destSub);
        }
    }
}
