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)
    {
        const string sql = """
            SELECT
                COUNT(*) as detected,
                COUNT(CASE WHEN resolved_at IS NOT NULL THEN 1 END) as resolved,
                MAX(detected_at) as last_check
            FROM compliance.operation_audit_trail
            WHERE event_type = 'DUPLICATE_DETECTED'
              AND detected_at >= @sevenDaysAgo
              AND published_at <= @now
            """;

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

        return result;
    }

    public async Task<(int Detected, int Resolved, List<string> Pending)?> GetReconciliationBreaksAsync(CancellationToken cancellationToken = default)
    {
        const string sql = """
            SELECT
                COUNT(*) as detected,
                COUNT(CASE WHEN resolved_at IS NOT NULL THEN 1 END) as resolved,
                STRING_AGG(DISTINCT (details->>'reason'), ', ') as reasons
            FROM compliance.operation_audit_trail
            WHERE event_type = 'RECONCILIATION_BREAK_DETECTED'
              AND detected_at >= @sevenDaysAgo
              AND published_at <= @now
            """;

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

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

        var (detected, resolved, reasons) = result.Value;
        var pending = string.IsNullOrEmpty(reasons)
            ? new List<string>()
            : reasons.Split(',').Select(r => r.Trim()).Take(5).ToList();

        return (detected, resolved, pending);
    }

    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;
    }
}
