using ScadaLink.Commons.Entities.Audit;
using ScadaLink.Commons.Types;
using ScadaLink.Commons.Types.Audit;
namespace ScadaLink.Commons.Interfaces.Repositories;
///
/// Append-only data access for the central AuditLog table (Audit Log #23).
///
///
///
/// The append-only invariant is enforced both at the SQL level (the
/// scadalink_audit_writer role has only INSERT + SELECT — UPDATE and DELETE
/// are not granted) and at the API level: this interface deliberately exposes no
/// Update and no single-row Delete. Bulk purge is performed exclusively via
/// monthly partition switch-out ().
///
///
/// Ingest is idempotent on EventId: is
/// first-write-wins, so retrying telemetry and reconciliation pulls can both feed
/// the same writer without producing duplicates.
///
///
public interface IAuditLogRepository
{
///
/// Inserts if no row with the same
/// exists; otherwise silently leaves the
/// stored row untouched (first-write-wins). Bypasses the EF change tracker
/// so the row never enters a tracked state.
///
/// The audit event to insert.
/// Cancellation token.
Task InsertIfNotExistsAsync(AuditEvent evt, CancellationToken ct = default);
///
/// Returns up to rows matching
/// , ordered by (OccurredAtUtc DESC, EventId DESC).
/// Use keyset paging by passing the last returned row's
/// OccurredAtUtc + EventId back via
/// +
/// to fetch the next page.
///
/// Filter criteria to apply to the query.
/// Paging cursor and page size.
/// Cancellation token.
Task> QueryAsync(
AuditLogQueryFilter filter,
AuditLogPaging paging,
CancellationToken ct = default);
///
/// Switches out (purges) the monthly partition whose lower bound is
/// and returns the approximate number
/// of rows discarded — sampled inside the transaction BEFORE the switch
/// so the row count reflects what the switch removed, not a post-purge
/// scan of a table that no longer exists.
///
///
///
/// Drop-and-rebuild dance. UX_AuditLog_EventId is intentionally
/// non-partition-aligned (it lives on [PRIMARY] so single-column
/// EventId uniqueness — required by —
/// can be enforced cheaply). SQL Server rejects
/// ALTER TABLE … SWITCH PARTITION while a non-aligned unique index
/// is present, so the M6 implementation drops the index, creates a staging
/// table with byte-identical schema, switches the partition's data into
/// staging, drops staging (discarding the rows), and rebuilds the unique
/// index. The CATCH branch guarantees the index is rebuilt even on partial
/// failure so the table never returns to live traffic without its
/// idempotency-supporting index.
///
///
/// Outage window. The dance briefly removes the unique index, so
/// concurrent calls during the switch
/// could in principle race past the IF NOT EXISTS check without the index
/// catching the duplicate. This is acceptable for the daily purge cadence
/// — the inserts that the IF NOT EXISTS check guards are themselves rare
/// enough that a sub-second collision window is operationally negligible,
/// and the composite PK still rejects same-(EventId, OccurredAtUtc) rows.
///
///
/// Lower-bound datetime of the monthly partition to switch out.
/// Cancellation token.
Task SwitchOutPartitionAsync(DateTime monthBoundary, CancellationToken ct = default);
///
/// Returns the set of pf_AuditLog_Month partition lower-bound
/// boundaries whose partitions contain only rows with
/// strictly older than
/// . Boundaries whose partition is empty are
/// excluded (a no-op switch is wasted work). Used by the M6 purge actor
/// to enumerate retention-eligible months on every tick.
///
/// Only partitions whose data is entirely older than this UTC datetime are returned.
/// Cancellation token.
Task> GetPartitionBoundariesOlderThanAsync(
DateTime threshold,
CancellationToken ct = default);
///
/// Audit Log (#23) M7 Bundle E (T13) — returns aggregate counts over the
/// trailing driving the central Health
/// dashboard's Audit KPI tiles.
///
///
/// Trailing time window (e.g. TimeSpan.FromHours(1)). Rows whose
/// OccurredAtUtc >= nowUtc - window are counted; the upper
/// bound is .
///
///
/// Optional explicit "now" timestamp used to anchor the trailing window.
/// Defaults to at call time when null —
/// production callers should leave this null; tests pin a deterministic
/// value so the window is reproducible across runs.
///
/// Cancellation token.
///
/// A snapshot with TotalEventsLastHour + ErrorEventsLastHour
/// populated; BacklogTotal is left at zero (this method has no
/// visibility into per-site backlogs — the service layer composes it in
/// from ).
/// AsOfUtc is set to the server-side UtcNow at the time of
/// the query.
///
///
///
/// Implemented as a single aggregate query
/// (SELECT COUNT_BIG(*) AS Total, SUM(CASE …) AS Errors) rather than
/// two round trips so the volume + error rate tiles read a consistent
/// snapshot — the denominator and numerator come from the same scan.
///
///
/// Errors are defined as ,
/// , or
///
/// — every non-success terminal lifecycle state. Submitted,
/// Forwarded, Attempted are in-flight and are NOT errors;
/// Delivered is success; Skipped is an intentional no-op.
///
///
Task GetKpiSnapshotAsync(
TimeSpan window,
DateTime? nowUtc = null,
CancellationToken ct = default);
///
/// Audit Log ParentExecutionId feature (Task 8) — given any
/// in an execution chain, returns the whole
/// chain rooted at the topmost ancestor: one
/// per distinct execution, summarising its AuditLog rows. The Central
/// UI renders the result as a tree.
///
///
///
/// The input id may be any node in the chain — a leaf, the root, or a middle
/// node. The implementation first walks up via
/// ParentExecutionId to find the root, then walks down from
/// the root via a recursive CTE, so the full chain is returned regardless of
/// entry point.
///
///
/// The ParentExecutionId graph is a tree (acyclic by construction —
/// each execution is minted fresh and its parent always pre-exists). Both
/// the upward walk and the downward CTE are nonetheless bounded at 32 levels
/// as a guard against corrupt/pathological data: a depth that exceeds the
/// guard raises an error rather than hanging the server. Chains are shallow
/// (1-2 levels typical) so the guard is never reached in practice.
///
///
/// A "stub" node — an execution that emitted no rows of its own yet is
/// referenced by a child via ParentExecutionId, or whose rows have
/// been purged — still appears, with
/// = 0. A purged/missing parent simply ends the upward walk.
///
///
/// When no AuditLog row carries in
/// either ExecutionId or ParentExecutionId, the result is a
/// single stub node for itself
/// ( = 0) — consistent with the
/// stub-node treatment of any other row-less execution.
///
///
/// Any execution id in the chain; the implementation walks to the root and back down.
/// Cancellation token.
Task> GetExecutionTreeAsync(
Guid executionId,
CancellationToken ct = default);
///
/// Returns the distinct, non-null SourceNode values present in the
/// AuditLog table, in ascending order. Backs the Audit Log page's
/// "Node" multi-select filter dropdown — the Central UI caches the result
/// for ~60s so the repository is hit at most once per minute per circuit.
///
/// Cancellation token.
Task> GetDistinctSourceNodesAsync(CancellationToken ct = default);
}