using Mbproxy.Admin;
using Shouldly;
using Xunit;

namespace Mbproxy.Tests.Admin;

/// <summary>
/// Unit tests for <see cref="StatusHtmlRenderer"/>.
/// All tests are pure: no network, no host, no DI.
/// </summary>
[Trait("Category", "Unit")]
public sealed class StatusHtmlRendererTests
{
    // ── Helpers ───────────────────────────────────────────────────────────────

    private static StatusResponse MakeStatus(
        IReadOnlyList<PlcStatus>? plcs = null,
        int uptimeSeconds = 42,
        string version = "1.2.3")
    {
        var service = new ServiceFields(
            UptimeSeconds:            uptimeSeconds,
            Version:                  version,
            ConfigLastReloadUtc:      null,
            ConfigReloadCount:        0,
            ConfigReloadRejectedCount: 0);

        var listeners = new ListenersAggregate(Bound: plcs?.Count ?? 0, Configured: plcs?.Count ?? 0);
        return new StatusResponse(service, listeners, plcs ?? []);
    }

    private static PlcStatus MakePlc(
        string name = "PLC-A",
        string state = "bound",
        string? lastBindError = null,
        int recoveryAttempts = 0,
        IReadOnlyList<ClientSnapshot>? clients = null)
    {
        var noClients = (IReadOnlyList<ClientSnapshot>)[];
        return new PlcStatus(
            Name:       name,
            Host:       "10.0.0.1",
            ListenPort: 5020,
            Listener:   new PlcListenerStatus(state, lastBindError, recoveryAttempts),
            Clients:    new PlcClientsStatus(clients?.Count ?? 0, clients ?? noClients),
            Pdus:       new PlcPdusStatus(100, new FcCounts(50, 10, 20, 15, 5), 30, 2),
            Backend:    new PlcBackendStatus(
                ConnectsSuccess: 0, ConnectsFailed: 0,
                ExceptionsByCode: new ExceptionCounts(1, 0, 0, 0),
                LastRoundTripMs: 3.5,
                InFlight: 0, MaxInFlight: 0, TxIdWraps: 0,
                DisconnectCascades: 0, QueueDepth: 0,
                CoalescedHitCount: 0, CoalescedMissCount: 0,
                CoalescedResponseToDeadUpstream: 0,
                CacheHitCount: 0, CacheMissCount: 0,
                CacheInvalidations: 0, CacheEntryCount: 0, CacheBytes: 0),
            Bytes:      new PlcBytesStatus(1024, 2048));
    }

    // ── 1. Valid HTML with meta-refresh for a single PLC ─────────────────────

    [Fact]
    public void Render_OnePlc_ProducesValidHtml_WithMetaRefresh()
    {
        var status = MakeStatus([MakePlc("PLC-A", "bound")]);

        string html = StatusHtmlRenderer.Render(status);

        html.ShouldContain("<meta http-equiv=\"refresh\" content=\"5\">");
        html.ShouldContain("<!DOCTYPE html>");
        html.ShouldContain("</html>");
        html.ShouldContain("PLC-A");
        html.ShouldContain("bound");
    }

    // ── 2. Recovering state highlights error ─────────────────────────────────

    [Fact]
    public void Render_RecoveringPlc_HighlightsState()
    {
        var plc = MakePlc("PLC-B", "recovering", lastBindError: "Address already in use", recoveryAttempts: 3);
        var status = MakeStatus([plc]);

        string html = StatusHtmlRenderer.Render(status);

        // State should be orange.
        html.ShouldContain("class=\"recovering\"");
        html.ShouldContain("Address already in use");
        html.ShouldContain("attempt 3");
    }

    // ── 3. Page weight under 50 KB for 54 PLCs ───────────────────────────────

    [Fact]
    public void Render_PageWeightUnder50KB_For54Plcs()
    {
        const int plcCount = 54;

        // Build 54 realistic PLC rows with 2 clients each.
        var plcs = new List<PlcStatus>(plcCount);
        for (int i = 0; i < plcCount; i++)
        {
            var clients = new List<ClientSnapshot>
            {
                new ClientSnapshot($"10.0.0.{i + 1}:49123", DateTimeOffset.UtcNow, 42),
                new ClientSnapshot($"10.0.0.{i + 1}:49124", DateTimeOffset.UtcNow, 17),
            };

            plcs.Add(MakePlc(
                name:  $"Line{i / 10 + 1}-Station{i % 10 + 1:D2}",
                state: i % 5 == 0 ? "recovering" : "bound",
                lastBindError: i % 5 == 0 ? "EADDRINUSE" : null,
                recoveryAttempts: i % 5 == 0 ? 2 : 0,
                clients: clients));
        }

        var status = MakeStatus(plcs);

        string html = StatusHtmlRenderer.Render(status);
        int byteCount = System.Text.Encoding.UTF8.GetByteCount(html);

        // Assert ≤ 50 KB.
        byteCount.ShouldBeLessThanOrEqualTo(50 * 1024,
            $"Page weight {byteCount} bytes exceeds 50 KB limit for {plcCount} PLCs");
    }
}
