using System.Security.Claims;
using Bunit;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using ScadaLink.Commons.Entities.Templates;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.CentralUI.Components.Shared;
using ScadaLink.TemplateEngine;
using ScadaLink.TemplateEngine.Services;
using TemplatesPage = ScadaLink.CentralUI.Components.Pages.Design.Templates;

namespace ScadaLink.CentralUI.Tests;

/// <summary>
/// bUnit rendering tests for the Templates page that verify the folder/template
/// tree builds the expected DOM for the main shape categories: empty state,
/// folder-containing-template nesting, and composition leaves under their owner.
/// </summary>
public class TemplatesPageTests : BunitContext
{
    private readonly ITemplateEngineRepository _repo = Substitute.For<ITemplateEngineRepository>();
    private readonly IAuditService _audit = Substitute.For<IAuditService>();

    public TemplatesPageTests()
    {
        // The page's TemplateService / TemplateFolderService are constructed via DI
        // from the repository and audit service, mirroring real Host wiring.
        Services.AddSingleton(_repo);
        Services.AddSingleton(_audit);
        Services.AddScoped<TemplateService>();
        Services.AddScoped<TemplateFolderService>();
        // The Templates page injects IDialogService for the new-folder prompt
        // and delete confirmations. The host is rendered in MainLayout, not
        // here, but the DI registration still has to satisfy the [Inject].
        Services.AddScoped<IDialogService, DialogService>();
        AddTestAuth();

        // The TreeView inside the page persists expansion state via JS interop
        // against sessionStorage (`templates-tree` key). bUnit requires explicit
        // stubs for all JS interop calls, otherwise rendering throws.
        JSInterop.Setup<string?>("treeviewStorage.load", _ => true).SetResult(null);
        JSInterop.SetupVoid("treeviewStorage.save", _ => true);
    }

    private void AddTestAuth()
    {
        // The page resolves the current user via the "Username" claim in
        // GetCurrentUserAsync(); supply a stub so OnInitializedAsync doesn't crash.
        var claims = new[]
        {
            new Claim("Username", "tester"),
            new Claim(ClaimTypes.Role, "Design")
        };
        var identity = new ClaimsIdentity(claims, "TestAuth");
        var user = new ClaimsPrincipal(identity);
        Services.AddSingleton<AuthenticationStateProvider>(new TestAuthStateProvider(user));
        Services.AddAuthorizationCore();
    }

    [Fact]
    public void Renders_EmptyState_WhenNoTemplatesOrFolders()
    {
        _repo.GetAllTemplatesAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<Template>>(new List<Template>()));
        _repo.GetAllFoldersAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<TemplateFolder>>(new List<TemplateFolder>()));

        var cut = Render<TemplatesPage>();

        Assert.Contains("No templates yet", cut.Markup);
    }

    [Fact]
    public void Renders_FolderAndTemplate_AtCorrectNesting()
    {
        var folder = new TemplateFolder("Dev") { Id = 1 };
        var template = new Template("TestMachine") { Id = 5, FolderId = 1 };
        _repo.GetAllTemplatesAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<Template>>(new List<Template> { template }));
        _repo.GetAllFoldersAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<TemplateFolder>>(new List<TemplateFolder> { folder }));

        var cut = Render<TemplatesPage>();

        // The folder is rendered collapsed; assert the folder label is present,
        // then expand it and assert the nested template label appears.
        Assert.Contains("Dev", cut.Markup);

        var folderToggle = cut.FindAll("li[role='treeitem']")
            .FirstOrDefault(li => li.TextContent.Contains("Dev"))
            ?.QuerySelector(".tv-toggle");
        Assert.NotNull(folderToggle);
        folderToggle!.Click();

        Assert.Contains("TestMachine", cut.Markup);
    }

    [Fact]
    public void Renders_CompositionChildren_UnderOwningTemplate()
    {
        var template = new Template("TestMachine") { Id = 5 };
        template.Compositions.Add(
            new TemplateComposition("DelmiaReceiver") { Id = 10, ComposedTemplateId = 99 });
        var composed = new Template("Other") { Id = 99 };

        _repo.GetAllTemplatesAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<Template>>(new List<Template> { template, composed }));
        _repo.GetAllFoldersAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<TemplateFolder>>(new List<TemplateFolder>()));

        var cut = Render<TemplatesPage>();

        // The owning template must be expanded for its composition leaves to be
        // in the DOM — composition children only render under an expanded parent.
        var ownerToggle = cut.FindAll("li[role='treeitem']")
            .FirstOrDefault(li => li.TextContent.Contains("TestMachine"))
            ?.QuerySelector(".tv-toggle");
        Assert.NotNull(ownerToggle);
        ownerToggle!.Click();

        Assert.Contains("DelmiaReceiver", cut.Markup);
        // The composition glyph appears via Bootstrap Icons; the composed template name
        // is intentionally not rendered on the tree (V7 spec).
        Assert.Contains("bi-arrow-return-right", cut.Markup);
    }

}

internal sealed class TestAuthStateProvider : AuthenticationStateProvider
{
    private readonly ClaimsPrincipal _user;
    public TestAuthStateProvider(ClaimsPrincipal user) => _user = user;
    public override Task<AuthenticationState> GetAuthenticationStateAsync()
        => Task.FromResult(new AuthenticationState(_user));
}
