using Bunit;
using Moq;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using SilverOS.Welcome.App.Components;
using SilverOS.Welcome.App.Components.Steps;
using SilverOS.Welcome.Core.Apply;
using SilverOS.Welcome.Core.Apps;
using SilverOS.Welcome.Core.Flavours;
using SilverOS.Welcome.Core.Preconfig;
using Xunit;

public class ApplyStepTests : TestContext
{
    // The component loads the catalog from AppContext.BaseDirectory/apps; no catalog.json
    // is staged in the test bin, so the real AppCatalog degrades to an empty list — which is
    // exactly what these tests want (no apps selected → empty Apps on the request).
    private static void AddCatalog(IServiceCollection services) =>
        services.AddSingleton<IAppCatalog>(new AppCatalog());

    // ApplyStep injects IPreconfigStore to clear the pin + mark configured after a
    // successful apply; a no-op fake keeps these UI tests off the real filesystem.
    private static void AddPreconfig(IServiceCollection services) =>
        services.AddSingleton<IPreconfigStore>(new FakePreconfigStore());

    private sealed class FakePreconfigStore : IPreconfigStore
    {
        public Preconfig? Load() => null;
        public void ClearPin() { }
        public bool IsConfigured() => false;
        public void MarkConfigured() { }
    }

    [Fact]
    public async Task Calls_apply_with_the_wizard_selections()
    {
        var apply = new Mock<IApplyService>();
        var state = new WizardState
        {
            Flavour = new FlavourManifest { Id = "daily-driver", Hardening = new() { Modules = new[] { "00" } } },
            BitLockerPin = "123456"
        };
        Services.AddSingleton(state);
        Services.AddSingleton(apply.Object);
        AddCatalog(Services);
        AddPreconfig(Services);
        var cut = RenderComponent<ApplyStep>();
        await cut.InvokeAsync(() => cut.Instance.StartAsync());
        apply.Verify(a => a.RunAsync(
            It.Is<ApplyRequest>(r => r.BitLockerPin == "123456" && r.Flavour.Id == "daily-driver"),
            It.IsAny<IProgress<ApplyProgress>>(),
            It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task OnComplete_invoked_when_apply_succeeds()
    {
        var apply = new Mock<IApplyService>();
        apply.Setup(a => a.RunAsync(It.IsAny<ApplyRequest>(), It.IsAny<IProgress<ApplyProgress>>(), It.IsAny<CancellationToken>()))
             .Returns(Task.CompletedTask);
        var state = new WizardState
        {
            Flavour = new FlavourManifest { Id = "daily-driver", Hardening = new() { Modules = new[] { "00" } } },
            BitLockerPin = "123456"
        };
        Services.AddSingleton(state);
        Services.AddSingleton(apply.Object);
        AddCatalog(Services);
        AddPreconfig(Services);
        var completed = false;
        var cut = RenderComponent<ApplyStep>(p => p.Add(s => s.OnComplete, EventCallback.Factory.Create(this, () => { completed = true; })));
        await cut.InvokeAsync(() => cut.Instance.StartAsync());
        Assert.True(completed);
    }

    [Fact]
    public void AutoStart_triggers_apply_once_without_a_button_click()
    {
        var apply = new Mock<IApplyService>();
        apply.Setup(a => a.RunAsync(It.IsAny<ApplyRequest>(), It.IsAny<IProgress<ApplyProgress>>(), It.IsAny<CancellationToken>()))
             .Returns(Task.CompletedTask);
        var state = new WizardState
        {
            Flavour = new FlavourManifest { Id = "daily-driver", Hardening = new() { Modules = new[] { "00" } } },
            BitLockerPin = "123456"
        };
        Services.AddSingleton(state);
        Services.AddSingleton(apply.Object);
        AddCatalog(Services);
        AddPreconfig(Services);

        // AutoStart=true should fire StartAsync from OnAfterRenderAsync on first render,
        // with no Start button click.
        var cut = RenderComponent<ApplyStep>(p => p.Add(s => s.AutoStart, true));

        cut.WaitForAssertion(() =>
            apply.Verify(a => a.RunAsync(
                It.Is<ApplyRequest>(r => r.Flavour.Id == "daily-driver"),
                It.IsAny<IProgress<ApplyProgress>>(),
                It.IsAny<CancellationToken>()), Times.Once));
    }

    [Fact]
    public async Task Shows_error_and_retry_button_when_apply_fails()
    {
        var apply = new Mock<IApplyService>();
        apply.Setup(a => a.RunAsync(It.IsAny<ApplyRequest>(), It.IsAny<IProgress<ApplyProgress>>(), It.IsAny<CancellationToken>()))
             .ThrowsAsync(new InvalidOperationException("Module 03 failed"));
        var state = new WizardState
        {
            Flavour = new FlavourManifest { Id = "daily-driver", Hardening = new() { Modules = new[] { "00" } } },
            BitLockerPin = "123456"
        };
        Services.AddSingleton(state);
        Services.AddSingleton(apply.Object);
        AddCatalog(Services);
        AddPreconfig(Services);
        var cut = RenderComponent<ApplyStep>();
        await cut.InvokeAsync(() => cut.Instance.StartAsync());
        Assert.Contains("Module 03 failed", cut.Markup);
        Assert.NotNull(cut.Find(".btn-retry"));
    }
}
