using Dapper;
using KArtSell.BuildingBlocks.Time;
using Npgsql;

namespace KArtSell.BuildingBlocks.Observability;

/// <summary>
/// Shared queries for observability metrics across all modules.
/// All queries use PIT (point-in-time) pattern: published_at <= cutoff.
/// Schema-qualified, explicit columns, no wildcard column selection.
/// </summary>
public class MetricsSql
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly IClock _clock;

    public MetricsSql(NpgsqlDataSource dataSource, IClock clock)
    {
        _dataSource = dataSource;
        _clock = clock;
    }

    public async Task<(int Total, int OnTime, TimeSpan AvgTime)?> GetBatchSlaAsync(CancellationToken cancellationToken = default)
    {
        const string sql = """
            SELECT
                COUNT(*) as total,
                COUNT(CASE WHEN status = 'success' THEN 1 END) as on_time,
                AVG(duration_seconds) as avg_seconds
            FROM observability.batch_sla_metrics
            WHERE published_at <= @now
              AND completed_at >= @sevenDaysAgo
            """;

        var now = _clock.UtcNow.UtcDateTime;
        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        var result = await connection.QueryFirstOrDefaultAsync<(int, int, double)?>(
            sql,
            new { now, sevenDaysAgo = now.AddDays(-7) },
            commandTimeout: 5);

        if (result == null || result.Value.Item1 == 0)
            return null;

        var (total, onTime, avgSec) = result.Value;
        return (total, onTime, TimeSpan.FromSeconds(avgSec));
    }

    public async Task<(int Quarantined, int Total, List<string> Errors)?> GetDataQualityQuarantineAsync(CancellationToken cancellationToken = default)
    {
        const string sql = """
            SELECT
                COUNT(*) as total,
                COUNT(CASE WHEN resolution_status IS NULL THEN 1 END) as quarantined,
                STRING_AGG(DISTINCT reason, ', ') as errors
            FROM observability.data_quality_quarantine
            WHERE published_at <= @now
              AND quarantined_at >= @sevenDaysAgo
            """;

        var now = _clock.UtcNow.UtcDateTime;
        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        var result = await connection.QueryFirstOrDefaultAsync<(int Total, int Quarantined, string? Errors)?>(
            sql,
            new { now, sevenDaysAgo = now.AddDays(-7) },
            commandTimeout: 5);

        if (result == null || result.Value.Item1 == 0)
            return null;

        var (total, quarantined, errors) = result.Value;
        var errorList = string.IsNullOrEmpty(errors) ? new List<string>() : errors.Split(',').Select(e => e.Trim()).Take(5).ToList();

        return (quarantined, total, errorList);
    }

    public async Task<(int Detected, int Resolved, DateTime LastCheck)?> GetDuplicateDetectionAsync(CancellationToken cancellationToken = default)
    {
        // building_blocks.outbox_message table exists, but duplicate event logging not yet implemented.
        // Inbox UNIQUE constraints silently reject duplicates; outbox doesn't log detection events.
        // Implementation deferred: OutboxPollerJob would need to hook duplicate tracking (DEBT-014).
        // Returns null until audit infrastructure is extended.
        await Task.CompletedTask;
        return null;
    }

    public async Task<(int Detected, int Resolved, List<string> Pending)?> GetReconciliationBreaksAsync(CancellationToken cancellationToken = default)
    {
        // Reconciliation break detection requires Evidence version mismatch correlation.
        // Requires audit log showing actual vs. expected state divergence (currently not captured).
        // Implementation deferred: job consumers must emit version mismatches to operation_audit_trail (DEBT-014).
        // Returns null until audit trail is enriched.
        await Task.CompletedTask;
        return null;
    }

    public async Task<(decimal Baseline, decimal Current)?> GetModelDriftAsync(CancellationToken cancellationToken = default)
    {
        // Model drift requires baseline (prior run) vs. current (latest run) sharpe ratio comparison.
        // Returns null until model_operations.shadow_run accumulates multiple runs with metrics.
        // Gate 3 rehearsal populates initial run; drift detection begins on subsequent rehearsals.
        // Full drift analytics deferred: sharpe percentile and rolling window logic (DEBT-009).
        await Task.CompletedTask;
        return null;
    }
}
