using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Data.Sqlite;
using YMhut.Box.Core.App;
using YMhut.Box.Core.Startup;

namespace YMhut.Box.Tests;

[TestClass]
[DoNotParallelize]
public sealed class StartupCheckTests
{
    [TestMethod]
    public async Task FastPreflightPassesForPureLangInstallLayout()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-fast");
        var installRoot = CreateHealthyInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var store = new StartupCheckStore(paths);
        var service = new InstallIntegrityCheckService(paths, store);

        var report = await service.RunFastPreflightAsync();

        Assert.AreEqual(StartupCheckKind.FastPreflight, report.Kind);
        Assert.AreEqual(0, report.CriticalIssueCount);
        Assert.IsTrue(report.Items.Any(item => item.Id == "lang:zh-CN" && item.Status == StartupCheckStatus.Passed));
        Assert.IsTrue(report.Items.Any(item => item.Id == "lang:en-US" && item.Status == StartupCheckStatus.Passed));
        Assert.IsFalse(Directory.Exists(Path.Combine(appRoot, "Runtime")));
        StringAssert.StartsWith(store.DatabasePath, Path.Combine(appRoot, "Logs"));
    }

    [TestMethod]
    public async Task FastPreflightDoesNotBlockDevelopmentRootCultureLayout()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-dev-layout");
        var installRoot = CreateDevelopmentInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));

        var report = await service.RunFastPreflightAsync();
        var resourcesPri = report.Items.FirstOrDefault(item => item.Id == "file:resources.pri");
        var zhCn = report.Items.FirstOrDefault(item => item.Id == "lang:zh-CN");
        var enUs = report.Items.FirstOrDefault(item => item.Id == "lang:en-US");

        Assert.AreEqual(0, report.CriticalIssueCount);
        Assert.IsNotNull(resourcesPri);
        Assert.AreEqual(StartupCheckStatus.Missing, resourcesPri.Status);
        Assert.AreEqual(StartupCheckSeverity.Warning, resourcesPri.Severity);
        Assert.IsNotNull(zhCn);
        Assert.AreEqual(StartupCheckStatus.Passed, zhCn.Status);
        Assert.IsNotNull(enUs);
        Assert.AreEqual(StartupCheckStatus.Passed, enUs.Status);
        Assert.IsFalse(report.Items.Any(item => item.Id.StartsWith("lang:root:", StringComparison.OrdinalIgnoreCase)));
    }

    [TestMethod]
    public async Task FastPreflightAcceptsWinUiDevelopmentPriAndRootCultureSatellites()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-dev-pri");
        var installRoot = CreateDevelopmentInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");
        workspace.CreateFile(["install", "YMhutBox.pri"], string.Empty);
        workspace.CreateFile(["install", "fr-FR", "Microsoft.ui.xaml.dll.mui"], string.Empty);
        workspace.CreateFile(["install", "ja-JP", "Microsoft.ui.xaml.dll.mui"], string.Empty);
        workspace.CreateFile(["install", "Tools", "Sample", "tool.exe"], string.Empty);
        workspace.CreateFile(["install", "Metadata", "tools.json"], """{ "tools": [] }""");

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));

        var report = await service.RunFastPreflightAsync();

        Assert.AreEqual(0, report.VisibleIssueCount);
        Assert.IsTrue(report.Items.Any(item => item.Id == "manifest" && item.Status == StartupCheckStatus.Skipped));
        Assert.IsTrue(report.Items.Any(item => item.Id == "file:resources.pri" && item.Status == StartupCheckStatus.Passed));
        Assert.IsTrue(report.Items.Any(item => item.Id == "lang:root-culture-summary" && item.Status == StartupCheckStatus.Skipped));
        Assert.IsFalse(report.Items.Any(item => item.Id.StartsWith("lang:root:", StringComparison.OrdinalIgnoreCase)));
    }

    [TestMethod]
    public async Task CurrentHistoryNormalizesLegacyDevelopmentLayoutFalsePositives()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-legacy-false-positive");
        var installRoot = CreateDevelopmentInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");
        workspace.CreateFile(["install", "YMhutBox.pri"], string.Empty);
        workspace.CreateFile(["install", "fr-FR", "Microsoft.ui.xaml.dll.mui"], string.Empty);
        workspace.CreateFile(["install", "Tools", "Sample", "tool.exe"], string.Empty);
        workspace.CreateFile(["install", "Metadata", "tools.json"], """{ "tools": [] }""");

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var store = new StartupCheckStore(paths);
        var manifestIdentity = InstallRootContext.BuildManifestIdentity(installRoot);
        var oldReport = StartupCheckReport.Create(
            StartupCheckKind.FastPreflight,
            new DateTimeOffset(2026, 6, 1, 8, 0, 0, TimeSpan.Zero),
            installRoot,
            manifestIdentity,
            InstallRootContext.BuildInstallIdentity(installRoot, manifestIdentity),
            $"installRoot={installRoot}; manifest={manifestIdentity}",
            [
                new StartupCheckItem("manifest", "安装清单", StartupCheckSeverity.Warning, StartupCheckStatus.Missing, "文件缺失", Path.Combine(installRoot, "config", "install-manifest.ini")),
                new StartupCheckItem("file:resources.pri", "resources.pri", StartupCheckSeverity.Warning, StartupCheckStatus.Missing, "文件缺失", Path.Combine(installRoot, "resources.pri")),
                new StartupCheckItem("lang:zh-CN", "语言资源 zh-CN", StartupCheckSeverity.Warning, StartupCheckStatus.Missing, "语言目录缺失", Path.Combine(installRoot, "lang", "zh-CN")),
                new StartupCheckItem("lang:root:fr-FR", "根目录语言资源 fr-FR", StartupCheckSeverity.Warning, StartupCheckStatus.Failed, "旧版误判", Path.Combine(installRoot, "fr-FR"))
            ]);
        await store.SaveAsync(oldReport);

        var service = new InstallIntegrityCheckService(paths, store);

        var latest = await service.GetLatestCurrentReportAsync(StartupCheckKind.FastPreflight);
        var history = await service.GetCurrentHistoryAsync(10);

        Assert.IsNotNull(latest);
        Assert.AreEqual(0, latest.VisibleIssueCount);
        Assert.AreEqual(StartupCheckStatus.Skipped, latest.Items.First(item => item.Id == "manifest").Status);
        Assert.AreEqual(StartupCheckStatus.Passed, latest.Items.First(item => item.Id == "file:resources.pri").Status);
        Assert.AreEqual(StartupCheckStatus.Passed, latest.Items.First(item => item.Id == "lang:zh-CN").Status);
        Assert.AreEqual(StartupCheckStatus.Skipped, latest.Items.First(item => item.Id == "lang:root:fr-FR").Status);
        Assert.AreEqual(0, history[0].VisibleIssueCount);
    }

    [TestMethod]
    public async Task MonthlyIntegrityUsesRecentReportUntilManifestIdentityChanges()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-monthly");
        var installRoot = CreateHealthyInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");
        var now = new DateTimeOffset(2026, 6, 14, 9, 0, 0, TimeSpan.Zero);

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var store = new StartupCheckStore(paths);
        var service = new InstallIntegrityCheckService(paths, store, clock: () => now);

        var first = await service.RunMonthlyIntegrityCheckAsync();
        now = now.AddDays(5);
        var cached = await service.RunMonthlyIntegrityCheckAsync();

        Assert.AreEqual(first.Id, cached.Id);

        File.AppendAllText(Path.Combine(installRoot, InstallManifest.RelativePath.Replace('/', Path.DirectorySeparatorChar)), Environment.NewLine + "; changed");
        now = now.AddDays(1);
        var changed = await service.RunMonthlyIntegrityCheckAsync();

        Assert.AreNotEqual(first.Id, changed.Id);
    }

    [TestMethod]
    public async Task MonthlyIntegrityDoesNotReuseReportFromDifferentInstallRoot()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-monthly-root");
        var installRoot = CreateHealthyInstall(workspace);
        var secondRoot = workspace.CreateDirectory("install2");
        CopyDirectory(installRoot, secondRoot);
        var appRoot = workspace.CreateDirectory("user");
        var now = new DateTimeOffset(2026, 6, 14, 9, 0, 0, TimeSpan.Zero);

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var store = new StartupCheckStore(paths);
        var service = new InstallIntegrityCheckService(paths, store, clock: () => now);
        var first = await service.RunMonthlyIntegrityCheckAsync();

        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, secondRoot);
        now = now.AddDays(1);
        var second = await service.RunMonthlyIntegrityCheckAsync();

        Assert.AreNotEqual(first.Id, second.Id);
        Assert.AreNotEqual(first.InstallIdentity, second.InstallIdentity);
    }

    [TestMethod]
    public async Task IntegrityReportsMissingExternalToolLaunchableAsWarning()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-tool-missing");
        var installRoot = CreateHealthyInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");
        Directory.CreateDirectory(Path.Combine(installRoot, "Tools", "Sample"));
        File.WriteAllText(Path.Combine(installRoot, "Tools", "Sample", "present.exe"), string.Empty);
        File.AppendAllText(
            Path.Combine(installRoot, InstallManifest.RelativePath.Replace('/', Path.DirectorySeparatorChar)),
            Environment.NewLine + "Tools/Sample/missing.exe");

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));

        var report = await service.RunManualIntegrityCheckAsync();
        var missing = report.Items.FirstOrDefault(item => item.Id == "file:Tools/Sample/missing.exe");

        Assert.IsNotNull(missing);
        Assert.AreEqual(StartupCheckStatus.Missing, missing.Status);
        Assert.AreEqual(StartupCheckSeverity.Warning, missing.Severity);
    }

    [TestMethod]
    public async Task UserDataRuntimePayloadIsRemovedBeforePreflight()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-userdata");
        var installRoot = CreateHealthyInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");
        var runtimeRoot = Path.Combine(appRoot, "Runtime");
        var runtimesRoot = Path.Combine(appRoot, "runtimes");
        Directory.CreateDirectory(runtimeRoot);
        Directory.CreateDirectory(runtimesRoot);
        File.WriteAllText(Path.Combine(runtimeRoot, "YMhutBox.exe"), string.Empty);
        File.WriteAllText(Path.Combine(runtimesRoot, "Microsoft.ui.xaml.dll"), string.Empty);

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        Assert.IsFalse(Directory.Exists(runtimeRoot));
        Assert.IsFalse(Directory.Exists(runtimesRoot));

        var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));

        var report = await service.RunFastPreflightAsync();
        var runtime = report.Items.FirstOrDefault(item => item.Id == "userdata:runtime");

        Assert.IsNotNull(runtime);
        Assert.AreEqual(StartupCheckStatus.Passed, runtime.Status);
        Assert.IsFalse(Directory.Exists(runtimeRoot));
        Assert.IsFalse(Directory.Exists(runtimesRoot));
    }

    [TestMethod]
    public async Task UserDataToolsAndMetadataPayloadsAreRemovedBeforePreflight()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-userdata-tools");
        var installRoot = CreateHealthyInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var toolsRoot = Path.Combine(appRoot, "Tools");
        var metadataRoot = Path.Combine(appRoot, "Metadata");
        Directory.CreateDirectory(toolsRoot);
        Directory.CreateDirectory(metadataRoot);
        File.WriteAllText(Path.Combine(toolsRoot, "YMhutBox.exe"), string.Empty);
        File.WriteAllText(Path.Combine(metadataRoot, "tools.json"), "{}");

        var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));

        var report = await service.RunFastPreflightAsync();
        var tools = report.Items.FirstOrDefault(item => item.Id == "userdata:tools");
        var metadata = report.Items.FirstOrDefault(item => item.Id == "userdata:metadata");

        Assert.IsNotNull(tools);
        Assert.IsNotNull(metadata);
        Assert.AreEqual(StartupCheckStatus.Passed, tools.Status);
        Assert.AreEqual(StartupCheckStatus.Passed, metadata.Status);
        Assert.IsFalse(Directory.Exists(toolsRoot));
        Assert.IsFalse(Directory.Exists(metadataRoot));
        Assert.AreEqual(0, report.VisibleIssueCount);
    }

    [TestMethod]
    public async Task CurrentHistoryFiltersOutHistoricalInstallReports()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-current-history");
        var installRoot = CreateHealthyInstall(workspace);
        var secondRoot = workspace.CreateDirectory("install2");
        CopyDirectory(installRoot, secondRoot);
        var appRoot = workspace.CreateDirectory("user");

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var store = new StartupCheckStore(paths);
        var service = new InstallIntegrityCheckService(paths, store);
        var oldReport = await service.RunManualIntegrityCheckAsync();

        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, secondRoot);
        var currentReport = await service.RunManualIntegrityCheckAsync();
        var all = await service.GetHistorySummariesAsync();
        var current = await service.GetCurrentHistoryAsync();

        Assert.IsTrue(all.Any(summary => summary.Id == oldReport.Id && !summary.IsCurrentInstall));
        Assert.IsTrue(current.All(summary => summary.IsCurrentInstall));
        Assert.IsTrue(current.Any(summary => summary.Id == currentReport.Id));
        Assert.IsFalse(current.Any(summary => summary.Id == oldReport.Id));
    }

    [TestMethod]
    public async Task RecheckItemReportsCurrentPathPassesAfterFileRestored()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-recheck");
        var installRoot = CreateHealthyInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");
        var executable = Path.Combine(installRoot, "YMhutBox.exe");
        File.Delete(executable);

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));
        var report = await service.RunManualIntegrityCheckAsync();
        var missing = report.Items.First(item => item.Id == "file:YMhutBox.exe");

        File.WriteAllText(executable, string.Empty);
        var rechecked = await service.RecheckItemAsync(report.Id, missing.Id);

        Assert.IsNotNull(rechecked);
        Assert.AreEqual(StartupCheckStatus.Passed, rechecked.Status);
        StringAssert.Contains(rechecked.Detail, "old record");
    }

    [TestMethod]
    public async Task IntegrityDoesNotTreatNormalRootFoldersAsCultureFolders()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-root-folders");
        var installRoot = CreateHealthyInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");
        Directory.CreateDirectory(Path.Combine(installRoot, "Assets"));

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));

        var report = await service.RunManualIntegrityCheckAsync();

        Assert.IsFalse(report.Items.Any(item => string.Equals(item.Id, "lang:root:Assets", StringComparison.OrdinalIgnoreCase)));
        Assert.IsFalse(report.Items.Any(item => string.Equals(item.Id, "lang:root:Tools", StringComparison.OrdinalIgnoreCase)));
    }

    [TestMethod]
    public async Task StartupCheckStorePersistsHistoryAndLatestReport()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-store");
        var installRoot = CreateHealthyInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var store = new StartupCheckStore(paths);
        var service = new InstallIntegrityCheckService(paths, store);

        var report = await service.RunManualIntegrityCheckAsync();
        var latest = await store.GetLatestReportAsync();
        var history = await store.GetHistoryAsync();
        var summaries = await store.GetHistorySummariesAsync();
        var loaded = await store.GetReportAsync(report.Id);

        Assert.IsNotNull(latest);
        Assert.AreEqual(report.Id, latest.Id);
        Assert.IsGreaterThanOrEqualTo(history.Count, 1);
        Assert.IsNotEmpty(history[0].Items);
        Assert.IsGreaterThanOrEqualTo(summaries.Count, 1);
        Assert.AreEqual(report.Id, summaries[0].Id);
        Assert.AreEqual(report.Items.Count, summaries[0].ItemCount);
        Assert.AreEqual(report.IssueCount, summaries[0].IssueCount);
        Assert.IsNotNull(loaded);
        Assert.AreEqual(report.Id, loaded.Id);
        Assert.IsNotEmpty(loaded.Items);
    }

    [TestMethod]
    public async Task StartupCheckStoreMigratesLegacyDatabaseIntoMainSqlite()
    {
        using var workspace = TestWorkspace.Create("ymhut-startup-store-migrate");
        var installRoot = CreateHealthyInstall(workspace);
        var appRoot = workspace.CreateDirectory("user");

        using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
        environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);

        var paths = AppPaths.ForCurrentUser(appRoot);
        var legacyPath = Path.Combine(paths.Data, "startup-checks.db");
        Directory.CreateDirectory(Path.GetDirectoryName(legacyPath)!);
        await using (var connection = new SqliteConnection($"Data Source={legacyPath};Pooling=False"))
        {
            connection.Open();
            await using var command = connection.CreateCommand();
            command.CommandText = """
                CREATE TABLE startup_check_reports (
                    id TEXT PRIMARY KEY,
                    kind TEXT NOT NULL,
                    started_at TEXT NOT NULL,
                    completed_at TEXT NOT NULL,
                    install_root TEXT NOT NULL,
                    manifest_identity TEXT NOT NULL,
                    issue_count INTEGER NOT NULL,
                    critical_issue_count INTEGER NOT NULL,
                    warning_issue_count INTEGER NOT NULL
                );
                CREATE TABLE startup_check_items (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    report_id TEXT NOT NULL,
                    item_id TEXT NOT NULL,
                    name TEXT NOT NULL,
                    severity TEXT NOT NULL,
                    status TEXT NOT NULL,
                    detail TEXT NOT NULL,
                    path TEXT NULL
                );
                INSERT INTO startup_check_reports(
                    id, kind, started_at, completed_at, install_root, manifest_identity,
                    issue_count, critical_issue_count, warning_issue_count)
                VALUES(
                    '11111111111111111111111111111111',
                    'FastPreflight',
                    '2026-06-01T00:00:00.0000000+00:00',
                    '2026-06-01T00:00:01.0000000+00:00',
                    'C:\Legacy',
                    'legacy',
                    1,
                    0,
                    1);
                INSERT INTO startup_check_items(report_id, item_id, name, severity, status, detail, path)
                VALUES(
                    '11111111111111111111111111111111',
                    'legacy:item',
                    '旧记录',
                    'Warning',
                    'Failed',
                    '从旧库导入',
                    NULL);
                """;
            await command.ExecuteNonQueryAsync();
        }

        var store = new StartupCheckStore(paths);
        var latest = await store.GetLatestReportAsync();

        Assert.IsNotNull(latest);
        Assert.AreEqual("legacy", latest.ManifestIdentity);
        Assert.AreEqual(1, latest.Items.Count);
        Assert.IsFalse(File.Exists(legacyPath));
        Assert.AreEqual(Path.Combine(paths.Logs, AppDatabasePaths.MainDatabaseFileName), store.DatabasePath);
    }

    [TestMethod]
    public async Task StartupInitializationPipelineReportsMonotonicWeightedProgress()
    {
        var progress = new List<StartupInitializationStageProgress>();
        var pipeline = new StartupInitializationPipeline([
            new StartupInitializationStage("first", "First", 1, true, (context, token) =>
            {
                context.Report(0.5, "Half");
                return Task.CompletedTask;
            }),
            new StartupInitializationStage("second", "Second", 3, true, (context, token) =>
            {
                context.Report(0.25, "Quarter");
                context.Report(0.75, "Almost");
                return Task.CompletedTask;
            })
        ]);

        await pipeline.RunAsync(new InlineProgress<StartupInitializationStageProgress>(progress.Add));

        Assert.IsTrue(progress.Count > 0);
        Assert.AreEqual(100, progress[^1].Progress);
        for (var index = 1; index < progress.Count; index++)
        {
            Assert.IsTrue(
                progress[index].Progress >= progress[index - 1].Progress,
                $"Progress moved backwards at {index}: {progress[index - 1].Progress} -> {progress[index].Progress}");
        }

        CollectionAssert.AreEqual(new[] { "first", "second" }, pipeline.Stages.Select(stage => stage.Id).ToArray());
    }

    [TestMethod]
    public void StartupSplashMessageSerializesStableContract()
    {
        var message = new StartupSplashMessage(
            "progress",
            "正在检查",
            42,
            "fast-preflight",
            "快速预检",
            8,
            10,
            "检查关键文件",
            "warning",
            IsRepairStep: true,
            Theme: "dark",
            ReducedMotion: true);

        var json = message.ToJson();

        StringAssert.Contains(json, "\"type\": \"progress\"");
        StringAssert.Contains(json, "\"stageId\": \"fast-preflight\"");
        StringAssert.Contains(json, "\"isRepairStep\": true");
        StringAssert.Contains(json, "\"reducedMotion\": true");
    }

    private static string CreateHealthyInstall(TestWorkspace workspace)
    {
        var installRoot = workspace.CreateDirectory("install");
        workspace.CreateFile(["install", "YMhutBox.exe"], string.Empty);
        workspace.CreateFile(["install", "YMhutBox.dll"], string.Empty);
        workspace.CreateFile(["install", "resources.pri"], string.Empty);
        workspace.CreateFile(["install", "lang", "zh-CN", "Microsoft.ui.xaml.dll.mui"], string.Empty);
        workspace.CreateFile(["install", "lang", "en-US", "Microsoft.ui.xaml.dll.mui"], string.Empty);
        workspace.CreateFile(["install", "Tools", "Sample", "tool.exe"], string.Empty);
        workspace.CreateFile(["install", "Metadata", "tools.json"], """{ "tools": [] }""");
        workspace.CreateFile(["install", "config", "install-manifest.ini"],
            """
            [Release]
            Version=2.0.6
            Build=1
            Channel=stable
            PackageVersion=2.0.6.1

            [RequiredFiles]
            YMhutBox.exe
            YMhutBox.dll
            resources.pri

            [Files]
            YMhutBox.exe
            YMhutBox.dll
            resources.pri
            lang/zh-CN/Microsoft.ui.xaml.dll.mui
            lang/en-US/Microsoft.ui.xaml.dll.mui
            Tools/Sample/tool.exe
            Metadata/tools.json
            """);
        return installRoot;
    }

    private static string CreateDevelopmentInstall(TestWorkspace workspace)
    {
        var installRoot = workspace.CreateDirectory("install");
        workspace.CreateFile(["install", "YMhutBox.exe"], string.Empty);
        workspace.CreateFile(["install", "YMhutBox.dll"], string.Empty);
        workspace.CreateFile(["install", "zh-CN", "Microsoft.ui.xaml.dll.mui"], string.Empty);
        workspace.CreateFile(["install", "en-US", "Microsoft.ui.xaml.dll.mui"], string.Empty);
        return installRoot;
    }

    private static void CopyDirectory(string source, string destination)
    {
        Directory.CreateDirectory(destination);
        foreach (var directory in Directory.EnumerateDirectories(source, "*", SearchOption.AllDirectories))
        {
            Directory.CreateDirectory(Path.Combine(destination, Path.GetRelativePath(source, directory)));
        }

        foreach (var file in Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories))
        {
            var target = Path.Combine(destination, Path.GetRelativePath(source, file));
            Directory.CreateDirectory(Path.GetDirectoryName(target)!);
            File.Copy(file, target, overwrite: true);
        }
    }

    private sealed class EnvironmentScope : IDisposable
    {
        private readonly Dictionary<string, string?> _snapshot;

        private EnvironmentScope(IEnumerable<string> names)
        {
            _snapshot = names.ToDictionary(name => name, Environment.GetEnvironmentVariable, StringComparer.Ordinal);
        }

        public static EnvironmentScope Capture(params string[] names) => new(names);

        public void Set(string name, string? value) => Environment.SetEnvironmentVariable(name, value);

        public void Dispose()
        {
            foreach (var item in _snapshot)
            {
                Environment.SetEnvironmentVariable(item.Key, item.Value);
            }
        }
    }

    private sealed class InlineProgress<T>(Action<T> report) : IProgress<T>
    {
        public void Report(T value) => report(value);
    }

    private sealed class TestWorkspace : IDisposable
    {
        private TestWorkspace(string root)
        {
            Root = root;
        }

        public string Root { get; }

        public static TestWorkspace Create(string prefix)
        {
            var root = Path.Combine(Path.GetTempPath(), prefix, Guid.NewGuid().ToString("N"));
            Directory.CreateDirectory(root);
            return new TestWorkspace(root);
        }

        public string CreateDirectory(params string[] parts)
        {
            var directory = Path.Combine([Root, .. parts]);
            Directory.CreateDirectory(directory);
            return directory;
        }

        public string CreateFile(string[] parts, string contents)
        {
            var path = Path.Combine([Root, .. parts]);
            Directory.CreateDirectory(Path.GetDirectoryName(path)!);
            File.WriteAllText(path, contents);
            return path;
        }

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