using Microsoft.AspNetCore.Components.Authorization;
using ScadaLink.Commons.Entities.Sites;
using ScadaLink.Security;

namespace ScadaLink.CentralUI.Auth;

/// <summary>
/// Resolves the set of sites the current user is permitted to operate on, from
/// the <c>SiteId</c> claims attached at login (CentralUI-002).
/// <para>
/// The design (Component-CentralUI, CLAUDE.md "Security &amp; Auth") makes the
/// Deployment role site-scoped: a Deployment user mapped through an LDAP group
/// with site-scope rules carries one <see cref="JwtTokenService.SiteIdClaimType"/>
/// claim per permitted site (the claim value is the integer <c>Site.Id</c>).
/// A Deployment user with no <c>SiteId</c> claim — and any Admin/Design user — is
/// system-wide.
/// </para>
/// <para>
/// Deployment and Monitoring pages must filter every site/instance list through
/// <see cref="FilterSitesAsync"/> and re-check <see cref="IsSiteAllowedAsync"/>
/// before any cross-site command, so a scoped user cannot view or act on sites
/// outside their grant.
/// </para>
/// </summary>
public sealed class SiteScopeService
{
    private readonly AuthenticationStateProvider _authStateProvider;
    private (bool IsSystemWide, IReadOnlySet<int> Sites)? _cached;

    public SiteScopeService(AuthenticationStateProvider authStateProvider)
    {
        _authStateProvider = authStateProvider;
    }

    /// <summary>
    /// True when the user is not restricted to a site subset (no <c>SiteId</c>
    /// claims). System-wide users see and act on every site.
    /// </summary>
    public async Task<bool> IsSystemWideAsync()
        => (await ResolveAsync()).IsSystemWide;

    /// <summary>
    /// The set of <c>Site.Id</c> values the user may operate on. Empty for a
    /// system-wide user (callers should consult <see cref="IsSystemWideAsync"/>
    /// or use the filter/allowed helpers, which already account for that).
    /// </summary>
    public async Task<IReadOnlySet<int>> PermittedSiteIdsAsync()
        => (await ResolveAsync()).Sites;

    /// <summary>
    /// Returns the subset of <paramref name="sites"/> the user is permitted to
    /// see. A system-wide user gets the full list back unchanged.
    /// </summary>
    public async Task<List<Site>> FilterSitesAsync(IEnumerable<Site> sites)
    {
        var (isSystemWide, allowed) = await ResolveAsync();
        if (isSystemWide)
            return sites.ToList();
        return sites.Where(s => allowed.Contains(s.Id)).ToList();
    }

    /// <summary>
    /// True when the user may operate on the site with the given <c>Site.Id</c>.
    /// Must be re-checked server-side before any mutating cross-site command.
    /// </summary>
    public async Task<bool> IsSiteAllowedAsync(int siteId)
    {
        var (isSystemWide, allowed) = await ResolveAsync();
        return isSystemWide || allowed.Contains(siteId);
    }

    private async Task<(bool IsSystemWide, IReadOnlySet<int> Sites)> ResolveAsync()
    {
        if (_cached is { } cached)
            return cached;

        var state = await _authStateProvider.GetAuthenticationStateAsync();
        var siteClaims = state.User.FindAll(JwtTokenService.SiteIdClaimType);

        var ids = new HashSet<int>();
        foreach (var claim in siteClaims)
        {
            if (int.TryParse(claim.Value, out var id))
                ids.Add(id);
        }

        // No SiteId claims => system-wide. This mirrors SiteScopeAuthorizationHandler:
        // absence of scope rules means an unrestricted deployer.
        var result = (IsSystemWide: ids.Count == 0, Sites: (IReadOnlySet<int>)ids);
        _cached = result;
        return result;
    }
}
