using ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Deployment;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
public interface IDeploymentManagerRepository
{
// DeploymentRecord
///
/// Gets a deployment record by its ID.
///
/// The deployment record ID.
/// A cancellation token that can be used to cancel the operation.
/// The deployment record, or null if not found.
Task GetDeploymentRecordByIdAsync(int id, CancellationToken cancellationToken = default);
///
/// Database-side paged, filtered and instance-joined deployment listing — THE
/// paged read of DeploymentRecords, shared by the Central UI
/// deployment-status page and the CLI/HTTP QueryDeployments command.
///
///
/// It was two reads until deferred-work register row 37. The other one
/// (QueryDeploymentSummariesAsync) differed in three ways that were all
/// defects rather than requirements: it took a raw skip instead of a page
/// number, returned no total (so a truncated page was indistinguishable from a
/// complete one), skipped the Instance join, and scoped by a caller-resolved
/// INSTANCE-ID LIST — which its one caller built by loading every instance in the
/// system on every scoped query. Site scoping is expressed here as
/// and resolved by
/// the join, so no caller needs that read.
///
///
///
/// Replaces a GetAllDeploymentRecords whole-table read. That method's only
/// caller loaded EVERY deployment record (the table is insert-only, one row per
/// deploy attempt for the retention window) plus EVERY instance, then filtered by
/// site scope, sorted, counted the status tiles and sliced a 25-row page — all in
/// the Blazor circuit's memory, on every render AND on every deployment-status
/// push. Cost scaled with the age of the system rather than the size of the page.
///
///
///
/// OFFSET paging, not keyset — deliberately. The sibling Audit Log and Site
/// Calls read paths are keyset-cursored, but this one is offset-paged like the
/// Notification Outbox, and for the same reason: its contract surfaces a PAGE
/// NUMBER and a TOTAL COUNT (the page renders a numbered, jump-to-any-page pager),
/// and a keyset cursor can express neither. The total is needed regardless, since
/// the status tiles are an aggregate over the same filtered set. The deep-offset
/// cost that pushes high-volume tables to keyset is bounded here by
/// , which caps the table at the
/// terminal-record retention window — unlike the 365-day central
/// AuditLog.
///
///
///
/// Ordering is DeployedAt DESC, Id DESC. The Id tie-break is not
/// cosmetic: DeployedAt ties on rapid redeploys, and an unstable sort key
/// makes offset paging non-deterministic — rows repeat across pages or vanish
/// between them.
///
///
/// Status / search / site-scope / instance constraints; all applied DB-side.
/// 1-based page number; values below 1 are floored at 1.
/// Rows per page; clamped to [1, DeploymentListPage.MaxPageSize]. There is no "unlimited" value.
/// A cancellation token that can be used to cancel the operation.
/// The requested page of rows plus the total count of the filtered set.
Task QueryDeploymentListPageAsync(
Types.Deployment.DeploymentListFilter filter,
int pageNumber,
int pageSize,
CancellationToken cancellationToken = default);
///
/// One grouped aggregation returning the per-status row counts of the filtered
/// set — the server-side replacement for the deployment page's four
/// records.Count(r => r.Status == X) tile computations over a
/// client-materialized table.
///
///
/// is ignored here.
/// The tiles are the status BREAKDOWN of the otherwise-identically-filtered set,
/// so constraining them by the active status filter would collapse three of the
/// four tiles to zero the moment an operator clicked one. Every other dimension
/// (search, site scope, instance) IS applied, so the tiles always describe the
/// same population the list is drawn from.
///
///
/// The active list filter; its Status member is deliberately not applied.
/// A cancellation token that can be used to cancel the operation.
/// Per-status counts, with absent statuses reported as 0.
Task GetDeploymentStatusCountsAsync(
Types.Deployment.DeploymentListFilter filter,
CancellationToken cancellationToken = default);
///
/// Deletes TERMINAL deployment records ( /
/// ) that completed before
/// , in one bounded batch.
///
///
/// Non-terminal rows are never touched: an InProgress record is what the
/// query-before-redeploy reconciliation path reads to decide whether a prior
/// deploy actually landed at the site, so purging one by age would silently
/// disable that idempotency guard.
///
///
/// Records completing before this instant are eligible.
/// Maximum rows deleted in this call.
/// A cancellation token that can be used to cancel the operation.
/// The number of rows deleted; fewer than means the sweep is complete.
Task PurgeTerminalDeploymentRecordsAsync(DateTimeOffset cutoffUtc, int batchSize, CancellationToken cancellationToken = default);
///
/// Gets all deployment records for a specific instance.
///
/// The instance ID.
/// A cancellation token that can be used to cancel the operation.
/// A read-only list of deployment records for the instance.
Task> GetDeploymentsByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
///
/// Gets the current deployment status for an instance.
///
/// The instance ID.
/// A cancellation token that can be used to cancel the operation.
/// The current deployment record, or null if no deployment exists.
Task GetCurrentDeploymentStatusAsync(int instanceId, CancellationToken cancellationToken = default);
///
/// Gets a deployment record by deployment ID.
///
/// The deployment ID.
/// A cancellation token that can be used to cancel the operation.
/// The deployment record, or null if not found.
Task GetDeploymentByDeploymentIdAsync(string deploymentId, CancellationToken cancellationToken = default);
///
/// Adds a new deployment record.
///
/// The deployment record to add.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task AddDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default);
///
/// Updates an existing deployment record.
///
/// The deployment record to update.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task UpdateDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default);
///
/// Deletes a deployment record by ID, enforcing optimistic concurrency against the
/// supplied . The caller MUST pass the
/// RowVersion it last observed on the record so EF emits
/// DELETE ... WHERE Id = @id AND RowVersion = @prior. A concurrent edit
/// surfaces as
/// on , matching the documented
/// "Optimistic concurrency is used on deployment status records" design rule.
///
/// The deployment record ID to delete.
/// The RowVersion the caller observed; used as the optimistic-concurrency token.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task DeleteDeploymentRecordAsync(int id, byte[] expectedRowVersion, CancellationToken cancellationToken = default);
// SystemArtifactDeploymentRecord
///
/// Gets a system artifact deployment record by ID.
///
/// The system artifact deployment record ID.
/// A cancellation token that can be used to cancel the operation.
/// The system artifact deployment record, or null if not found.
Task GetSystemArtifactDeploymentByIdAsync(int id, CancellationToken cancellationToken = default);
///
/// Gets all system artifact deployment records.
///
/// A cancellation token that can be used to cancel the operation.
/// A read-only list of all system artifact deployment records.
Task> GetAllSystemArtifactDeploymentsAsync(CancellationToken cancellationToken = default);
///
/// Adds a new system artifact deployment record.
///
/// The system artifact deployment record to add.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task AddSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default);
///
/// Updates an existing system artifact deployment record.
///
/// The system artifact deployment record to update.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task UpdateSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default);
///
/// Deletes a system artifact deployment record by ID.
///
/// The system artifact deployment record ID to delete.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task DeleteSystemArtifactDeploymentAsync(int id, CancellationToken cancellationToken = default);
///
/// Deletes system artifact deployment records deployed before ,
/// in one bounded batch. One row is written per Deploy Artifacts run and nothing
/// reads them back, so they share the terminal deployment-record retention window
/// instead of growing without bound.
///
/// Records deployed before this instant are eligible.
/// Maximum rows deleted in this call.
/// A cancellation token that can be used to cancel the operation.
/// The number of rows deleted; fewer than means the sweep is complete.
Task PurgeSystemArtifactDeploymentRecordsAsync(DateTimeOffset cutoffUtc, int batchSize, CancellationToken cancellationToken = default);
// DeployedConfigSnapshot
///
/// Gets the deployed config snapshot for an instance.
///
/// The instance ID.
/// A cancellation token that can be used to cancel the operation.
/// The deployed config snapshot, or null if not found.
Task GetDeployedSnapshotByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
///
/// InstanceId → RevisionHash for every DeployedConfigSnapshot. A projection read — never
/// materialises ConfigurationJson, so it is safe to call for the whole fleet (the
/// pending-updates aggregate does, once per cache TTL).
///
///
/// Deliberately UNFILTERED: it always returns the whole fleet's two-column projection, with no
/// site, state or scope narrowing. A site-scoped or otherwise partial caller must filter the
/// returned map itself rather than assume the read was narrowed for it.
///
///
/// A cancellation token that can be used to cancel the operation.
/// A dictionary keyed by instance ID; instances with no snapshot are absent.
Task> GetDeployedRevisionHashesAsync(CancellationToken cancellationToken = default);
///
/// Adds a new deployed config snapshot.
///
/// The deployed config snapshot to add.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task AddDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default);
///
/// Updates an existing deployed config snapshot.
///
/// The deployed config snapshot to update.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task UpdateDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default);
///
/// Deletes the deployed config snapshot for an instance.
///
/// The instance ID.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task DeleteDeployedSnapshotAsync(int instanceId, CancellationToken cancellationToken = default);
// Notify-and-fetch: PendingDeployment staging store
///
/// Stages a flattened instance config for an in-flight deployment. Supersession:
/// any prior pending row for the same InstanceId is removed first, so at most
/// one pending row exists per instance (safe because the per-instance operation lock
/// serializes same-instance deploys). Does NOT call —
/// the caller commits, mirroring the other Add/Delete repository methods.
///
/// The pending deployment to stage.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task AddPendingDeploymentAsync(PendingDeployment pending, CancellationToken cancellationToken = default);
///
/// Gets a pending deployment by its deployment ID (the fetch key).
///
/// The deployment ID.
/// A cancellation token that can be used to cancel the operation.
/// The pending deployment, or null if not found.
Task GetPendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default);
///
/// Gets the pending deployment staged for a given instance (there is at most one per instance
/// by design — supersedes and
/// inserts only if absent). Used by startup reconcile:
/// when reports a row already exists (a concurrent
/// reconcile from the other node, or an in-flight deploy), the handler reads that existing row
/// so a second concurrently-missing node can fetch the same pending config with its (multi-use,
/// TTL-bound) token instead of being omitted from the gap. Defensive against >1 row: returns
/// the most recently created one. When is supplied, EXPIRED rows
/// (ExpiresAtUtc <= nowUtc) are filtered out so the caller never receives a token the
/// config-fetch endpoint would 404.
///
/// The instance whose pending deployment to read.
/// When supplied, only rows with ExpiresAtUtc > nowUtc are considered (expired rows excluded).
/// A cancellation token that can be used to cancel the operation.
/// The pending deployment for the instance, or null if none is staged.
Task GetPendingDeploymentByInstanceIdAsync(int instanceId, DateTimeOffset? nowUtc = null, CancellationToken cancellationToken = default);
///
/// Deletes a pending deployment by its deployment ID. No-op if not found. Does NOT
/// call — the caller commits, mirroring the other
/// Add/Delete repository methods.
///
/// The deployment ID to delete.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task DeletePendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default);
///
/// Purges all pending deployments whose ExpiresAtUtc is at or before
/// (TTL maintenance). This is a self-contained maintenance
/// operation: it commits its own delete and returns the number of rows removed.
///
/// The current UTC time; rows with ExpiresAtUtc <= nowUtc are purged.
/// A cancellation token that can be used to cancel the operation.
/// The number of pending deployments purged.
Task PurgeExpiredPendingDeploymentsAsync(DateTimeOffset nowUtc, CancellationToken cancellationToken = default);
// Startup reconciliation: expected-set query and insert-if-absent staging
///
/// Returns the set of instances that central considers deployed for the given site — the join of
/// DeployedConfigSnapshot (by InstanceId) with Instance (where SiteId == siteId).
/// Instances without a snapshot are excluded. No ConfigurationJson is loaded; the full
/// config is fetched on demand via the HTTP endpoint using a freshly-minted token from
/// .
///
/// The site primary key to filter on.
/// A cancellation token that can be used to cancel the operation.
/// All deployed instances for the site, as lightweight projections.
Task> GetExpectedDeploymentsForSiteAsync(int siteId, CancellationToken cancellationToken = default);
///
/// Inserts a for the instance ONLY IF no pending row already
/// exists for that InstanceId. An existing pending row signals an in-flight deploy that
/// is already delivering to the node — do NOT supersede it (contrast with
/// , which supersedes). Returns true if the row
/// was staged, false if an existing row was found and left untouched.
/// This method is self-contained: it commits its own save and returns a meaningful result,
/// matching the convention of .
///
/// The instance to stage the pending deployment for.
/// The deployment ID (fetch key).
/// Revision hash of the flattened configuration.
/// JSON-serialized flattened configuration.
/// Short-TTL fetch token the site node will present to the HTTP endpoint.
/// UTC timestamp for this pending row.
/// UTC expiry after which the token is no longer valid.
/// A cancellation token that can be used to cancel the operation.
/// true if staged; false if a pending row already existed and was left unchanged.
Task StagePendingIfAbsentAsync(int instanceId, string deploymentId, string revisionHash,
string configurationJson, string token, DateTimeOffset createdAtUtc, DateTimeOffset expiresAtUtc,
CancellationToken cancellationToken = default);
// Instance lookups for deployment pipeline
///
/// Gets an instance by ID.
///
/// The instance ID.
/// A cancellation token that can be used to cancel the operation.
/// The instance, or null if not found.
Task GetInstanceByIdAsync(int instanceId, CancellationToken cancellationToken = default);
///
/// Gets an instance by unique name.
///
/// The unique instance name.
/// A cancellation token that can be used to cancel the operation.
/// The instance, or null if not found.
Task GetInstanceByUniqueNameAsync(string uniqueName, CancellationToken cancellationToken = default);
///
/// Updates an instance.
///
/// The instance to update.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task UpdateInstanceAsync(Instance instance, CancellationToken cancellationToken = default);
///
/// Removes an instance and everything that depends on it: deployment
/// records, deployed config snapshot, attribute/alarm overrides, and
/// connection bindings.
///
/// The instance ID to delete.
/// A cancellation token that can be used to cancel the operation.
/// A task representing the asynchronous operation.
Task DeleteInstanceAsync(int instanceId, CancellationToken cancellationToken = default);
///
/// Saves all pending changes to the database.
///
/// A cancellation token that can be used to cancel the operation.
/// A task representing the number of entities saved.
Task SaveChangesAsync(CancellationToken cancellationToken = default);
///
/// Abandons every pending (added / modified / deleted) change on the unit of
/// work, leaving the underlying store untouched, and returns how many entries
/// were dropped.
///
///
/// Recovery only. A failure does NOT
/// untrack the entity that caused it: it stays in the change set and is
/// re-attempted by the NEXT save, so one unsavable entity poisons every
/// later save made through the same unit of work. That matters most on the
/// bulk site-deploy path, where all three phases share ONE scoped context
/// across every instance in the batch — a persistent failure on instance 1
/// would otherwise fail instance 2..N's status writes as well, including the
/// cleanup write that exists precisely to stop records being stranded
/// InProgress. Call this only after a save has already failed and its
/// pending work has been given up on; anything not yet committed is lost by
/// design.
///
///
/// The number of tracked entries detached (0 when nothing was pending).
int DetachPendingChanges();
}