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

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

/// <summary>
/// Data access for the central KPI-history backbone ("KPI History &amp; Trends")
/// — the tall / EAV <c>KpiSample</c> table in central MS SQL. Backs the recorder
/// singleton's bulk write, the bucketed query path that feeds the reusable trend
/// chart, and the retention purge. Implementation lives in the Configuration
/// Database component.
/// </summary>
public interface IKpiHistoryRepository
{
    /// <summary>
    /// Bulk-inserts a batch of captured samples (one sampling pass across all
    /// registered sources).
    /// </summary>
    /// <param name="samples">The samples to record.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task that represents the asynchronous write.</returns>
    Task RecordSamplesAsync(IReadOnlyCollection<KpiSample> samples, CancellationToken cancellationToken = default);

    /// <summary>
    /// Returns the raw points for one series in <c>[fromUtc, toUtc]</c>, ordered by
    /// <see cref="KpiSample.CapturedAtUtc"/> ascending.
    /// </summary>
    /// <param name="source">Source identifier — a value from <see cref="KpiSources"/>.</param>
    /// <param name="metric">Metric name from the source's catalog.</param>
    /// <param name="scope">Scope discriminator — a value from <see cref="KpiScopes"/>.</param>
    /// <param name="scopeKey">Scope qualifier (site id / node name); <c>null</c> for the Global scope.</param>
    /// <param name="fromUtc">Inclusive lower bound of the time window (UTC).</param>
    /// <param name="toUtc">Inclusive upper bound of the time window (UTC).</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>
    /// A task resolving to the raw series points in ascending capture order; empty when
    /// the series has no samples in the window.
    /// </returns>
    Task<IReadOnlyList<KpiSeriesPoint>> GetRawSeriesAsync(
        string source, string metric, string scope, string? scopeKey,
        DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);

    /// <summary>
    /// Bulk-deletes rows whose <see cref="KpiSample.CapturedAtUtc"/> is strictly older
    /// than <paramref name="before"/> (retention purge). Implementations slice the
    /// purge into time-windowed DELETE batches so a large catch-up (e.g. after an
    /// outage) never runs as a single lock/log-heavy transaction (arch-review 04, P4).
    /// </summary>
    /// <param name="before">UTC cut-off; rows captured before this instant are deleted.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task resolving to the number of rows deleted.</returns>
    Task<int> PurgeOlderThanAsync(DateTime before, CancellationToken cancellationToken = default);

    /// <summary>
    /// Folds the raw <c>KpiSample</c> rows in the window into the hourly
    /// <c>KpiRollupHourly</c> table — one row per
    /// (<c>Source</c>, <c>Metric</c>, <c>Scope</c>, <c>ScopeKey</c>) per UTC hour.
    /// The window is truncated to whole-hour boundaries: only the complete hours in
    /// <c>[truncate(fromHourUtc), truncate(toHourUtc))</c> are folded — the upper bound
    /// is <em>exclusive</em>, so a caller that passes the current hour's start as
    /// <paramref name="toHourUtc"/> never folds the in-progress (incomplete) hour.
    /// </summary>
    /// <remarks>
    /// <para>
    /// For each (series, hour) group the fold records <c>SampleCount</c> (raw rows in the
    /// group), <c>MinValue</c>/<c>MaxValue</c> (min/max raw value), and a <c>Value</c>
    /// whose aggregation is chosen per metric by
    /// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi.KpiMetricAggregationCatalog.Resolve(string, string)"/>:
    /// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi.KpiRollupAggregation.Gauge"/> stores the
    /// last (latest-timestamp) value in the hour; <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi.KpiRollupAggregation.Rate"/>
    /// stores the sum of values in the hour.
    /// </para>
    /// <para>
    /// The write is an idempotent upsert keyed on
    /// (<c>Source</c>, <c>Metric</c>, <c>Scope</c>, <c>ScopeKey</c>, <c>HourStartUtc</c>)
    /// with <c>null</c> <c>ScopeKey</c> participating in equality: an existing row is updated
    /// in place, otherwise a new row is inserted. Re-running the fold over the same window
    /// yields identical rows (no duplicates, no double-counting), so a singleton-failover
    /// handover that missed a tick self-heals on the next re-fold. Metrics that are only
    /// conditionally emitted (missing in some minutes) are handled naturally by grouping.
    /// </para>
    /// </remarks>
    /// <param name="fromHourUtc">Inclusive lower bound of the fold window (UTC), truncated to the hour.</param>
    /// <param name="toHourUtc">Exclusive upper bound of the fold window (UTC), truncated to the hour; pass the current hour's start to exclude the in-progress hour.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task that represents the asynchronous fold + upsert.</returns>
    Task FoldHourlyRollupsAsync(
        DateTime fromHourUtc, DateTime toHourUtc, CancellationToken cancellationToken = default);

    /// <summary>
    /// Returns the hourly rollup points for one series in <c>[fromUtc, toUtc]</c>, ordered by
    /// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi.KpiRollupHourly.HourStartUtc"/>
    /// ascending. Same contract and return shape as
    /// <see cref="GetRawSeriesAsync"/> (each point carries the hour start plus the folded
    /// <c>Value</c>) so the downstream bucketer is agnostic to raw-vs-rollup source.
    /// </summary>
    /// <param name="source">Source identifier — a value from <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi.KpiSources"/>.</param>
    /// <param name="metric">Metric name from the source's catalog.</param>
    /// <param name="scope">Scope discriminator — a value from <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi.KpiScopes"/>.</param>
    /// <param name="scopeKey">Scope qualifier (site id / node name); <c>null</c> for the Global scope.</param>
    /// <param name="fromUtc">Inclusive lower bound of the time window (UTC).</param>
    /// <param name="toUtc">Inclusive upper bound of the time window (UTC).</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>
    /// A task resolving to the hourly series points in ascending hour order; empty when the
    /// series has no rollups in the window.
    /// </returns>
    Task<IReadOnlyList<KpiSeriesPoint>> GetHourlySeriesAsync(
        string source, string metric, string scope, string? scopeKey,
        DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);

    /// <summary>
    /// Returns the newest <c>KpiRollupHourly.HourStartUtc</c> across all series, or
    /// <c>null</c> when no rollups exist. The recorder's one-shot backfill consults
    /// this watermark to skip (or shrink to) the un-rolled tail instead of re-folding
    /// the whole raw-retention window on every failover (arch-review 04 round 2, R1).
    /// </summary>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task resolving to the newest folded hour start, or <c>null</c> when empty.</returns>
    Task<DateTime?> GetLatestRollupHourAsync(CancellationToken cancellationToken = default);

    /// <summary>
    /// Bulk-deletes hourly rollup rows whose
    /// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi.KpiRollupHourly.HourStartUtc"/> is
    /// strictly older than <paramref name="before"/> (rollup retention purge). Mirrors
    /// <see cref="PurgeOlderThanAsync"/>: the purge is sliced into one-hour DELETE batches
    /// so a large catch-up never runs as a single lock/log-heavy transaction
    /// (arch-review 04, P4).
    /// </summary>
    /// <param name="before">UTC cut-off; rollups whose hour start is before this instant are deleted.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task that represents the asynchronous purge.</returns>
    Task PurgeRollupsOlderThanAsync(DateTime before, CancellationToken cancellationToken = default);
}
