using Microsoft.AspNetCore.Components;
using ZB.MOM.WW.ScadaBridge.Commons.Types;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Health;

/// <summary>
/// Code-behind for <see cref="AuditKpiTiles"/>.
/// Renders three KPI tiles — volume, error rate, backlog — from a
/// <see cref="AuditLogKpiSnapshot"/> the parent page supplies. Tiles act as
/// drill-in links: clicking navigates to <c>/audit/log</c> with the relevant
/// query-string filter pre-applied (the page already parses these params).
/// </summary>
/// <remarks>
/// <para>
/// <b>Why purely presentational.</b> The Health dashboard already owns a 10s
/// auto-refresh loop and an "as-of" timestamp display; pushing those concerns
/// into the tile component would either duplicate them (one timer per tile) or
/// awkwardly couple back to the page. The parent passes a fresh
/// <see cref="AuditLogKpiSnapshot"/> every refresh and the tile component
/// re-renders.
/// </para>
/// <para>
/// <b>Error rate division.</b> When <c>TotalEventsLastHour == 0</c> we render
/// "0%" rather than "—" — the snapshot itself is available, the system just had
/// no audit traffic to evaluate. This avoids a divide-by-zero AND keeps the
/// "0% errors" reading semantically true. The em dash is reserved for
/// <see cref="IsAvailable"/> = <c>false</c>, which represents a failed snapshot
/// query (different signal from "quiet hour").
/// </para>
/// </remarks>
public partial class AuditKpiTiles
{
    /// <summary>
    /// Latest KPI snapshot. <c>null</c> means the parent has not loaded it yet
    /// or the load failed — the tiles render em dashes in that case.
    /// </summary>
    [Parameter] public AuditLogKpiSnapshot? Snapshot { get; set; }

    /// <summary>
    /// True when <see cref="Snapshot"/> is a successful query result. False
    /// when the parent's refresh threw and the displayed values should be
    /// rendered as em dashes with an error explanation underneath.
    /// </summary>
    [Parameter] public bool IsAvailable { get; set; }

    /// <summary>
    /// Optional error message to render underneath the tiles when
    /// <see cref="IsAvailable"/> is false. Mirrors how the Notification Outbox
    /// section on the Health dashboard surfaces transient KPI failures.
    /// </summary>
    [Parameter] public string? ErrorMessage { get; set; }

    // ── Volume tile ─────────────────────────────────────────────────────────

    private string VolumeDisplay =>
        IsAvailable && Snapshot is not null
            ? Snapshot.TotalEventsLastHour.ToString("N0")
            : "—";

    private void NavigateToVolume()
    {
        // Volume is "all audit rows in the last hour" — no status filter; the
        // page's existing instance-search seam is enough for drill-in. We rely
        // on the page's default render which omits a time-range constraint and
        // shows the newest rows first.
        Navigation.NavigateTo("/audit/log");
    }

    // ── Error rate tile ─────────────────────────────────────────────────────

    /// <summary>
    /// Percentage of error rows (Failed/Parked/Discarded) over the trailing
    /// hour. Returns 0 when the snapshot is unavailable OR when total events
    /// is zero (rather than throwing). The display layer renders "—" for the
    /// unavailable case and "0%" for the zero-events case.
    /// </summary>
    internal double ErrorRatePercent
    {
        get
        {
            if (!IsAvailable || Snapshot is null || Snapshot.TotalEventsLastHour <= 0)
            {
                return 0;
            }
            return 100.0 * Snapshot.ErrorEventsLastHour / Snapshot.TotalEventsLastHour;
        }
    }

    private string ErrorRateDisplay
    {
        get
        {
            if (!IsAvailable || Snapshot is null)
            {
                return "—";
            }
            // Format to one decimal so a rate of 1 error in 2000 doesn't round to 0%.
            return $"{ErrorRatePercent:0.0}%";
        }
    }

    // Border + text colour bracket the tile visually: any nonzero error rate
    // gets a warning border; anything above 10% bumps it to danger. The
    // thresholds match the Notification Outbox tile pattern (border-warning
    // when Stuck > 0, border-danger when Parked > 0).
    private string ErrorRateBorderClass =>
        !IsAvailable || Snapshot is null || Snapshot.ErrorEventsLastHour == 0
            ? string.Empty
            : (ErrorRatePercent >= 10 ? "border-danger" : "border-warning");

    private string ErrorRateTextClass =>
        !IsAvailable || Snapshot is null || Snapshot.ErrorEventsLastHour == 0
            ? string.Empty
            : (ErrorRatePercent >= 10 ? "text-danger" : "text-warning");

    private void NavigateToErrors()
    {
        // Drill in pre-filtered to Failed — the most common error class.
        // (The Audit Log page also accepts ?status=Parked / =Discarded for
        // operators who want to see those specifically; the tile picks Failed
        // as the primary surface since it's the only synchronous-failure
        // status. Parked + Discarded both still appear in the unfiltered grid.)
        Navigation.NavigateTo("/audit/log?status=Failed");
    }

    // ── Backlog tile ────────────────────────────────────────────────────────

    private string BacklogDisplay =>
        IsAvailable && Snapshot is not null
            ? Snapshot.BacklogTotal.ToString("N0")
            : "—";

    // Backlog above zero is itself a signal — sites should normally drain to
    // empty. We render warning when there's a backlog at all; a hard danger
    // threshold could be added later if ops want it but the on-call playbook
    // for "backlog > 0" is the same as "backlog > 1000": check why the site
    // isn't draining.
    private string BacklogBorderClass =>
        IsAvailable && Snapshot is not null && Snapshot.BacklogTotal > 0
            ? "border-warning"
            : string.Empty;

    private string BacklogTextClass =>
        IsAvailable && Snapshot is not null && Snapshot.BacklogTotal > 0
            ? "text-warning"
            : string.Empty;

    private void NavigateToBacklog()
    {
        // The audit-log page itself doesn't carry a per-site backlog grid —
        // the Health dashboard already shows that per-site card. The natural
        // drill-in for "the system has a backlog" is the unfiltered Audit Log
        // page sorted by newest, so an operator can see the most recent rows
        // and judge whether the queue is moving.
        Navigation.NavigateTo("/audit/log");
    }
}
