using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Security.Auth;

namespace ZB.MOM.WW.OtOpcUa.Security.Tests;

public class AutoLoginAuthenticationHandlerTests
{
    private static async Task<AutoLoginAuthenticationHandler> BuildHandlerAsync(string user = "multi-role-test")
    {
        var schemeOpts = new StubOptionsMonitor<AuthenticationSchemeOptions>(new AuthenticationSchemeOptions());
        var disableOpts = Options.Create(new AuthDisableLoginOptions { DisableLogin = true, User = user });

        var handler = new AutoLoginAuthenticationHandler(
            schemeOpts, NullLoggerFactory.Instance, UrlEncoder.Default, disableOpts);
        await handler.InitializeAsync(
            new AuthenticationScheme(
                CookieAuthenticationDefaults.AuthenticationScheme, null, typeof(AutoLoginAuthenticationHandler)),
            new DefaultHttpContext());
        return handler;
    }

    private static async Task<AuthenticateResult> AuthenticateAsync(string user = "multi-role-test")
    {
        var handler = await BuildHandlerAsync(user);
        return await handler.AuthenticateAsync();
    }

    [Fact]
    public async Task Authenticates_as_configured_user_with_all_roles()
    {
        var result = await AuthenticateAsync();

        result.Succeeded.ShouldBeTrue();
        result.Principal!.Identity!.IsAuthenticated.ShouldBeTrue();
        result.Principal.Identity.Name.ShouldBe("multi-role-test");
        foreach (var role in DevAuthRoles.All)
            result.Principal.IsInRole(role).ShouldBeTrue();
        // Satisfies the FleetAdmin (Administrator) + DriverOperator (Operator|Administrator) policies.
        result.Principal.IsInRole("Administrator").ShouldBeTrue();
        result.Principal.IsInRole("Operator").ShouldBeTrue();
    }

    [Fact]
    public async Task Honours_configured_username()
    {
        var result = await AuthenticateAsync("custom-dev");
        result.Principal!.Identity!.Name.ShouldBe("custom-dev");
    }

    [Fact]
    public async Task SignIn_and_SignOut_are_noops_and_do_not_throw()
    {
        var handler = await BuildHandlerAsync();
        await Should.NotThrowAsync(async () =>
        {
            await ((IAuthenticationSignInHandler)handler).SignInAsync(new ClaimsPrincipal(), null);
            await ((IAuthenticationSignOutHandler)handler).SignOutAsync(null);
        });
    }

    /// <summary>
    ///     Minimal <see cref="IOptionsMonitor{TOptions}"/> stub returning a fixed value for any
    ///     name. The test project does not reference Moq, so the scheme-options monitor the base
    ///     <see cref="AuthenticationHandler{TOptions}"/> needs is hand-rolled here.
    /// </summary>
    private sealed class StubOptionsMonitor<T>(T value) : IOptionsMonitor<T>
    {
        public T CurrentValue { get; } = value;

        public T Get(string? name) => CurrentValue;

        public IDisposable? OnChange(Action<T, string?> listener) => null;
    }
}
