using System.Security.Cryptography;

namespace NexusInstaller.Services;

internal sealed class CleanupResult
{
    public bool Success { get; init; }
    public string Message { get; init; } = string.Empty;
    public string? BackupPath { get; init; }
    public IReadOnlyList<string> RemovedPaths { get; init; } = [];
}

internal static class NexusInstallCleanupService
{
    private static readonly string[] BinaryFolders = ["shell", "identity", "versions", "current", "cache", "staging"];

    public static CleanupResult RepairBinaries(string installRoot, IProgress<string>? log = null)
    {
        var removed = new List<string>();
        var root = Path.GetFullPath(installRoot);

        foreach (var folder in BinaryFolders)
        {
            var path = Path.Combine(root, folder);
            if (!Directory.Exists(path))
            {
                continue;
            }

            TryRemove(path, removed, log);
        }

        RemoveStaleTempExtracts(root, removed, log);
        RemoveBadShortcuts(root, log);
        ClearStuckUpdateJobs(log);

        var dataPreserved = Directory.Exists(NexusIdentityDataPaths.ResolveDataDirectory(root));
        return new CleanupResult
        {
            Success = true,
            Message = dataPreserved
                ? $"Repair fuldført — data/identity bevaret ({removed.Count} sti(er) ryddet)."
                : $"Repair fuldført ({removed.Count} sti(er) ryddet).",
            RemovedPaths = removed,
        };
    }

    public static CleanupResult FreshPocReset(string installRoot, IProgress<string>? log = null)
    {
        var root = Path.GetFullPath(installRoot);
        var removed = new List<string>();
        string? backupPath = null;

        var dataDir = NexusIdentityDataPaths.ResolveDataDirectory(root);
        if (Directory.Exists(dataDir))
        {
            backupPath = Path.Combine(root, "backup", DateTime.Now.ToString("yyyyMMdd-HHmm"));
            Directory.CreateDirectory(backupPath);
            CopyDirectory(dataDir, Path.Combine(backupPath, "data-identity"));
            log?.Report($"Backup: {backupPath}");

            TryRemove(dataDir, removed, log);
        }

        var enrollmentDir = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "NinjaNexus");

        if (Directory.Exists(enrollmentDir))
        {
            var backupEnrollment = backupPath is null
                ? Path.Combine(root, "backup", DateTime.Now.ToString("yyyyMMdd-HHmm"))
                : backupPath;
            Directory.CreateDirectory(backupEnrollment);
            CopyDirectory(enrollmentDir, Path.Combine(backupEnrollment, "LocalAppData-NinjaNexus"));
            TryRemove(enrollmentDir, removed, log);
        }

        var repair = RepairBinaries(root, log);
        removed.AddRange(repair.RemovedPaths);

        return new CleanupResult
        {
            Success = true,
            Message = "Fresh POC reset fuldført — data backed up og nulstillet.",
            BackupPath = backupPath,
            RemovedPaths = removed,
        };
    }

    public static FileLockCheckResult CheckFileLocks(string installRoot)
    {
        var locked = new List<string>();
        var root = Path.GetFullPath(installRoot);

        foreach (var rel in new[] { "shell\\NexusShell.exe", "identity\\NexusIdentity.Api.exe" })
        {
            var path = Path.Combine(root, rel);
            if (!File.Exists(path))
            {
                continue;
            }

            if (IsFileLocked(path))
            {
                locked.Add(rel);
            }
        }

        return new FileLockCheckResult
        {
            IsLocked = locked.Count > 0,
            LockedPaths = locked,
        };
    }

    public static string ComputePayloadSha256(string installRoot)
    {
        var shellExe = Path.Combine(installRoot, "shell", "NexusShell.exe");
        if (!File.Exists(shellExe))
        {
            return "—";
        }

        using var stream = File.OpenRead(shellExe);
        var hash = SHA256.HashData(stream);
        return Convert.ToHexString(hash).ToLowerInvariant();
    }

    private static void RemoveStaleTempExtracts(string root, List<string> removed, IProgress<string>? log)
    {
        foreach (var pattern in new[] { "nexus-payload-*.zip", "nexus-embedded-*.zip" })
        {
            foreach (var file in Directory.GetFiles(Path.GetTempPath(), pattern))
            {
                TryRemove(file, removed, log);
            }
        }

        var pendingZip = Path.Combine(root, "pending-update.zip");
        if (File.Exists(pendingZip))
        {
            TryRemove(pendingZip, removed, log);
        }
    }

    private static void RemoveBadShortcuts(string installRoot, IProgress<string>? log)
    {
        var desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
        var shortcut = Path.Combine(desktop, "Ninja Nexus.lnk");
        if (File.Exists(shortcut))
        {
            try
            {
                File.Delete(shortcut);
                log?.Report("Fjernede gammel desktop-genvej.");
            }
            catch
            {
                // non-fatal
            }
        }
    }

    private static void ClearStuckUpdateJobs(IProgress<string>? log)
    {
        var updateDir = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "NinjaNexus",
            "update");

        foreach (var file in new[] { "pending-job.json", "pending-apply.json" })
        {
            var path = Path.Combine(updateDir, file);
            if (File.Exists(path))
            {
                try
                {
                    File.Delete(path);
                    log?.Report($"Ryddede stuck update job: {file}");
                }
                catch
                {
                    // ignore
                }
            }
        }
    }

    private static bool IsFileLocked(string path)
    {
        try
        {
            using var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
            return false;
        }
        catch (IOException)
        {
            return true;
        }
        catch
        {
            return false;
        }
    }

    private static void TryRemove(string path, List<string> removed, IProgress<string>? log)
    {
        try
        {
            if (Directory.Exists(path))
            {
                Directory.Delete(path, recursive: true);
            }
            else if (File.Exists(path))
            {
                File.Delete(path);
            }
            else
            {
                return;
            }

            removed.Add(path);
            log?.Report($"Fjernede: {path}");
        }
        catch (Exception ex)
        {
            log?.Report($"Kunne ikke fjerne {path}: {ex.Message}");
        }
    }

    private static void CopyDirectory(string source, string destination)
    {
        Directory.CreateDirectory(destination);
        foreach (var dir in Directory.GetDirectories(source, "*", SearchOption.AllDirectories))
        {
            Directory.CreateDirectory(dir.Replace(source, destination, StringComparison.OrdinalIgnoreCase));
        }

        foreach (var file in Directory.GetFiles(source, "*", SearchOption.AllDirectories))
        {
            var dest = file.Replace(source, destination, StringComparison.OrdinalIgnoreCase);
            Directory.CreateDirectory(Path.GetDirectoryName(dest)!);
            File.Copy(file, dest, overwrite: true);
        }
    }
}

internal sealed class FileLockCheckResult
{
    public bool IsLocked { get; init; }
    public IReadOnlyList<string> LockedPaths { get; init; } = [];
}
