using System.Diagnostics.CodeAnalysis;
using ZB.MOM.WW.MxGateway.Contracts.Proto;

namespace ZB.MOM.WW.MxGateway.Server.Sessions;

public interface ISessionManager
{
    /// <summary>Opens a new gateway session and launches a worker process.</summary>
    /// <param name="request">Request payload.</param>
    /// <param name="clientIdentity">Client identity string.</param>
    /// <param name="ownerKeyId">API key identifier of the caller creating the session.</param>
    /// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
    /// <returns>The newly opened session.</returns>
    Task<GatewaySession> OpenSessionAsync(
        SessionOpenRequest request,
        string? clientIdentity,
        string? ownerKeyId,
        CancellationToken cancellationToken);

    /// <summary>
    ///     Opens a new gateway session, stamping the owning API key's dashboard event-visibility
    ///     tags onto it (SEC-25).
    /// </summary>
    /// <param name="request">Request payload.</param>
    /// <param name="clientIdentity">Client identity string.</param>
    /// <param name="ownerKeyId">API key identifier of the caller creating the session.</param>
    /// <param name="ownerDashboardTags">
    ///     The owner key's <c>ApiKeyConstraints.DashboardTags</c>. Null or empty opens an untagged
    ///     session. Never sourced from the client's wire request — see
    ///     <c>docs/plans/2026-07-10-dashboard-session-acl-tst15.md</c> §3.1.
    /// </param>
    /// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
    /// <returns>The newly opened session.</returns>
    /// <remarks>
    ///     The default implementation forwards to the tagless overload, so an implementation that
    ///     does not model tags (unit-test fakes) opens an <em>untagged</em> session. That is the
    ///     fail-closed direction: untagged sessions are the least dashboard-visible ones.
    /// </remarks>
    Task<GatewaySession> OpenSessionAsync(
        SessionOpenRequest request,
        string? clientIdentity,
        string? ownerKeyId,
        IReadOnlyList<string>? ownerDashboardTags,
        CancellationToken cancellationToken)
        => OpenSessionAsync(request, clientIdentity, ownerKeyId, cancellationToken);

    /// <summary>Attempts to retrieve a session by ID.</summary>
    /// <param name="sessionId">Identifier of the session.</param>
    /// <param name="session">The retrieved session, if found.</param>
    /// <returns>True if the session exists; otherwise false.</returns>
    bool TryGetSession(
        string sessionId,
        [MaybeNullWhen(false)] out GatewaySession session);

    /// <summary>Invokes a command on the worker for the specified session.</summary>
    /// <param name="sessionId">Identifier of the session.</param>
    /// <param name="command">Command to invoke.</param>
    /// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
    /// <returns>The command reply from the worker.</returns>
    Task<WorkerCommandReply> InvokeAsync(
        string sessionId,
        WorkerCommand command,
        CancellationToken cancellationToken);

    /// <summary>Closes a session and terminates its worker process.</summary>
    /// <param name="sessionId">Identifier of the session to close.</param>
    /// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
    /// <returns>The result of closing the session.</returns>
    Task<SessionCloseResult> CloseSessionAsync(
        string sessionId,
        CancellationToken cancellationToken);

    /// <summary>
    /// Forcefully terminates a session's worker without attempting graceful shutdown,
    /// transitions the session to Closed, and removes it from the registry.
    /// </summary>
    /// <param name="sessionId">Identifier of the session whose worker to kill.</param>
    /// <param name="reason">Reason for killing the worker (recorded in logs/audit).</param>
    /// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
    /// <returns>The result of closing the session.</returns>
    Task<SessionCloseResult> KillWorkerAsync(
        string sessionId,
        string reason,
        CancellationToken cancellationToken);

    /// <summary>Closes all sessions with expired leases at the specified time.</summary>
    /// <param name="now">The current time to evaluate expiration against.</param>
    /// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
    /// <returns>The number of sessions closed.</returns>
    /// <remarks>
    ///     A close that fails does not abandon the rest of the pass: every session selected by this
    ///     sweep is attempted, and the first failure is then rethrown so the caller still observes
    ///     (and logs) that the sweep failed. Which failure surfaces is nondeterministic when several
    ///     closes fail in the same pass, because the closes run concurrently.
    /// </remarks>
    Task<int> CloseExpiredLeasesAsync(
        DateTimeOffset now,
        CancellationToken cancellationToken);

    /// <summary>Shuts down all sessions and the session manager.</summary>
    /// <param name="cancellationToken">
    ///     Token that <em>degrades</em> the drain rather than cancelling it. It is passed only to each
    ///     session's graceful close; the drain loop and the kill fallback are not bound to it, so
    ///     cancelling turns the drain into a kill sweep instead of abandoning the untried sessions as
    ///     leaked workers. The call therefore overruns a cancelled token by a bounded amount —
    ///     roughly <c>ceil(sessionCount / 4)</c> batches of the worker shutdown timeout in the worst
    ///     case, where 4 is <c>MaxParallelSessionCloses</c>.
    /// </param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    Task ShutdownAsync(CancellationToken cancellationToken);
}
