using System.Globalization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;

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

/// <summary>
/// Execution-Tree Node Detail Modal. Opened from an execution-tree node
/// double-click: given an
/// <see cref="ExecutionId"/> it loads that execution's audit rows via
/// <see cref="IAuditLogQueryService"/> and shows a list → per-row detail.
///
/// <para>
/// <b>Chrome.</b> A hand-rolled Bootstrap modal — visibility is pure Blazor
/// state (<see cref="IsOpen"/>) plus the <c>d-block</c>/<c>show</c> CSS classes
/// and a sibling <c>modal-backdrop</c>, mirroring how
/// <see cref="AuditDrilldownDrawer"/> hand-rolls its offcanvas. No
/// <c>bootstrap.bundle.js</c> modal API is used.
/// </para>
///
/// <para>
/// <b>Load timing.</b> The modal queries only on the closed → open transition
/// (detected in <see cref="OnParametersSetAsync"/>), never on every parameter
/// change, so re-renders while open do not re-hit the service.
/// </para>
///
/// <para>
/// <b>States.</b> Two-or-more rows → list view (one button per row, click sets
/// the selected row); exactly one row → opens straight to the detail view;
/// zero rows → a friendly empty state. A query failure degrades to an inline
/// error banner — it is never rethrown, so a transient DB outage cannot kill
/// the SignalR circuit (the same posture as <c>ExecutionTreePage.LoadChainAsync</c>).
/// The per-row detail body is delegated to the shared <see cref="AuditEventDetail"/>.
/// </para>
/// </summary>
public partial class ExecutionDetailModal
{
    [Inject] private IAuditLogQueryService AuditLogQueryService { get; set; } = null!;

    /// <summary>
    /// The execution whose audit rows the modal loads. When null an open modal
    /// loads nothing and shows the empty state — the host is expected to pair a
    /// non-null id with <see cref="IsOpen"/>.
    /// </summary>
    [Parameter] public Guid? ExecutionId { get; set; }

    /// <summary>
    /// True when the host wants the modal visible. The closed → open transition
    /// triggers the row load; see <see cref="OnParametersSetAsync"/>.
    /// </summary>
    [Parameter] public bool IsOpen { get; set; }

    /// <summary>
    /// Fired when the user dismisses the modal (header X, backdrop click, or
    /// footer Close). The host is expected to flip <see cref="IsOpen"/> to false.
    /// </summary>
    [Parameter] public EventCallback OnClose { get; set; }

    // The loaded rows for the current execution; empty until a load completes.
    private IReadOnlyList<AuditEventView> _rows = Array.Empty<AuditEventView>();

    // The row whose detail is shown; null = list view.
    private AuditEventView? _selectedRow;

    private bool _loading;
    private string? _error;

    // Tracks the previous IsOpen so OnParametersSet can detect the open
    // transition and load exactly once per open, not on every parameter change.
    private bool _wasOpen;

    /// <summary>
    /// Page size for the execution-row query. One execution's audit rows are
    /// few (cached calls top out around 4–5 rows); 100 comfortably covers a
    /// whole execution without paging.
    /// </summary>
    private const int RowPageSize = 100;

    /// <inheritdoc />
    protected override async Task OnParametersSetAsync()
    {
        // Load only on the closed → open transition. A re-render while already
        // open (or while closed) must not re-hit the service.
        if (IsOpen && !_wasOpen)
        {
            await LoadRowsAsync();
        }
        _wasOpen = IsOpen;
    }

    /// <summary>
    /// Loads the current execution's audit rows. On success, a single-row
    /// result opens straight to the detail view; otherwise the list view shows.
    /// A query failure degrades to an inline error banner and is never
    /// rethrown — audit drill-in is best-effort and must not kill the circuit.
    /// </summary>
    private async Task LoadRowsAsync()
    {
        _loading = true;
        _error = null;
        _selectedRow = null;
        _rows = Array.Empty<AuditEventView>();

        if (ExecutionId is null)
        {
            // Nothing to load — fall through to the empty state.
            _loading = false;
            return;
        }

        try
        {
            // No CancellationToken is passed deliberately: this is a bounded,
            // small (~100-row) query for one execution, so the IDisposable/CTS
            // machinery is not worth it for a modal. The closed → open guard in
            // OnParametersSetAsync cleanly re-loads on the next open if needed.
            _rows = await AuditLogQueryService.QueryAsync(
                new AuditLogQueryFilter(ExecutionId: ExecutionId.Value),
                new AuditLogPaging(PageSize: RowPageSize));

            // A single-row execution opens straight to its detail — there is
            // no list to choose from.
            if (_rows.Count == 1)
            {
                _selectedRow = _rows[0];
            }
        }
        catch (Exception ex)
        {
            // Mirror ExecutionTreePage.LoadChainAsync: a transient DB outage
            // degrades the modal to an inline error banner rather than killing
            // the SignalR circuit. Never rethrow.
            _error = $"Could not load this execution's audit rows: {ex.Message}";
            _rows = Array.Empty<AuditEventView>();
            _selectedRow = null;
        }
        finally
        {
            _loading = false;
        }
    }

    private void SelectRow(AuditEventView row) => _selectedRow = row;

    private void BackToList() => _selectedRow = null;

    private async Task HandleClose()
    {
        if (OnClose.HasDelegate)
        {
            await OnClose.InvokeAsync();
        }
    }

    /// <summary>
    /// Closes the modal when Escape is pressed, matching the header X, backdrop
    /// click, and footer Close affordances. The root <c>.modal</c> div carries
    /// <c>tabindex="-1"</c> so it can receive the keydown.
    /// </summary>
    private async Task HandleKeyDown(KeyboardEventArgs e)
    {
        if (e.Key == "Escape")
        {
            await HandleClose();
        }
    }

    /// <summary>First 8 hex digits of the execution id, mirroring the UI's short-id convention.</summary>
    private string ShortExecutionId()
    {
        if (ExecutionId is null)
        {
            return "—";
        }
        var n = ExecutionId.Value.ToString("N");
        return n.Length >= 8 ? n[..8] : n;
    }

    private static string FormatTime(DateTime occurredAtUtc)
        => occurredAtUtc.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture);

    /// <summary>
    /// Bootstrap badge class for a row's status — green for the success
    /// terminal state, red for failure/discard, amber for in-flight. Mirrors
    /// the status-badge colouring used by the Audit Log results grid.
    /// </summary>
    private static string StatusBadgeClass(AuditStatus status) => status switch
    {
        AuditStatus.Delivered => "text-bg-success",
        AuditStatus.Failed or AuditStatus.Discarded or AuditStatus.Parked => "text-bg-danger",
        _ => "text-bg-warning",
    };
}
