using System.Net.Http.Json;
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;

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

/// <summary>
/// Blazor Server <see cref="AuthenticationStateProvider"/> that snapshots the cookie-backed
/// principal supplied at circuit boot and polls <c>/auth/ping</c> every 60 seconds to detect
/// expiry. Mirrors ScadaLink's CentralUI implementation.
/// </summary>
public sealed class CookieAuthenticationStateProvider : AuthenticationStateProvider, IAsyncDisposable
{
    private static readonly TimeSpan PingInterval = TimeSpan.FromSeconds(60);

    private readonly HttpClient _http;
    private readonly ILogger<CookieAuthenticationStateProvider> _logger;
    private readonly CancellationTokenSource _cts = new();
    private ClaimsPrincipal _current;
    private Task? _pingLoop;

    /// <summary>Initializes a new instance of the CookieAuthenticationStateProvider class.</summary>
    /// <param name="initial">The initial claims principal from circuit boot.</param>
    /// <param name="http">The HTTP client for authentication ping requests.</param>
    /// <param name="logger">The logger for diagnostic messages.</param>
    public CookieAuthenticationStateProvider(
        ClaimsPrincipal initial,
        HttpClient http,
        ILogger<CookieAuthenticationStateProvider> logger)
    {
        _current = initial;
        _http = http;
        _logger = logger;
    }

    /// <inheritdoc />
    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        _pingLoop ??= Task.Run(() => PingLoopAsync(_cts.Token));
        return Task.FromResult(new AuthenticationState(_current));
    }

    private async Task PingLoopAsync(CancellationToken ct)
    {
        try
        {
            while (!ct.IsCancellationRequested)
            {
                await Task.Delay(PingInterval, ct).ConfigureAwait(false);
                var resp = await _http.GetAsync("/auth/ping", ct).ConfigureAwait(false);
                if (!resp.IsSuccessStatusCode && _current.Identity?.IsAuthenticated == true)
                {
                    _logger.LogInformation("/auth/ping returned {Code}; notifying circuit", (int)resp.StatusCode);
                    _current = new ClaimsPrincipal(new ClaimsIdentity());
                    NotifyAuthenticationStateChanged(
                        Task.FromResult(new AuthenticationState(_current)));
                }
            }
        }
        catch (OperationCanceledException) { /* expected on shutdown */ }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Auth ping loop terminated unexpectedly");
        }
    }

    /// <summary>Cancels the background ping loop, awaits its completion, and disposes the cancellation token source.</summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    public async ValueTask DisposeAsync()
    {
        _cts.Cancel();
        if (_pingLoop is not null)
        {
            try { await _pingLoop.ConfigureAwait(false); } catch { /* swallow shutdown errors */ }
        }
        _cts.Dispose();
    }
}
