using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;

namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;

/// <summary>
/// Operational-state data access for the central <c>SiteCalls</c> table.
/// One row per
/// <see cref="TrackedOperationId"/>; sites remain the source of truth and this
/// table is an eventually-consistent mirror fed by best-effort gRPC telemetry
/// plus periodic reconciliation pulls.
/// </summary>
/// <remarks>
/// <para>
/// Unlike the partitioned append-only <c>AuditLog</c>, this table holds
/// mutable operational state. <see cref="UpsertAsync"/> is insert-if-not-exists
/// then monotonic update — a status update with rank less than or equal to the
/// stored status is a silent no-op so out-of-order telemetry, duplicate gRPC
/// packets, and reconciliation pulls can all feed the same writer without
/// rolling state backward.
/// </para>
/// <para>
/// Status rank for monotonic comparison (lower wins): <c>Submitted=0,
/// Forwarded=1, Attempted=2, Skipped=2, Delivered=3, Failed=3, Parked=3,
/// Discarded=3</c>. Terminal statuses share rank 3 and are mutually exclusive
/// — an attempt to upsert e.g. <c>Delivered</c> over an existing <c>Parked</c>
/// row is a no-op.
/// </para>
/// </remarks>
public interface ISiteCallAuditRepository
{
    /// <summary>
    /// Inserts <paramref name="siteCall"/> if no row with the same
    /// <see cref="SiteCall.TrackedOperationId"/> exists; otherwise updates the
    /// existing row IF AND ONLY IF the incoming status' rank strictly exceeds
    /// the stored status' rank. Out-of-order / duplicate updates are silently
    /// dropped (monotonic forward-only progression).
    /// </summary>
    /// <param name="siteCall">The site call row to insert or monotonically update.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    Task UpsertAsync(SiteCall siteCall, CancellationToken ct = default);

    /// <summary>
    /// Returns the row for the given id, or <c>null</c> if none exists.
    /// </summary>
    /// <param name="id">The tracked operation id to look up.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that resolves to the matching <see cref="SiteCall"/>, or <c>null</c> if no row exists.</returns>
    Task<SiteCall?> GetAsync(TrackedOperationId id, CancellationToken ct = default);

    /// <summary>
    /// Returns up to <see cref="SiteCallPaging.PageSize"/> rows matching
    /// <paramref name="filter"/>, ordered by <c>(CreatedAtUtc DESC,
    /// TrackedOperationId DESC)</c>. Use keyset paging via
    /// <see cref="SiteCallPaging.AfterCreatedAtUtc"/> + <see cref="SiteCallPaging.AfterId"/>
    /// to fetch subsequent pages.
    /// </summary>
    /// <param name="filter">Filter criteria for the query.</param>
    /// <param name="paging">Keyset paging parameters.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that resolves to a page of <see cref="SiteCall"/> rows matching the filter, ordered by creation time descending.</returns>
    Task<IReadOnlyList<SiteCall>> QueryAsync(
        SiteCallQueryFilter filter,
        SiteCallPaging paging,
        CancellationToken ct = default);

    /// <summary>
    /// Deletes terminal rows whose <see cref="SiteCall.TerminalAtUtc"/> is
    /// strictly older than <paramref name="olderThanUtc"/>. Non-terminal rows
    /// (TerminalAtUtc IS NULL) are NEVER purged. Returns the number of rows
    /// deleted.
    /// </summary>
    /// <param name="olderThanUtc">UTC cutoff; terminal rows older than this are deleted.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that resolves to the number of rows deleted.</returns>
    Task<int> PurgeTerminalAsync(DateTime olderThanUtc, CancellationToken ct = default);

    /// <summary>
    /// Computes a point-in-time global <see cref="SiteCallKpiSnapshot"/> from the
    /// <c>SiteCalls</c> table. Counts are aggregated server-side (no row
    /// materialisation): <c>StuckCount</c> uses <paramref name="stuckCutoff"/>;
    /// <c>FailedLastInterval</c> / <c>DeliveredLastInterval</c> use
    /// <paramref name="intervalSince"/>; the current time for <c>OldestPendingAge</c>
    /// is captured inside the method.
    /// </summary>
    /// <param name="stuckCutoff">UTC threshold for classifying a row as stuck.</param>
    /// <param name="intervalSince">UTC start of the delivered/failed interval window.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that resolves to a global <see cref="SiteCallKpiSnapshot"/> computed from the current table state.</returns>
    Task<SiteCallKpiSnapshot> ComputeKpisAsync(
        DateTime stuckCutoff,
        DateTime intervalSince,
        CancellationToken ct = default);

    /// <summary>
    /// Computes a point-in-time <see cref="SiteCallSiteKpiSnapshot"/> per source
    /// site. Sites with no <c>SiteCalls</c> rows at all are omitted. The stuck
    /// cutoff and interval bounds are interpreted as in <see cref="ComputeKpisAsync"/>.
    /// </summary>
    /// <param name="stuckCutoff">UTC threshold for classifying a row as stuck.</param>
    /// <param name="intervalSince">UTC start of the delivered/failed interval window.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that resolves to a per-site KPI list; sites with no rows are omitted.</returns>
    Task<IReadOnlyList<SiteCallSiteKpiSnapshot>> ComputePerSiteKpisAsync(
        DateTime stuckCutoff,
        DateTime intervalSince,
        CancellationToken ct = default);

    /// <summary>
    /// Computes a point-in-time <see cref="SiteCallNodeKpiSnapshot"/> per originating
    /// node. Nodes with no <c>SiteCalls</c> rows at all are omitted; rows with a
    /// <c>NULL</c> <c>SourceNode</c> are excluded. The stuck cutoff and interval
    /// bounds are interpreted as in <see cref="ComputeKpisAsync"/>.
    /// </summary>
    /// <param name="stuckCutoff">UTC threshold for classifying a row as stuck.</param>
    /// <param name="intervalSince">UTC start of the delivered/failed interval window.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that resolves to a per-node KPI list; nodes with no rows are omitted.</returns>
    Task<IReadOnlyList<SiteCallNodeKpiSnapshot>> ComputePerNodeKpisAsync(
        DateTime stuckCutoff,
        DateTime intervalSince,
        CancellationToken ct = default);
}
