@page "/sessions/{SessionId}"
@inherits DashboardPageBase
@implements IAsyncDisposable
@using Microsoft.AspNetCore.SignalR.Client
@using ZB.MOM.WW.MxGateway.Contracts.Proto
@using ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject IDashboardSessionAdminService SessionAdminService

<PageTitle>Dashboard Session</PageTitle>

@if (Snapshot is null)
{
    <div class="empty-state">Loading session.</div>
}
else if (CurrentSession is null)
{
    <section class="dashboard-section">
        <h1 class="h4 mb-3">Session Not Found</h1>
        <p class="mb-0">The session is not present in the current snapshot.</p>
    </section>
}
else
{
    <div class="dashboard-page-header">
        <div>
            <h1>Session Details</h1>
            <div class="text-secondary"><code>@CurrentSession.SessionId</code></div>
        </div>
        <div class="d-flex align-items-center gap-2">
            <StatusBadge Text="@CurrentSession.State.ToString()" />
            @if (CanManage)
            {
                <div class="btn-group btn-group-sm" role="group" aria-label="Session admin actions">
                    <button type="button" class="btn btn-outline-warning"
                            disabled="@IsBusy"
                            @onclick="RequestClose">
                        Close session
                    </button>
                    <button type="button" class="btn btn-outline-danger"
                            disabled="@IsBusy"
                            @onclick="RequestKill">
                        Kill worker
                    </button>
                </div>
            }
        </div>
    </div>

    @if (CanManage && !string.IsNullOrWhiteSpace(ResultMessage))
    {
        <div class="alert @(LastOperationSucceeded ? "alert-success" : "alert-danger")" role="alert">
            @ResultMessage
        </div>
    }

    @if (CanManage)
    {
        <ConfirmDialog IsOpen="@(PendingAction is not null)"
                       Title="@(PendingAction?.Title ?? string.Empty)"
                       Message="@(PendingAction?.Message ?? string.Empty)"
                       ConfirmLabel="@(PendingAction?.ConfirmLabel ?? "Confirm")"
                       ConfirmButtonClass="@(PendingAction?.ConfirmButtonClass ?? "btn-primary")"
                       IsBusy="IsBusy"
                       OnConfirm="ConfirmPendingAsync"
                       OnCancel="CancelPending" />
    }

    <section class="dashboard-section">
        <div class="section-heading">
            <h2>Session</h2>
        </div>
        <div class="table-responsive">
            <table class="table table-sm dashboard-table details-table">
                <tbody>
                    <tr><th scope="row">Backend</th><td>@CurrentSession.BackendName</td></tr>
                    <tr><th scope="row">Client identity</th><td>@DashboardDisplay.Text(CurrentSession.ClientIdentity)</td></tr>
                    <tr><th scope="row">Client session</th><td>@DashboardDisplay.Text(CurrentSession.ClientSessionName)</td></tr>
                    <tr><th scope="row">Client correlation</th><td>@DashboardDisplay.Text(CurrentSession.ClientCorrelationId)</td></tr>
                    <tr><th scope="row">Opened</th><td>@DashboardDisplay.DateTime(CurrentSession.OpenedAt)</td></tr>
                    <tr><th scope="row">Last activity</th><td>@DashboardDisplay.DateTime(CurrentSession.LastClientActivityAt)</td></tr>
                    <tr><th scope="row">Lease expires</th><td>@DashboardDisplay.DateTime(CurrentSession.LeaseExpiresAt)</td></tr>
                    <tr><th scope="row">Events received</th><td>@DashboardDisplay.Count(CurrentSession.EventsReceived)</td></tr>
                    <tr><th scope="row">Last fault</th><td>@DashboardDisplay.Text(CurrentSession.LastFault)</td></tr>
                </tbody>
            </table>
        </div>
    </section>

    <section class="dashboard-section">
        <div class="section-heading">
            <h2>Worker</h2>
        </div>
        <div class="table-responsive">
            <table class="table table-sm dashboard-table details-table">
                <tbody>
                    <tr><th scope="row">Process id</th><td>@(CurrentSession.WorkerProcessId?.ToString(System.Globalization.CultureInfo.InvariantCulture) ?? "-")</td></tr>
                    <tr><th scope="row">State</th><td><StatusBadge Text="@(CurrentSession.WorkerState?.ToString() ?? "-")" /></td></tr>
                    <tr><th scope="row">Last heartbeat</th><td>@DashboardDisplay.DateTime(CurrentSession.LastWorkerHeartbeatAt)</td></tr>
                </tbody>
            </table>
        </div>
    </section>

    <section class="dashboard-section">
        <div class="section-heading">
            <h2>Recent events</h2>
            <span class="conn-pill" data-state="@(_eventsConnected ? "connected" : "disconnected")">
                <span class="dot"></span>
                <span>@(_eventsConnected ? "live" : "offline")</span>
            </span>
        </div>
        @if (_recentEvents.Count == 0)
        {
            <div class="empty-state">
                Waiting for events. The dashboard mirrors the session's gRPC event stream — events
                appear here only while a gRPC client is also consuming this session's events.
            </div>
        }
        else
        {
            <div class="table-responsive">
                <table class="table table-sm dashboard-table">
                    <thead>
                        <tr>
                            <th scope="col">Worker seq</th>
                            <th scope="col">Family</th>
                            <th scope="col">Server</th>
                            <th scope="col">Item</th>
                            <th scope="col">Status</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (MxEvent evt in _recentEvents)
                        {
                            <tr>
                                <td class="num mono">@evt.WorkerSequence</td>
                                <td>@evt.Family</td>
                                <td class="num mono">@evt.ServerHandle</td>
                                <td class="num mono">@evt.ItemHandle</td>
                                <td>@DashboardDisplay.Text(EventStatusLabel(evt))</td>
                            </tr>
                        }
                    </tbody>
                </table>
            </div>
        }
    </section>
}

@code {
    private const int MaxRecentEvents = 50;

    [Parameter]
    public string SessionId { get; set; } = string.Empty;

    private DashboardSessionSummary? CurrentSession => Snapshot?.Sessions.FirstOrDefault(session =>
        string.Equals(session.SessionId, SessionId, StringComparison.Ordinal));

    private HubConnection? _eventsHub;
    private bool _eventsConnected;
    private string? _subscribedSessionId;
    private readonly LinkedList<MxEvent> _recentEvents = new();

    private bool CanManage { get; set; }

    private bool IsBusy { get; set; }

    private string? ResultMessage { get; set; }

    private bool LastOperationSucceeded { get; set; }

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync().ConfigureAwait(false);

        AuthenticationState authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync()
            .ConfigureAwait(false);
        CanManage = SessionAdminService.CanManage(authenticationState.User);
    }

    protected override async Task OnParametersSetAsync()
    {
        if (!string.Equals(_subscribedSessionId, SessionId, StringComparison.Ordinal))
        {
            await DetachEventsHubAsync().ConfigureAwait(false);
            await AttachEventsHubAsync().ConfigureAwait(false);
        }
    }

    private PendingConfirm? PendingAction { get; set; }

    private void RequestClose()
    {
        if (IsBusy)
        {
            return;
        }

        PendingAction = new PendingConfirm(
            Title: "Close session?",
            Message: $"Gracefully close session {SessionId}? The worker will be shut down.",
            ConfirmLabel: "Close",
            ConfirmButtonClass: "btn-warning",
            Action: user => SessionAdminService.CloseSessionAsync(user, SessionId, CancellationToken.None));
    }

    private void RequestKill()
    {
        if (IsBusy)
        {
            return;
        }

        PendingAction = new PendingConfirm(
            Title: "Kill worker?",
            Message: $"Forcefully kill the worker for session {SessionId}? This skips graceful shutdown.",
            ConfirmLabel: "Kill",
            ConfirmButtonClass: "btn-danger",
            Action: user => SessionAdminService.KillWorkerAsync(user, SessionId, CancellationToken.None));
    }

    private void CancelPending()
    {
        if (!IsBusy)
        {
            PendingAction = null;
        }
    }

    private async Task ConfirmPendingAsync()
    {
        if (IsBusy || PendingAction is null)
        {
            return;
        }

        Func<System.Security.Claims.ClaimsPrincipal, Task<DashboardSessionAdminResult>> action = PendingAction.Action;

        IsBusy = true;
        try
        {
            AuthenticationState authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync()
                .ConfigureAwait(false);
            CanManage = SessionAdminService.CanManage(authenticationState.User);
            DashboardSessionAdminResult result = await action(authenticationState.User).ConfigureAwait(false);
            ResultMessage = result.Message;
            LastOperationSucceeded = result.Succeeded;
        }
        finally
        {
            IsBusy = false;
            PendingAction = null;
        }
    }

    private sealed record PendingConfirm(
        string Title,
        string Message,
        string ConfirmLabel,
        string ConfirmButtonClass,
        Func<System.Security.Claims.ClaimsPrincipal, Task<DashboardSessionAdminResult>> Action);

    private async Task AttachEventsHubAsync()
    {
        if (string.IsNullOrWhiteSpace(SessionId))
        {
            return;
        }

        _eventsHub = HubFactory.Create("/hubs/events");
        _eventsHub.On<MxEvent>(EventsHub.EventMessage, async mxEvent =>
        {
            _recentEvents.AddFirst(mxEvent);
            while (_recentEvents.Count > MaxRecentEvents)
            {
                _recentEvents.RemoveLast();
            }

            await InvokeAsync(StateHasChanged).ConfigureAwait(false);
        });

        _eventsHub.Closed += _ =>
        {
            _eventsConnected = false;
            return InvokeAsync(StateHasChanged);
        };
        _eventsHub.Reconnected += _ =>
        {
            _eventsConnected = true;
            return InvokeAsync(StateHasChanged);
        };

        try
        {
            await _eventsHub.StartAsync().ConfigureAwait(false);
            await _eventsHub.SendAsync("SubscribeSession", SessionId).ConfigureAwait(false);
            _eventsConnected = true;
            _subscribedSessionId = SessionId;
        }
        catch
        {
            _eventsConnected = false;
        }
    }

    private async Task DetachEventsHubAsync()
    {
        HubConnection? hub = _eventsHub;
        _eventsHub = null;
        _eventsConnected = false;
        _subscribedSessionId = null;
        _recentEvents.Clear();

        if (hub is not null)
        {
            try
            {
                await hub.DisposeAsync().ConfigureAwait(false);
            }
            catch
            {
                // Disposal-time errors are best-effort.
            }
        }
    }

    private static string EventStatusLabel(MxEvent evt)
    {
        return evt.Family == MxEventFamily.OnAlarmTransition
            ? evt.OnAlarmTransition?.AlarmFullReference ?? string.Empty
            : string.Empty;
    }

    public new async ValueTask DisposeAsync()
    {
        await DetachEventsHubAsync().ConfigureAwait(false);
        await base.DisposeAsync().ConfigureAwait(false);
    }
}
