@page "/monitoring/health"
@attribute [Authorize]
@using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Health
@using ZB.MOM.WW.ScadaBridge.CentralUI.Services
@using ZB.MOM.WW.ScadaBridge.Commons.Types
@using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories
@using ZB.MOM.WW.ScadaBridge.HealthMonitoring
@using ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification
@using ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit
@using ZB.MOM.WW.ScadaBridge.Communication
@implements IDisposable
@inject ICentralHealthAggregator HealthAggregator
@inject ISiteRepository SiteRepository
@inject CommunicationService CommunicationService
@inject IAuditLogQueryService AuditLogQueryService

<div class="container-fluid mt-3">
    <div class="d-flex justify-content-between align-items-center mb-3">
        <h4 class="mb-0">Health Dashboard</h4>
        <div>
            <span class="text-muted small me-2">Auto-refresh: @(_autoRefreshSeconds)s</span>
            <button class="btn btn-outline-secondary btn-sm" @onclick="RefreshNow">Refresh Now</button>
        </div>
    </div>

    @* Notification Outbox headline KPIs — a central concern, shown regardless of site reports *@
    <div class="d-flex justify-content-between align-items-center mb-2">
        <h6 class="text-muted mb-0">Notification Outbox</h6>
        <a class="small" href="/notifications/kpis">View details &rarr;</a>
    </div>
    <div class="row g-3 mb-3">
        <div class="col-lg-4 col-md-6 col-12">
            <div class="card h-100">
                <div class="card-body text-center">
                    <h3 class="mb-0">@OutboxTileValue(_outboxKpi.QueueDepth)</h3>
                    <small class="text-muted">Queue Depth</small>
                </div>
            </div>
        </div>
        <div class="col-lg-4 col-md-6 col-12">
            <div class="card h-100 @(_outboxKpiAvailable && _outboxKpi.StuckCount > 0 ? "border-warning" : "")">
                <div class="card-body text-center">
                    <h3 class="mb-0 @(_outboxKpiAvailable && _outboxKpi.StuckCount > 0 ? "text-warning" : "")">@OutboxTileValue(_outboxKpi.StuckCount)</h3>
                    <small class="text-muted">Stuck</small>
                </div>
            </div>
        </div>
        <div class="col-lg-4 col-md-6 col-12">
            <div class="card h-100 @(_outboxKpiAvailable && _outboxKpi.ParkedCount > 0 ? "border-danger" : "")">
                <div class="card-body text-center">
                    <h3 class="mb-0 @(_outboxKpiAvailable && _outboxKpi.ParkedCount > 0 ? "text-danger" : "")">@OutboxTileValue(_outboxKpi.ParkedCount)</h3>
                    <small class="text-muted">Parked</small>
                </div>
            </div>
        </div>
    </div>
    @if (!_outboxKpiAvailable && _outboxKpiError != null)
    {
        <div class="text-muted small mb-3">Notification Outbox KPIs unavailable: @_outboxKpiError</div>
    }

    @* Site Call Audit (#22) Task 7 — three KPI tiles for the Site Call channel
       (buffered / stuck / parked). Refreshed alongside the site states. *@
    <SiteCallKpiTiles Snapshot="@_siteCallKpi"
                      IsAvailable="@_siteCallKpiAvailable"
                      ErrorMessage="@_siteCallKpiError" />

    @* Audit Log (#23) M7 Bundle E — three KPI tiles for the Audit channel
       (volume / error rate / backlog). Refreshed alongside the site states. *@
    <AuditKpiTiles Snapshot="@_auditKpi"
                   IsAvailable="@_auditKpiAvailable"
                   ErrorMessage="@_auditKpiError" />

    @if (_siteStates.Count == 0)
    {
        <div class="alert alert-info">No site health reports received yet.</div>
    }
    else
    {
        @* Overview cards *@
        <div class="row g-3 mb-3">
            <div class="col-lg-4 col-md-6 col-12">
                <div class="card border-success h-100">
                    <div class="card-body text-center">
                        <h3 class="mb-0 text-success">@_siteStates.Values.Count(s => s.IsOnline)</h3>
                        <small class="text-muted">Sites Online</small>
                    </div>
                </div>
            </div>
            <div class="col-lg-4 col-md-6 col-12">
                <div class="card border-danger h-100">
                    <div class="card-body text-center">
                        <h3 class="mb-0 text-danger">@_siteStates.Values.Count(s => !s.IsOnline)</h3>
                        <small class="text-muted">Sites Offline</small>
                    </div>
                </div>
            </div>
            <div class="col-lg-4 col-md-6 col-12">
                <div class="card border-warning h-100">
                    <div class="card-body text-center">
                        <h3 class="mb-0 text-warning">@_siteStates.Values.Count(SiteHasActiveErrors)</h3>
                        <small class="text-muted">Sites with active errors</small>
                    </div>
                </div>
            </div>
        </div>

        @* Per-site detail cards — central cluster pinned to the top, then sites alphabetically *@
        @foreach (var (siteId, state) in _siteStates.OrderBy(s => s.Key == CentralHealthReportLoop.CentralSiteId ? 0 : 1).ThenBy(s => s.Key))
        {
            var isCentral = siteId == CentralHealthReportLoop.CentralSiteId;
            var siteName = isCentral ? "Central Cluster" : GetSiteName(siteId);
            var detailsCollapseId = $"site-details-{siteId}";
            <div class="card mb-3">
                <div class="card-header d-flex justify-content-between align-items-center py-2">
                    <div>
                        @if (state.IsOnline)
                        {
                            <span class="badge bg-success me-2" aria-label="State: Online">@OnlineGlyph Online</span>
                        }
                        else
                        {
                            <span class="badge bg-danger me-2" aria-label="State: Offline">@OfflineGlyph Offline</span>
                        }
                        <strong class="fs-5">@siteName@(isCentral ? "" : $" ({siteId})")</strong>
                    </div>
                    <small class="text-muted">
                        Last report: <TimestampDisplay Value="@state.LastReportReceivedAt" Format="HH:mm:ss" NullText="awaiting first report" />
                        | Last heartbeat: <TimestampDisplay Value="@state.LastHeartbeatAt" Format="HH:mm:ss" />
                        | Seq: @state.LastSequenceNumber
                    </small>
                </div>
                <div class="card-body p-3">
                    @if (state.LatestReport != null)
                    {
                        var report = state.LatestReport;
                        <div class="row g-3">
                            @* Column 1: Nodes *@
                            <div class="col-md-6">
                                <h6 class="text-muted mb-2 border-bottom pb-1">Nodes</h6>
                                <table class="table table-sm table-borderless mb-0">
                                    <tbody>
                                        @if (report.ClusterNodes is { Count: > 0 })
                                        {
                                            @foreach (var node in report.ClusterNodes)
                                            {
                                                <tr>
                                                    <td class="small">@node.Hostname</td>
                                                    <td>
                                                        <span class="badge @(node.IsOnline ? "bg-success" : "bg-danger")"
                                                              aria-label="State: @(node.IsOnline ? "Online" : "Offline")">
                                                            @(node.IsOnline ? OnlineGlyph : OfflineGlyph) @(node.IsOnline ? "Online" : "Offline")
                                                        </span>
                                                    </td>
                                                    <td>
                                                        <span class="badge @(node.Role == "Primary" ? "bg-primary" : "bg-secondary")"
                                                              aria-label="State: @node.Role">
                                                            @(node.Role == "Primary" ? PrimaryGlyph : StandbyGlyph) @node.Role
                                                        </span>
                                                    </td>
                                                </tr>
                                            }
                                        }
                                        else
                                        {
                                            <tr>
                                                <td class="small">@(report.NodeHostname != "" ? report.NodeHostname : "Node")</td>
                                                <td>
                                                    <span class="badge @(state.IsOnline ? "bg-success" : "bg-danger")"
                                                          aria-label="State: @(state.IsOnline ? "Online" : "Offline")">
                                                        @(state.IsOnline ? OnlineGlyph : OfflineGlyph) @(state.IsOnline ? "Online" : "Offline")
                                                    </span>
                                                </td>
                                                <td>
                                                    @{
                                                        var roleLabel = report.NodeRole == "Active" ? "Primary" : "Standby";
                                                    }
                                                    <span class="badge @(report.NodeRole == "Active" ? "bg-primary" : "bg-secondary")"
                                                          aria-label="State: @roleLabel">
                                                        @(roleLabel == "Primary" ? PrimaryGlyph : StandbyGlyph) @roleLabel
                                                    </span>
                                                </td>
                                            </tr>
                                        }
                                    </tbody>
                                </table>
                            </div>

                            @* Column 2: Data Connections (collapsible) *@
                            <div class="col-md-6">
                                <button class="btn btn-link btn-sm p-0 text-decoration-none mb-2"
                                        data-bs-toggle="collapse"
                                        data-bs-target="@($"#{detailsCollapseId}-conns")"
                                        aria-expanded="false">
                                    Data Connections (@report.DataConnectionStatuses.Count)
                                </button>
                                <div class="collapse" id="@($"{detailsCollapseId}-conns")">
                                    @if (report.DataConnectionStatuses.Count == 0)
                                    {
                                        <span class="text-muted small">None</span>
                                    }
                                    else
                                    {
                                        @foreach (var (connName, health) in report.DataConnectionStatuses)
                                        {
                                            var endpoint = report.DataConnectionEndpoints?.GetValueOrDefault(connName);
                                            var quality = report.DataConnectionTagQuality?.GetValueOrDefault(connName);
                                            <div class="mb-2">
                                                <div class="d-flex justify-content-between">
                                                    <strong class="small">@connName</strong>
                                                    <span class="small">@(endpoint ?? health.ToString())</span>
                                                </div>
                                                @if (quality != null)
                                                {
                                                    <table class="table table-sm table-borderless mb-0">
                                                        <tbody>
                                                            <tr>
                                                                <td class="small text-muted py-0">Tags good</td>
                                                                <td class="small text-end py-0">@quality.Good.ToString("N0")</td>
                                                            </tr>
                                                            <tr>
                                                                <td class="small text-muted py-0">Tags bad</td>
                                                                <td class="small text-end py-0">@quality.Bad.ToString("N0")</td>
                                                            </tr>
                                                            <tr>
                                                                <td class="small text-muted py-0">Tags uncertain</td>
                                                                <td class="small text-end py-0">@quality.Uncertain.ToString("N0")</td>
                                                            </tr>
                                                        </tbody>
                                                    </table>
                                                }
                                            </div>
                                        }
                                    }
                                </div>
                            </div>

                            @* Column 3: Instances + Store-and-Forward (collapsible) *@
                            <div class="col-md-6">
                                <button class="btn btn-link btn-sm p-0 text-decoration-none mb-2"
                                        data-bs-toggle="collapse"
                                        data-bs-target="@($"#{detailsCollapseId}-queues")"
                                        aria-expanded="false">
                                    Instances &amp; Queues
                                </button>
                                <div class="collapse" id="@($"{detailsCollapseId}-queues")">
                                    <h6 class="text-muted mb-2 border-bottom pb-1">Instances</h6>
                                    <table class="table table-sm table-borderless mb-0">
                                        <tbody>
                                            <tr>
                                                <td class="small">Deployed</td>
                                                <td class="text-end">@report.DeployedInstanceCount</td>
                                            </tr>
                                            <tr>
                                                <td class="small">Enabled</td>
                                                <td class="text-end text-success">@report.EnabledInstanceCount</td>
                                            </tr>
                                            <tr>
                                                <td class="small">Disabled</td>
                                                <td class="text-end">@report.DisabledInstanceCount</td>
                                            </tr>
                                        </tbody>
                                    </table>

                                    <h6 class="text-muted mb-2 mt-3 border-bottom pb-1">Store-and-Forward Buffers</h6>
                                    @if (report.StoreAndForwardBufferDepths.Count == 0)
                                    {
                                        <span class="text-muted small">Empty</span>
                                    }
                                    else
                                    {
                                        @foreach (var (category, depth) in report.StoreAndForwardBufferDepths)
                                        {
                                            <div class="d-flex justify-content-between mb-1">
                                                <span class="small">@category</span>
                                                <span class="badge @(depth > 0 ? "bg-warning text-dark" : "bg-light text-dark")">@depth</span>
                                            </div>
                                        }
                                    }
                                </div>
                            </div>

                            @* Column 4: Error Counts + Parked Messages (collapsible) *@
                            <div class="col-md-6">
                                <button class="btn btn-link btn-sm p-0 text-decoration-none mb-2"
                                        data-bs-toggle="collapse"
                                        data-bs-target="@($"#{detailsCollapseId}-errors")"
                                        aria-expanded="false">
                                    Errors &amp; Parked Messages
                                </button>
                                <div class="collapse" id="@($"{detailsCollapseId}-errors")">
                                    <h6 class="text-muted mb-2 border-bottom pb-1">Error Counts</h6>
                                    <table class="table table-sm table-borderless mb-0">
                                        <tbody>
                                            <tr>
                                                <td class="small">Script Errors</td>
                                                <td class="text-end">
                                                    <span class="@(report.ScriptErrorCount > 0 ? "text-danger fw-bold" : "")">@report.ScriptErrorCount</span>
                                                </td>
                                            </tr>
                                            <tr>
                                                <td class="small">Alarm Eval Errors</td>
                                                <td class="text-end">
                                                    <span class="@(report.AlarmEvaluationErrorCount > 0 ? "text-warning fw-bold" : "")">@report.AlarmEvaluationErrorCount</span>
                                                </td>
                                            </tr>
                                            <tr>
                                                <td class="small">Dead Letters</td>
                                                <td class="text-end">
                                                    <span class="@(report.DeadLetterCount > 0 ? "text-danger fw-bold" : "")">@report.DeadLetterCount</span>
                                                </td>
                                            </tr>
                                        </tbody>
                                    </table>

                                    <h6 class="text-muted mb-2 mt-3 border-bottom pb-1">Parked Messages</h6>
                                    @if (report.ParkedMessageCount == 0)
                                    {
                                        <span class="text-muted small">Empty</span>
                                    }
                                    else
                                    {
                                        <span class="badge bg-warning text-dark">@report.ParkedMessageCount</span>
                                    }
                                </div>
                            </div>
                        </div>
                    }
                    else
                    {
                        <span class="text-muted">No report data available.</span>
                    }
                </div>
            </div>
        }
    }
</div>

@code {
    // Shape-coded status glyphs to pair with badge colour.
    private const string OnlineGlyph = "●";   // ●
    private const string OfflineGlyph = "○";  // ○
    private const string PrimaryGlyph = "▲";  // ▲
    private const string StandbyGlyph = "△";  // △

    private IReadOnlyDictionary<string, SiteHealthState> _siteStates = new Dictionary<string, SiteHealthState>();
    private Dictionary<string, string> _siteNames = new();
    private Timer? _refreshTimer;
    private int _autoRefreshSeconds = 10;

    // Notification Outbox headline KPIs, refreshed alongside the site states.
    private NotificationKpiResponse _outboxKpi =
        new(
            CorrelationId: string.Empty,
            Success: false,
            ErrorMessage: null,
            QueueDepth: 0,
            StuckCount: 0,
            ParkedCount: 0,
            DeliveredLastInterval: 0,
            OldestPendingAge: null);
    private bool _outboxKpiAvailable;
    private string? _outboxKpiError;

    // Audit Log (#23) M7 Bundle E — Audit KPI tiles. Volume + error rate come
    // from a 1h aggregate over the central AuditLog table; backlog sums the
    // per-site SiteAuditBacklog.PendingCount via the health aggregator.
    private AuditLogKpiSnapshot? _auditKpi;
    private bool _auditKpiAvailable;
    private string? _auditKpiError;

    // Site Call Audit (#22) Task 7 — Site Call KPI tiles. Point-in-time counts
    // from the central SiteCalls table, fetched alongside the site states. The
    // SiteCallKpiResponse message doubles as the snapshot the tile takes.
    private SiteCallKpiResponse? _siteCallKpi;
    private bool _siteCallKpiAvailable;
    private string? _siteCallKpiError;

    private static bool SiteHasActiveErrors(SiteHealthState state)
    {
        var report = state.LatestReport;
        if (report == null) return false;
        return report.ScriptErrorCount > 0
            || report.AlarmEvaluationErrorCount > 0
            || report.DeadLetterCount > 0;
    }

    protected override async Task OnInitializedAsync()
    {
        // Load site names for display
        try
        {
            var sites = await SiteRepository.GetAllSitesAsync();
            _siteNames = sites.ToDictionary(s => s.SiteIdentifier, s => s.Name);
        }
        catch
        {
            // Non-fatal — fall back to showing siteId only
        }

        await RefreshNow();
        _refreshTimer = new Timer(_ =>
        {
            InvokeAsync(async () =>
            {
                await RefreshNow();
                StateHasChanged();
            });
        }, null, TimeSpan.FromSeconds(_autoRefreshSeconds), TimeSpan.FromSeconds(_autoRefreshSeconds));
    }

    private async Task RefreshNow()
    {
        _siteStates = HealthAggregator.GetAllSiteStates();
        await LoadOutboxKpis();
        await LoadSiteCallKpis();
        await LoadAuditKpis();
    }

    private async Task LoadOutboxKpis()
    {
        try
        {
            var response = await CommunicationService.GetNotificationKpisAsync(
                new NotificationKpiRequest(Guid.NewGuid().ToString("N")));
            if (response.Success)
            {
                _outboxKpi = response;
                _outboxKpiAvailable = true;
                _outboxKpiError = null;
            }
            else
            {
                _outboxKpiAvailable = false;
                _outboxKpiError = response.ErrorMessage ?? "KPI query failed.";
            }
        }
        catch (Exception ex)
        {
            _outboxKpiAvailable = false;
            _outboxKpiError = $"KPI query failed: {ex.Message}";
        }
    }

    // Site Call KPI loader: wraps the service call so a transient fault degrades
    // the three Site Call tiles to em dashes with an inline error rather than
    // killing the dashboard. Mirrors LoadOutboxKpis's error handling shape — a
    // response with Success == false (repository fault) and an Ask that threw
    // (transport fault) both collapse to "unavailable".
    private async Task LoadSiteCallKpis()
    {
        try
        {
            var response = await CommunicationService.GetSiteCallKpisAsync(
                new SiteCallKpiRequest(Guid.NewGuid().ToString("N")));
            if (response.Success)
            {
                _siteCallKpi = response;
                _siteCallKpiAvailable = true;
                _siteCallKpiError = null;
            }
            else
            {
                _siteCallKpiAvailable = false;
                _siteCallKpiError = response.ErrorMessage ?? "KPI query failed.";
            }
        }
        catch (Exception ex)
        {
            _siteCallKpiAvailable = false;
            _siteCallKpiError = $"KPI query failed: {ex.Message}";
        }
    }

    // Tiles show the numeric KPI when available, or an em dash when the outbox
    // KPI query failed — matching how the page renders other unavailable data.
    private string OutboxTileValue(int value) =>
        _outboxKpiAvailable ? value.ToString() : "—";

    // Audit KPI loader: wraps the service call so a transient DB outage degrades
    // the three tiles to em dashes with an inline error rather than killing the
    // dashboard. Mirrors LoadOutboxKpis's error handling shape.
    private async Task LoadAuditKpis()
    {
        try
        {
            _auditKpi = await AuditLogQueryService.GetKpiSnapshotAsync();
            _auditKpiAvailable = true;
            _auditKpiError = null;
        }
        catch (Exception ex)
        {
            _auditKpiAvailable = false;
            _auditKpiError = $"KPI query failed: {ex.Message}";
        }
    }

    private string GetSiteName(string siteId)
    {
        return _siteNames.GetValueOrDefault(siteId, siteId);
    }

    private static string GetConnectionHealthBadge(ConnectionHealth health) => health switch
    {
        ConnectionHealth.Connected => "bg-success",
        ConnectionHealth.Connecting => "bg-warning text-dark",
        ConnectionHealth.Disconnected => "bg-danger",
        _ => "bg-secondary"
    };

    public void Dispose()
    {
        _refreshTimer?.Dispose();
    }
}
