using System.IO;
using NexusShell.Models;
using NexusShell.Services;
using NexusShell.Services.Deployment;
using Xunit;

namespace NexusShell.Tests;

public sealed class NexusUpdateLockTests : IDisposable
{
    private readonly string _installRoot;
    private readonly string _jobBackup;

    public NexusUpdateLockTests()
    {
        _installRoot = Path.Combine(Path.GetTempPath(), "nxp-update-lock-" + Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(Path.Combine(_installRoot, "identity"));
        Directory.CreateDirectory(Path.Combine(_installRoot, "shell"));
        File.WriteAllText(Path.Combine(_installRoot, "identity", "e_sqlite3.dll"), "dll");
        File.WriteAllText(Path.Combine(_installRoot, "identity", "NexusIdentity.Api.exe"), "exe");
        File.WriteAllText(Path.Combine(_installRoot, "shell", "NexusShell.exe"), "exe");

        _jobBackup = NexusPlatformUpdateJobStore.Load()?.ZipPath ?? string.Empty;
        NexusPlatformUpdateJobStore.Clear();
    }

    [Fact]
    public void File_lock_checker_reports_locked_native_dll()
    {
        var lockedPath = Path.Combine(_installRoot, "identity", "e_sqlite3.dll");
        using var hold = new FileStream(lockedPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);

        var result = NexusUpdateFileLockChecker.CheckInstallLocks(_installRoot);

        Assert.False(result.CanProceed);
        Assert.Contains(result.LockedFiles, f => f.FilePath.Contains("e_sqlite3.dll"));
        Assert.Contains("låst", result.SummaryMessage, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void File_lock_checker_passes_when_files_are_free()
    {
        var result = NexusUpdateFileLockChecker.CheckInstallLocks(_installRoot);
        Assert.True(result.CanProceed);
    }

    [Fact]
    public void Process_stopper_succeeds_when_no_nexus_processes_under_root()
    {
        var result = NexusUpdateProcessStopper.StopForUpdate(_installRoot);
        Assert.True(result.Success);
        Assert.Empty(result.StillRunning);
    }

    [Fact]
    public void Stale_preparing_job_recovers_to_failed()
    {
        var staleUtc = DateTimeOffset.UtcNow.AddMinutes(-10);
        var jobDir = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "NinjaNexus",
            "update");
        Directory.CreateDirectory(jobDir);
        var jobFile = Path.Combine(jobDir, "pending-job.json");
        File.WriteAllText(jobFile, $$"""
            {
              "phase": "Preparing",
              "installRoot": "{{_installRoot.Replace("\\", "\\\\")}}",
              "startedUtc": "{{staleUtc:o}}",
              "updatedUtc": "{{staleUtc:o}}"
            }
            """);

        var recovered = NexusPlatformUpdateJobStore.RecoverStaleJob(out var message);

        Assert.Equal(ClientUpdateFlowState.Stale, recovered);
        Assert.Contains("forbereder installation", message, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Stale_applying_job_recovers_to_stale()
    {
        var staleUtc = DateTimeOffset.UtcNow.AddMinutes(-15);
        var jobDir = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "NinjaNexus",
            "update");
        Directory.CreateDirectory(jobDir);
        var jobFile = Path.Combine(jobDir, "pending-job.json");
        File.WriteAllText(jobFile, $$"""
            {
              "phase": "Applying",
              "installRoot": "{{_installRoot.Replace("\\", "\\\\")}}",
              "startedUtc": "{{staleUtc:o}}",
              "updatedUtc": "{{staleUtc:o}}"
            }
            """);

        var recovered = NexusPlatformUpdateJobStore.RecoverStaleJob(out var message);

        Assert.Equal(ClientUpdateFlowState.Stale, recovered);
        Assert.Contains("anvendelse", message, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Clear_stuck_job_removes_persisted_state()
    {
        NexusPlatformUpdateJobStore.Save(new NexusPlatformUpdateJob
        {
            Phase = PlatformUpdateJobPhase.Blocked,
            ErrorMessage = "test lock",
        });

        NexusPlatformUpdateJobStore.Clear();

        Assert.Null(NexusPlatformUpdateJobStore.Load());
    }

    [Fact]
    public void Blocked_job_recovers_with_blocked_state()
    {
        NexusPlatformUpdateJobStore.Save(new NexusPlatformUpdateJob
        {
            Phase = PlatformUpdateJobPhase.Blocked,
            ErrorMessage = "identity/e_sqlite3.dll låst",
            UpdatedUtc = DateTimeOffset.UtcNow,
        });

        var recovered = NexusPlatformUpdateJobStore.RecoverStaleJob(out var message);

        Assert.Equal(ClientUpdateFlowState.Blocked, recovered);
        Assert.Equal("identity/e_sqlite3.dll låst", message);
    }

    [Fact]
    public void Identity_db_path_lives_under_data_not_identity_publish()
    {
        var dbPath = NexusIdentityDataPaths.ResolveDatabasePath(_installRoot);
        Assert.Contains(Path.Combine("data", "identity"), dbPath);
        Assert.EndsWith("nexus-identity.db", dbPath);
    }

    [Fact]
    public void Identity_db_path_uses_install_root_before_data_folder_exists()
    {
        var root = Path.Combine(Path.GetTempPath(), "nxp-shell-first-run-" + Guid.NewGuid().ToString("N"));

        try
        {
            var dbPath = NexusIdentityDataPaths.ResolveDatabasePath(root);

            Assert.StartsWith(root, dbPath, StringComparison.OrdinalIgnoreCase);
            Assert.Contains(Path.Combine("data", "identity"), dbPath);
            Assert.True(Directory.Exists(Path.Combine(root, "data", "identity")));
        }
        finally
        {
            if (Directory.Exists(root))
            {
                Directory.Delete(root, recursive: true);
            }
        }
    }

    [Fact]
    public async Task Install_queue_times_out_long_running_job()
    {
        var queue = new NexusInstallQueue();
        await Assert.ThrowsAsync<TimeoutException>(async () =>
            await queue.EnqueueAsync(
                async ct =>
                {
                    await Task.Delay(TimeSpan.FromSeconds(30), ct);
                    return true;
                },
                TimeSpan.FromMilliseconds(200)));
    }

    public void Dispose()
    {
        NexusPlatformUpdateJobStore.Clear();
        if (Directory.Exists(_installRoot))
        {
            Directory.Delete(_installRoot, recursive: true);
        }
    }
}
