using System.Security.Claims;
using Akka.Actor;
using Bunit;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using NSubstitute;
using ScadaLink.Commons.Entities.Sites;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Messages.Notification;
using ScadaLink.Communication;
using ScadaLink.HealthMonitoring;
using HealthPage = ScadaLink.CentralUI.Components.Pages.Monitoring.Health;

namespace ScadaLink.CentralUI.Tests.Pages;

/// <summary>
/// bUnit rendering tests for the Health Monitoring dashboard (Task 24).
///
/// Scope: the Notification Outbox KPI tile row added to the Health dashboard.
/// <see cref="ICentralHealthAggregator"/> is an interface (mockable), but
/// <see cref="CommunicationService"/> is a concrete class whose outbox calls
/// route through an injected notification-outbox <see cref="IActorRef"/>; the
/// tests reuse the scripted-actor seam established by the Notification Report
/// page tests (see <c>NotificationReportPageTests</c>).
/// </summary>
public class HealthPageTests : BunitContext
{
    private readonly ActorSystem _system = ActorSystem.Create("health-page-tests");
    private readonly CommunicationService _comms;

    // Mutable scripted reply — individual tests can override before rendering.
    private NotificationKpiResponse _kpiReply =
        new("k", true, null, QueueDepth: 12, StuckCount: 4, ParkedCount: 3,
            DeliveredLastInterval: 88, OldestPendingAge: TimeSpan.FromMinutes(6));

    public HealthPageTests()
    {
        _comms = new CommunicationService(
            Options.Create(new CommunicationOptions()),
            NullLogger<CommunicationService>.Instance);

        var outbox = _system.ActorOf(Props.Create(() => new ScriptedOutboxActor(this)));
        _comms.SetNotificationOutbox(outbox);
        Services.AddSingleton(_comms);

        var aggregator = Substitute.For<ICentralHealthAggregator>();
        aggregator.GetAllSiteStates()
            .Returns(new Dictionary<string, SiteHealthState>());
        Services.AddSingleton(aggregator);

        var siteRepo = Substitute.For<ISiteRepository>();
        siteRepo.GetAllSitesAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<Site>>(new List<Site>()));
        Services.AddSingleton(siteRepo);

        var claims = new[]
        {
            new Claim("Username", "tester"),
            new Claim(ClaimTypes.Role, "Admin"),
        };
        var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
        Services.AddSingleton<AuthenticationStateProvider>(new TestAuthStateProvider(user));
        Services.AddAuthorizationCore();
    }

    [Fact]
    public void Renders_OutboxKpiTiles_WithValues()
    {
        var cut = Render<HealthPage>();

        // KPI data arrives via an async actor Ask after first render.
        cut.WaitForAssertion(() =>
        {
            Assert.Contains("Notification Outbox", cut.Markup);
            Assert.Contains("Queue Depth", cut.Markup);
            Assert.Contains("Stuck", cut.Markup);
            Assert.Contains("Parked", cut.Markup);
            // KPI numeric values surface in the tiles.
            Assert.Contains(">12<", cut.Markup);  // QueueDepth
            Assert.Contains(">4<", cut.Markup);   // StuckCount
            Assert.Contains(">3<", cut.Markup);   // ParkedCount
        });
    }

    [Fact]
    public void RendersLinkToTheNotificationKpisPage()
    {
        var cut = Render<HealthPage>();
        var link = cut.Find("a[href='/notifications/kpis']");
        Assert.Contains("View details", link.TextContent);
    }

    [Fact]
    public void OutboxKpiFailure_ShowsGracefulFallback()
    {
        _kpiReply = new NotificationKpiResponse(
            "k", false, "outbox repository unavailable", 0, 0, 0, 0, null);

        var cut = Render<HealthPage>();

        cut.WaitForAssertion(() =>
        {
            // Failure must not crash the page; tiles fall back to a dash.
            Assert.Contains("Notification Outbox", cut.Markup);
            Assert.Contains("Queue Depth", cut.Markup);
            Assert.Contains(">—<", cut.Markup);
        });
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _system.Terminate().Wait(TimeSpan.FromSeconds(5));
        }
        base.Dispose(disposing);
    }

    /// <summary>
    /// Stand-in for the notification-outbox actor. Replies to the KPI request
    /// with the test's currently-scripted response.
    /// </summary>
    private sealed class ScriptedOutboxActor : ReceiveActor
    {
        public ScriptedOutboxActor(HealthPageTests test)
        {
            Receive<NotificationKpiRequest>(_ => Sender.Tell(test._kpiReply));
        }
    }
}
