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

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

/// <summary>
/// Site-side hook the
/// store-and-forward retry loop invokes after every cached-call attempt and
/// at terminal-state transitions, so the audit pipeline can emit
/// <c>ApiCallCached</c>/<c>DbWriteCached</c> per-attempt rows and the
/// <c>CachedResolve</c> terminal row under the original
/// <see cref="TrackedOperationId"/>.
/// </summary>
/// <remarks>
/// <para>
/// The interface deliberately uses <see cref="CachedCallAttemptOutcome"/>
/// rather than <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Enums.AuditStatus"/> so the
/// S&amp;F project does not need to depend on the audit vocabulary — the
/// bridge living in <c>ZB.MOM.WW.ScadaBridge.AuditLog</c> maps the outcome to the right
/// audit kind + status when materialising the <c>CachedCallTelemetry</c>
/// packet.
/// </para>
/// <para>
/// <b>Best-effort contract (alog.md §7):</b> implementations MUST swallow
/// internal failures rather than propagating to the S&amp;F service — a
/// thrown observer must not be misclassified as a transient delivery
/// failure and must not corrupt the retry-count bookkeeping.
/// </para>
/// </remarks>
public interface ICachedCallLifecycleObserver
{
    /// <summary>
    /// Called by the store-and-forward retry loop after every cached-call
    /// delivery attempt. Receives the message's TrackedOperationId-bearing id,
    /// the per-category channel discriminator, retry-count + last-error
    /// context, and whether the outcome reached a terminal state.
    /// </summary>
    /// <param name="context">Per-attempt context including the tracking id, outcome, and audit provenance fields.</param>
    /// <param name="ct">Cancellation token for the observation operation.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    Task OnAttemptCompletedAsync(CachedCallAttemptContext context, CancellationToken ct = default);
}

/// <summary>
/// Per-attempt context handed to <see cref="ICachedCallLifecycleObserver"/>.
/// </summary>
/// <param name="TrackedOperationId">
/// Tracking id parsed from the underlying <c>StoreAndForwardMessage.Id</c>.
/// </param>
/// <param name="Channel">
/// Trust-boundary channel string — <c>"ApiOutbound"</c> for ExternalSystem
/// cached calls, <c>"DbOutbound"</c> for cached DB writes.
/// </param>
/// <param name="Target">Human-readable target (system name / DB connection).</param>
/// <param name="SourceSite">Site id that submitted the cached call.</param>
/// <param name="Outcome">Per-attempt outcome.</param>
/// <param name="RetryCount">Number of retries performed so far (S&amp;F bookkeeping).</param>
/// <param name="LastError">Most recent error message (null on success).</param>
/// <param name="HttpStatus">Most recent HTTP status (null when not applicable).</param>
/// <param name="CreatedAtUtc">When the underlying S&amp;F message was first enqueued.</param>
/// <param name="OccurredAtUtc">When this attempt completed.</param>
/// <param name="DurationMs">Duration of the attempt in milliseconds (null when not measured).</param>
/// <param name="SourceInstanceId">Originating instance, when known.</param>
/// <param name="ExecutionId">
/// The originating script execution's
/// per-run correlation id, threaded through the store-and-forward buffer from
/// the cached-call enqueue path. The audit bridge stamps it onto the
/// retry-loop <c>ApiCallCached</c>/<c>DbWriteCached</c> Attempted and
/// <c>CachedResolve</c> rows so they correlate with the rest of the run.
/// <c>null</c> for rows buffered before this field was introduced (back-compat).
/// </param>
/// <param name="SourceScript">
/// The originating script identifier,
/// threaded alongside <paramref name="ExecutionId"/> so the retry-loop audit
/// rows carry the same <c>SourceScript</c> provenance the script-side cached
/// rows already do. <c>null</c> when not known.
/// </param>
/// <param name="ParentExecutionId">
/// The <c>ExecutionId</c> of the
/// inbound-API request that spawned the originating script execution,
/// threaded through the store-and-forward buffer alongside
/// <paramref name="ExecutionId"/>. The audit bridge stamps it onto the
/// retry-loop <c>ApiCallCached</c>/<c>DbWriteCached</c> Attempted and
/// <c>CachedResolve</c> rows so they correlate back to the spawning run.
/// <c>null</c> for a non-routed run and for rows buffered before this field
/// was introduced (back-compat).
/// </param>
public sealed record CachedCallAttemptContext(
    TrackedOperationId TrackedOperationId,
    string Channel,
    string Target,
    string SourceSite,
    CachedCallAttemptOutcome Outcome,
    int RetryCount,
    string? LastError,
    int? HttpStatus,
    DateTime CreatedAtUtc,
    DateTime OccurredAtUtc,
    int? DurationMs,
    string? SourceInstanceId,
    Guid? ExecutionId = null,
    string? SourceScript = null,
    Guid? ParentExecutionId = null);

/// <summary>
/// Coarse outcome of one cached-call delivery attempt, observed from inside
/// the store-and-forward retry loop. The audit bridge maps this to the
/// <c>ApiCallCached</c>/<c>DbWriteCached</c> Attempted row and, when terminal,
/// the corresponding <c>CachedResolve</c> row.
/// </summary>
public enum CachedCallAttemptOutcome
{
    /// <summary>Attempt delivered successfully — terminal Delivered state.</summary>
    Delivered,

    /// <summary>Attempt failed transiently; another retry will follow.</summary>
    TransientFailure,

    /// <summary>Attempt returned permanent failure — terminal Parked state (S&amp;F semantics).</summary>
    PermanentFailure,

    /// <summary>Retry budget exhausted — terminal Parked state.</summary>
    ParkedMaxRetries,
}
