namespace ZB.MOM.WW.ScadaBridge.SiteCallAudit;

/// <summary>
/// Configuration options for the Site Call Audit: stuck-call detection +
/// KPI windowing for the read-side, plus the cadence/retention knobs for the
/// two central-singleton schedulers — the periodic per-site reconciliation
/// pull (self-heal for lost telemetry) and the daily terminal-row purge.
/// Mirrors the KPI-relevant subset of <c>NotificationOutboxOptions</c> and the
/// scheduler-cadence shape of <c>SiteAuditReconciliationOptions</c> /
/// <c>AuditLogPurgeOptions</c>.
/// </summary>
public class SiteCallAuditOptions
{
    /// <summary>
    /// Age past which a non-terminal cached call (<c>Pending</c>/<c>Retrying</c>)
    /// is considered stuck. Display-only — surfaced as the Stuck KPI and a row
    /// badge, with no escalation. Default 10 minutes, matching
    /// <c>NotificationOutboxOptions.StuckAgeThreshold</c>.
    /// </summary>
    public TimeSpan StuckAgeThreshold { get; set; } = TimeSpan.FromMinutes(10);

    /// <summary>
    /// Trailing window used to compute the delivered- and failed-last-interval
    /// throughput KPIs. Default 1 minute, matching
    /// <c>NotificationOutboxOptions.DeliveredKpiWindow</c>.
    /// </summary>
    public TimeSpan KpiInterval { get; set; } = TimeSpan.FromMinutes(1);

    /// <summary>
    /// Ask timeout for the central→site Retry/Discard relay. When
    /// the owning site does not ack a <c>RetryParkedOperation</c> /
    /// <c>DiscardParkedOperation</c> within this window — site offline, no
    /// route to the site, or central buffering deliberately absent — the relay
    /// reports a <c>SiteUnreachable</c> outcome. Default 10 seconds: long enough
    /// to absorb a healthy cross-cluster round-trip, short enough that an
    /// operator clicking Retry on an offline site gets a fast, honest answer.
    /// <para>
    /// <b>Ordering invariant:</b> <c>RelayTimeout</c> must stay below
    /// <c>CommunicationOptions.QueryTimeout</c> (default 30s), the timeout the
    /// outer <c>CommunicationService.RetrySiteCallAsync</c>/<c>DiscardSiteCallAsync</c>
    /// Ask of the <c>SiteCallAuditActor</c> uses. The outer Ask must outlive this
    /// inner site relay Ask so the inner relay times out first and yields the
    /// distinct <c>SiteUnreachable</c> outcome; if the outer Ask expired first,
    /// that outcome would be lost to a generic Ask-timeout exception. The
    /// defaults (10s &lt; 30s) satisfy this — keep the gap when tuning either.
    /// </para>
    /// </summary>
    public TimeSpan RelayTimeout { get; set; } = TimeSpan.FromSeconds(10);

    // ── Reconciliation tick: periodic per-site self-heal pull ──

    /// <summary>
    /// Period of the reconciliation tick. Each tick visits every known site
    /// once, pulls changed <c>SiteCall</c> rows since a per-site cursor, and
    /// upserts them idempotently — the documented self-heal when best-effort
    /// push telemetry is lost. Default 5 minutes, matching the sibling
    /// <c>SiteAuditReconciliationOptions</c> cadence. Clamped to at least
    /// <see cref="MinReconciliationInterval"/> via <see cref="ReconciliationInterval"/>.
    /// </summary>
    public TimeSpan ReconciliationInterval { get; set; } = TimeSpan.FromMinutes(5);

    /// <summary>
    /// Test-only override for the reconciliation tick cadence — bypasses the
    /// <see cref="MinReconciliationInterval"/> clamp so unit tests can drop the
    /// cadence to milliseconds. Production config never sets this; leave null.
    /// </summary>
    public TimeSpan? ReconciliationIntervalOverride { get; set; }

    /// <summary>
    /// Maximum number of <c>SiteCall</c> rows requested per <c>PullSiteCalls</c>
    /// RPC. Default 500. A <c>MoreAvailable=true</c> response signals the cursor
    /// advanced and the next tick should keep draining the backlog.
    /// </summary>
    public int ReconciliationBatchSize { get; set; } = 500;

    /// <summary>
    /// Minimum interval the config-bound <see cref="ReconciliationInterval"/> can
    /// resolve to. Clamps a misconfigured <c>0</c> (or negative) value away from
    /// <see cref="TimeSpan.Zero"/>, which would make Akka's
    /// <c>ScheduleTellRepeatedlyCancelable</c> spin — the exact footgun flagged in
    /// a prior review of the sibling reconciliation options.
    /// </summary>
    private static readonly TimeSpan MinReconciliationInterval = TimeSpan.FromSeconds(1);

    /// <summary>
    /// Resolves the effective reconciliation tick interval: the test override
    /// when set (bypassing the clamp), otherwise <see cref="ReconciliationInterval"/>
    /// clamped to at least <see cref="MinReconciliationInterval"/> so a
    /// zero/negative config value can never yield <see cref="TimeSpan.Zero"/>.
    /// </summary>
    public TimeSpan ResolvedReconciliationInterval =>
        ReconciliationIntervalOverride is { } o
            ? o
            : ReconciliationInterval < MinReconciliationInterval
                ? MinReconciliationInterval
                : ReconciliationInterval;

    // ── Purge scheduler: daily terminal-row purge ──

    /// <summary>
    /// Period of the purge tick. Each tick drops terminal <c>SiteCalls</c> rows
    /// older than the retention window via
    /// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories.ISiteCallAuditRepository.PurgeTerminalAsync"/>.
    /// Default 24 hours, matching <c>AuditLogPurgeOptions</c>. Clamped to at
    /// least <see cref="MinPurgeInterval"/> via <see cref="ResolvedPurgeInterval"/>.
    /// </summary>
    public TimeSpan PurgeInterval { get; set; } = TimeSpan.FromHours(24);

    /// <summary>
    /// Test-only override for the purge tick cadence — bypasses the
    /// <see cref="MinPurgeInterval"/> clamp so unit tests can drop the cadence
    /// to milliseconds. Production config never sets this; leave null.
    /// </summary>
    public TimeSpan? PurgeIntervalOverride { get; set; }

    /// <summary>
    /// Retention window for terminal rows. On each purge tick a row whose
    /// <c>TerminalAtUtc</c> is older than <c>UtcNow - RetentionDays</c> is
    /// deleted; non-terminal rows are never purged. Default 365 days, matching
    /// the central audit-store retention policy.
    /// </summary>
    public int RetentionDays { get; set; } = 365;

    /// <summary>
    /// Minimum interval the config-bound <see cref="PurgeInterval"/> can resolve
    /// to. Clamps a misconfigured <c>0</c> (or negative) value away from
    /// <see cref="TimeSpan.Zero"/> for the same scheduler-spin reason as
    /// <see cref="MinReconciliationInterval"/>; the purge is daily so the floor
    /// is a more generous 1 minute.
    /// </summary>
    private static readonly TimeSpan MinPurgeInterval = TimeSpan.FromMinutes(1);

    /// <summary>
    /// Resolves the effective purge tick interval: the test override when set
    /// (bypassing the clamp), otherwise <see cref="PurgeInterval"/> clamped to at
    /// least <see cref="MinPurgeInterval"/>.
    /// </summary>
    public TimeSpan ResolvedPurgeInterval =>
        PurgeIntervalOverride is { } o
            ? o
            : PurgeInterval < MinPurgeInterval
                ? MinPurgeInterval
                : PurgeInterval;
}
