using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using SilverOS.Welcome.Core.Apps;
using SilverOS.Welcome.Core.Apply;

public class AppInstallerTests
{
    static AppCatalogEntry App(string id, string winget, string? cfg = null) =>
        new() { Id = id, Name = id, Source = new AppSource { Winget = winget }, Configure = cfg };

    static Mock<IProcessRunner> Runner(int exit = 0)
    {
        var m = new Mock<IProcessRunner>();
        m.Setup(r => r.RunAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
         .ReturnsAsync(new ProcessResult(exit, "", ""));
        return m;
    }

    [Fact]
    public async Task Installs_each_selected_app_via_winget()
    {
        var run = Runner();
        run.Setup(r => r.RunAsync("winget", It.Is<string>(s => s.Contains("--version")), It.IsAny<CancellationToken>()))
           .ReturnsAsync(new ProcessResult(0, "v1.8", ""));
        var sut = new AppInstaller(run.Object, "C:\\apps");
        var res = await sut.InstallAsync(new[] { App("vscodium", "VSCodium.VSCodium") },
                                         new Progress<ApplyProgress>(_ => { }));
        run.Verify(r => r.RunAsync("winget", It.Is<string>(s =>
            s.Contains("install") && s.Contains("VSCodium.VSCodium") && s.Contains("--silent")),
            It.IsAny<CancellationToken>()), Times.Once);
        Assert.True(res.Single().Installed);
    }

    [Fact]
    public async Task Bootstraps_winget_when_absent()
    {
        var run = Runner();
        // Any winget probe (bare name OR the WindowsApps alias path) reports absent.
        run.Setup(r => r.RunAsync(It.IsAny<string>(), It.Is<string>(s => s.Contains("--version")), It.IsAny<CancellationToken>()))
           .ReturnsAsync(new ProcessResult(1, "", "not found"));
        var sut = new AppInstaller(run.Object, "C:\\apps");
        await sut.InstallAsync(new[] { App("tb", "Mozilla.Thunderbird") }, new Progress<ApplyProgress>(_ => { }));
        run.Verify(r => r.RunAsync("powershell.exe", It.Is<string>(s =>
            s.Contains("DesktopAppInstaller")), It.IsAny<CancellationToken>()), Times.AtLeastOnce);
    }

    [Fact]
    public async Task One_app_failure_does_not_stop_the_rest()
    {
        var run = new Mock<IProcessRunner>();
        run.Setup(r => r.RunAsync("winget", It.Is<string>(s => s.Contains("--version")), It.IsAny<CancellationToken>()))
           .ReturnsAsync(new ProcessResult(0, "v1.8", ""));
        run.Setup(r => r.RunAsync("winget", It.Is<string>(s => s.Contains("Bad.App")), It.IsAny<CancellationToken>()))
           .ReturnsAsync(new ProcessResult(1, "", "fail"));
        run.Setup(r => r.RunAsync("winget", It.Is<string>(s => s.Contains("Good.App")), It.IsAny<CancellationToken>()))
           .ReturnsAsync(new ProcessResult(0, "", ""));
        var sut = new AppInstaller(run.Object, "C:\\apps");
        var res = await sut.InstallAsync(new[] { App("bad", "Bad.App"), App("good", "Good.App") },
                                         new Progress<ApplyProgress>(_ => { }));
        Assert.False(res.First(r => r.Id == "bad").Installed);
        Assert.True(res.First(r => r.Id == "good").Installed);
    }

    [Fact]
    public async Task Winget_launch_exception_does_not_crash_apply_and_skips_installs()
    {
        // On IoT LTSC winget is absent, so Process.Start throws Win32Exception instead of
        // returning a non-zero code. The installer must swallow it, skip installs, and NOT throw.
        var run = new Mock<IProcessRunner>();
        run.Setup(r => r.RunAsync(It.IsAny<string>(), It.Is<string>(s => s.Contains("--version")), It.IsAny<CancellationToken>()))
           .ThrowsAsync(new System.ComponentModel.Win32Exception("The system cannot find the file specified."));
        run.Setup(r => r.RunAsync("powershell.exe", It.IsAny<string>(), It.IsAny<CancellationToken>()))
           .ReturnsAsync(new ProcessResult(0, "", ""));
        var sut = new AppInstaller(run.Object, "C:\\apps");

        var res = await sut.InstallAsync(new[] { App("vscodium", "VSCodium.VSCodium"), App("git", "Git.Git") },
                                         new Progress<ApplyProgress>(_ => { }));

        Assert.Equal(2, res.Count);
        Assert.All(res, r => Assert.False(r.Installed));
        // never attempted an install once winget was unresolved
        run.Verify(r => r.RunAsync(It.IsAny<string>(), It.Is<string>(s => s.Contains("install --id")), It.IsAny<CancellationToken>()), Times.Never);
    }

    [Fact]
    public async Task App_install_exception_is_isolated_from_the_rest()
    {
        var run = new Mock<IProcessRunner>();
        run.Setup(r => r.RunAsync(It.IsAny<string>(), It.Is<string>(s => s.Contains("--version")), It.IsAny<CancellationToken>()))
           .ReturnsAsync(new ProcessResult(0, "v1.8", ""));
        run.Setup(r => r.RunAsync(It.IsAny<string>(), It.Is<string>(s => s.Contains("Bad.App")), It.IsAny<CancellationToken>()))
           .ThrowsAsync(new System.ComponentModel.Win32Exception("boom"));
        run.Setup(r => r.RunAsync(It.IsAny<string>(), It.Is<string>(s => s.Contains("Good.App")), It.IsAny<CancellationToken>()))
           .ReturnsAsync(new ProcessResult(0, "", ""));
        var sut = new AppInstaller(run.Object, "C:\\apps");

        var res = await sut.InstallAsync(new[] { App("bad", "Bad.App"), App("good", "Good.App") },
                                         new Progress<ApplyProgress>(_ => { }));

        Assert.False(res.First(r => r.Id == "bad").Installed);
        Assert.True(res.First(r => r.Id == "good").Installed);
    }
}
