using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Api.Security;
using Tiku.Application.Security;

namespace Tiku.IntegrationTests.Api;

public sealed class RbacAuthorizationTests
{
    [Fact]
    public async Task TenantPolicy_requires_current_membership_and_permission()
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var snapshot = Snapshot(
            userId,
            tenantId,
            tenantPermissions: [BackendPermissions.TenantRoleManage]);
        await using var provider = Services(snapshot);
        var authorization = provider.GetRequiredService<IAuthorizationService>();

        var allowed = await authorization.AuthorizeAsync(
            Principal(userId, "tenant", tenantId),
            null,
            BackendPermissions.TenantRoleManage);
        var wrongTenant = await authorization.AuthorizeAsync(
            Principal(userId, "tenant", Guid.NewGuid()),
            null,
            BackendPermissions.TenantRoleManage);

        Assert.True(allowed.Succeeded);
        Assert.False(wrongTenant.Succeeded);
    }

    [Fact]
    public async Task PlatformPolicy_RejectsTenantRealmEvenWhenUserHasPlatformPermission()
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var snapshot = Snapshot(
            userId,
            tenantId,
            platformPermissions: [BackendPermissions.PlatformRoleManage]);
        await using var provider = Services(snapshot);
        var authorization = provider.GetRequiredService<IAuthorizationService>();

        var tenantRealm = await authorization.AuthorizeAsync(
            Principal(userId, "tenant", tenantId),
            null,
            BackendPermissions.PlatformRoleManage);
        var platformRealm = await authorization.AuthorizeAsync(
            Principal(userId, "platform", null),
            null,
            BackendPermissions.PlatformRoleManage);

        Assert.False(tenantRealm.Succeeded);
        Assert.True(platformRealm.Succeeded);
    }

    [Fact]
    public async Task PermissionPolicy_DoesNotUseJwtRoleClaims()
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var snapshot = Snapshot(userId, tenantId);
        await using var provider = Services(snapshot);
        var authorization = provider.GetRequiredService<IAuthorizationService>();
        var principal = Principal(userId, "tenant", tenantId);
        ((ClaimsIdentity)principal.Identity!).AddClaim(new Claim(ClaimTypes.Role, "TenantOwner"));

        var result = await authorization.AuthorizeAsync(
            principal,
            null,
            BackendPermissions.TenantRoleManage);

        Assert.False(result.Succeeded);
    }

    [Fact]
    public async Task AllScopePolicy_RejectsRestrictedOrSelfDataScope()
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var principal = Principal(userId, "tenant", tenantId);
        var selfSnapshot = Snapshot(
            userId,
            tenantId,
            tenantPermissions: [BackendPermissions.TenantContentManage]);
        await using var selfProvider = Services(selfSnapshot);
        var denied = await selfProvider.GetRequiredService<IAuthorizationService>().AuthorizeAsync(
            principal,
            null,
            TikuPolicies.TenantContentManageAllScope);

        var allScope = new CurrentDataScope(DataScopeMode.All, new HashSet<Guid>(), new HashSet<Guid>(), true);
        var allSnapshot = Snapshot(
            userId,
            tenantId,
            tenantPermissions: [BackendPermissions.TenantContentManage],
            dataScope: allScope);
        await using var allProvider = Services(allSnapshot);
        var allowed = await allProvider.GetRequiredService<IAuthorizationService>().AuthorizeAsync(
            principal,
            null,
            TikuPolicies.TenantContentManageAllScope);

        Assert.False(denied.Succeeded);
        Assert.True(allowed.Succeeded);
    }

    [Fact]
    public async Task ResourceRequirement_UsesOwnerRegionClassAndTenantBoundary()
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var regionId = Guid.NewGuid();
        var classId = Guid.NewGuid();
        var scope = new CurrentDataScope(
            DataScopeMode.Restricted,
            new HashSet<Guid> { regionId },
            new HashSet<Guid> { classId },
            true);
        await using var provider = Services(Snapshot(userId, tenantId, dataScope: scope));
        var authorization = provider.GetRequiredService<IAuthorizationService>();
        var principal = Principal(userId, "tenant", tenantId);
        var requirement = new TenantResourceAccessRequirement();

        var own = await authorization.AuthorizeAsync(
            principal,
            new TenantResourceAuthorizationResource(tenantId, OwnerUserId: userId),
            requirement);
        var region = await authorization.AuthorizeAsync(
            principal,
            new TenantResourceAuthorizationResource(tenantId, RegionId: regionId),
            requirement);
        var @class = await authorization.AuthorizeAsync(
            principal,
            new TenantResourceAuthorizationResource(tenantId, ClassId: classId),
            requirement);
        var outside = await authorization.AuthorizeAsync(
            principal,
            new TenantResourceAuthorizationResource(tenantId, RegionId: Guid.NewGuid()),
            requirement);
        var otherTenant = await authorization.AuthorizeAsync(
            principal,
            new TenantResourceAuthorizationResource(Guid.NewGuid(), OwnerUserId: userId),
            requirement);

        Assert.True(own.Succeeded);
        Assert.True(region.Succeeded);
        Assert.True(@class.Succeeded);
        Assert.False(outside.Succeeded);
        Assert.False(otherTenant.Succeeded);
    }

    [Fact]
    public async Task BackofficeBootstrapPolicies_RequireCurrentRealmAndEffectiveAccess()
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var tenantSnapshot = Snapshot(userId, tenantId);
        await using var tenantProvider = Services(tenantSnapshot);
        var tenantAuthorization = tenantProvider.GetRequiredService<IAuthorizationService>();
        var tenantAllowed = await tenantAuthorization.AuthorizeAsync(
            Principal(userId, "tenant", tenantId),
            null,
            TikuPolicies.TenantBackofficeBootstrap);

        var platformSnapshot = Snapshot(
            userId,
            null,
            platformPermissions: [BackendPermissions.PlatformDashboardView]);
        await using var platformProvider = Services(platformSnapshot);
        var platformAuthorization = platformProvider.GetRequiredService<IAuthorizationService>();
        var platformAllowed = await platformAuthorization.AuthorizeAsync(
            Principal(userId, "platform", null),
            null,
            TikuPolicies.PlatformBackofficeBootstrap);
        var tenantRealmDenied = await platformAuthorization.AuthorizeAsync(
            Principal(userId, "tenant", tenantId),
            null,
            TikuPolicies.PlatformBackofficeBootstrap);

        Assert.True(tenantAllowed.Succeeded);
        Assert.True(platformAllowed.Succeeded);
        Assert.False(tenantRealmDenied.Succeeded);
    }

    private static ServiceProvider Services(CurrentAccessSnapshot snapshot)
    {
        var services = new ServiceCollection();
        services.AddLogging();
        services.AddSingleton<ICurrentAccessContext>(new StubCurrentAccessContext(snapshot));
        services.AddTikuRbacAuthorization();
        return services.BuildServiceProvider();
    }

    private static ClaimsPrincipal Principal(
        Guid userId,
        string realm,
        Guid? tenantId)
    {
        var claims = new List<Claim>
        {
            new(TikuClaimTypes.UserId, userId.ToString()),
            new(TikuClaimTypes.Realm, realm)
        };
        if (tenantId.HasValue)
        {
            claims.Add(new Claim(TikuClaimTypes.TenantId, tenantId.Value.ToString()));
        }

        return new ClaimsPrincipal(new ClaimsIdentity(claims, "test"));
    }

    private static CurrentAccessSnapshot Snapshot(
        Guid userId,
        Guid? tenantId,
        IEnumerable<string>? tenantPermissions = null,
        IEnumerable<string>? platformPermissions = null,
        CurrentDataScope? dataScope = null)
    {
        return new CurrentAccessSnapshot(
            userId,
            tenantId,
            true,
            tenantId.HasValue,
            (tenantPermissions ?? []).ToHashSet(StringComparer.Ordinal),
            (platformPermissions ?? []).ToHashSet(StringComparer.Ordinal),
            dataScope ?? CurrentDataScope.Self);
    }

    private sealed class StubCurrentAccessContext(CurrentAccessSnapshot snapshot) : ICurrentAccessContext
    {
        public Task<CurrentAccessSnapshot> GetAsync(CancellationToken cancellationToken = default) =>
            Task.FromResult(snapshot);
    }
}
