using System.Security.Claims;
using Bunit;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Security;
using ZB.MOM.WW.ScadaBridge.Security;
using ApiKeys = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Admin.ApiKeys;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Admin;

/// <summary>
/// Inbound-API key re-arch (C3): the API Keys list page now reads keys from the
/// <see cref="IInboundApiKeyAdmin"/> seam (string KeyId + method-scopes) instead of the SQL
/// Server ApiKey entity. There is no retrievable hash, so the old masked Key-Hash column is gone.
/// </summary>
public class ApiKeysListPageTests : BunitContext
{
    private readonly IInboundApiKeyAdmin _admin = Substitute.For<IInboundApiKeyAdmin>();

    public ApiKeysListPageTests()
    {
        Services.AddSingleton(_admin);
        Services.AddSingleton<IDialogService>(Substitute.For<IDialogService>());

        var claims = new[]
        {
            new Claim(JwtTokenService.UsernameClaimType, "admin"),
            new Claim(JwtTokenService.RoleClaimType, "Administrator"),
        };
        var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
        Services.AddSingleton<AuthenticationStateProvider>(new TestAuthStateProvider(user));
        Services.AddAuthorizationCore();
        AuthorizationPolicies.AddScadaBridgeAuthorization(Services);
    }

    private static InboundApiKeyInfo Key(
        string keyId, string name, bool enabled, params string[] methods) =>
        new(keyId, name, enabled, methods, DateTimeOffset.UnixEpoch, null);

    [Fact]
    public void List_RendersKeysFromSeam_WithNoKeyHashColumn()
    {
        _admin.ListAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<InboundApiKeyInfo>>(new[]
            {
                Key("aaaaaaaaaaaa1111", "Orders-Integration", true, "PlaceOrder", "GetStatus"),
                Key("bbbbbbbbbbbb2222", "Disabled-Key", false, "Ping"),
            }));

        var cut = Render<ApiKeys>();

        cut.WaitForAssertion(() =>
        {
            // Both key names render.
            Assert.Contains("Orders-Integration", cut.Markup);
            Assert.Contains("Disabled-Key", cut.Markup);

            // The disabled key carries the Disabled badge.
            Assert.Contains("Disabled", cut.Markup);

            // No Key-Hash column header.
            var headers = cut.FindAll("th").Select(h => h.TextContent.Trim()).ToList();
            Assert.DoesNotContain(headers, h => h.Contains("Hash", StringComparison.OrdinalIgnoreCase));
            Assert.Contains(headers, h => h == "Key ID");
            Assert.Contains(headers, h => h == "Methods");

            // KeyId renders truncated (first 12 chars + ellipsis), not the full value.
            Assert.Contains("aaaaaaaaaaaa…", cut.Markup);

            // The Methods column shows the per-key scope count (2 for the first key).
            var cells = cut.FindAll("td").Select(c => c.TextContent.Trim()).ToList();
            Assert.Contains("2", cells);
        });
    }

    [Fact]
    public async Task ToggleKey_CallsSetEnabledOnSeam()
    {
        _admin.ListAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<InboundApiKeyInfo>>(new[]
            {
                Key("aaaaaaaaaaaa1111", "Orders-Integration", true, "PlaceOrder"),
            }));

        var cut = Render<ApiKeys>();

        // The dropdown's first item toggles enabled state (currently enabled -> Disable).
        var disableButton = cut.WaitForElement("button.dropdown-item");
        await disableButton.ClickAsync(new());

        await _admin.Received(1).SetEnabledAsync("aaaaaaaaaaaa1111", false, Arg.Any<CancellationToken>());
    }
}
