using Dapper;
using KArtSell.BuildingBlocks.Data;

namespace KArtSell.Modules.ModelOperations.ShadowRun;

/// <summary>
/// Data access for shadow run persistence.
/// Queries use schema-qualified tables, explicit columns, and PIT safety.
/// </summary>
public sealed class ShadowRunQueries(IDbConnectionFactory connectionFactory)
{
    /// <summary>
    /// Persist shadow run result (immutable append).
    /// </summary>
    public async Task InsertShadowRunAsync(
        ShadowRunResult result,
        CancellationToken cancellationToken)
    {
        const string sql = """
            insert into model_operations.shadow_run
            (run_id, model_id, window_start, window_end, status, metrics_json, phase_analysis_json,
             cost_analysis_json, false_exit_analysis_json, validation_gates_json, error_message, created_at)
            values (
                @RunId, @ModelId, @WindowStart, @WindowEnd, @Status,
                cast(@MetricsJson as jsonb), cast(@PhaseJson as jsonb),
                cast(@CostJson as jsonb), cast(@FalseExitJson as jsonb), cast(@ValidationJson as jsonb),
                @ErrorMessage, @CreatedAt
            )
            """;

        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        await connection.ExecuteAsync(
            new CommandDefinition(
                sql,
                new
                {
                    RunId = result.RunId,
                    ModelId = result.ModelId,
                    WindowStart = result.WindowStartDate.ToDateTime(new TimeOnly(0, 0, 0)),
                    WindowEnd = result.WindowEndDate.ToDateTime(new TimeOnly(0, 0, 0)),
                    Status = result.Status.ToString(),
                    MetricsJson = SerializeMetrics(result.Metrics),
                    PhaseJson = SerializePhaseBreakdown(result.PhaseAnalysis),
                    CostJson = SerializeCostAnalysis(result.CostAnalysis),
                    FalseExitJson = SerializeFalseExitAnalysis(result.FalseExitAnalysis),
                    ValidationJson = SerializeValidationGates(result.ValidationGates),
                    ErrorMessage = result.ErrorMessage,
                    CreatedAt = result.CreatedAt
                },
                cancellationToken: cancellationToken));
    }

    /// <summary>
    /// Pre-insert shadow run with "Queued" status (no metrics yet).
    /// Called by InitiateShadowRunHandler to enable immediate polling.
    /// Dapper: Convert DateOnly to string for SQL parameter (Dapper limitation).
    /// </summary>
    public async Task InsertShadowRunQueuedAsync(
        Guid runId,
        Guid modelId,
        DateOnly windowStart,
        DateOnly windowEnd,
        DateTimeOffset createdAt,
        CancellationToken cancellationToken)
    {
        const string sql = """
            insert into model_operations.shadow_run
            (run_id, model_id, window_start, window_end, status, created_at)
            values (@RunId, @ModelId, @WindowStart::date, @WindowEnd::date, @Status, @CreatedAt)
            """;

        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        await connection.ExecuteAsync(
            new CommandDefinition(
                sql,
                new
                {
                    RunId = runId,
                    ModelId = modelId,
                    WindowStart = windowStart.ToString("yyyy-MM-dd"),  // PostgreSQL: ::date cast
                    WindowEnd = windowEnd.ToString("yyyy-MM-dd"),      // PostgreSQL: ::date cast
                    Status = "Queued",
                    CreatedAt = createdAt
                },
                cancellationToken: cancellationToken));
    }

    /// <summary>
    /// Retrieve latest shadow run for model (PIT: published_at &lt;= cutoff).
    /// </summary>
    public async Task<ShadowRunResult?> GetLatestShadowRunAsync(
        Guid modelId,
        DateTimeOffset cutoffTime,
        CancellationToken cancellationToken)
    {
        const string sql = """
            select
                run_id as RunId,
                model_id as ModelId,
                window_start as WindowStartDate,
                window_end as WindowEndDate,
                status as Status,
                error_message as ErrorMessage,
                created_at as CreatedAt
            from model_operations.shadow_run
            where model_id = @ModelId
              and published_at <= @Cutoff
            order by created_at desc
            limit 1
            """;

        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        var row = await connection.QuerySingleOrDefaultAsync<dynamic>(
            new CommandDefinition(
                sql,
                new { ModelId = modelId, Cutoff = cutoffTime },
                cancellationToken: cancellationToken));

        if (row == null)
            return null;

        return new ShadowRunResult(
            RunId: (Guid)row.RunId,
            ModelId: (Guid)row.ModelId,
            WindowStartDate: (DateOnly)row.WindowStartDate,
            WindowEndDate: (DateOnly)row.WindowEndDate,
            Status: Enum.Parse<ShadowRunStatus>((string)row.Status),
            Metrics: new ShadowRunMetrics(0, 0, 0, 0, 0, 0, 0, 0), // Reconstructed from JSONB
            PhaseAnalysis: new PhaseBreakdown(
                new PhaseMetrics(0, 0, 0, 0, 0),
                new PhaseMetrics(0, 0, 0, 0, 0),
                new PhaseMetrics(0, 0, 0, 0, 0),
                new PhaseMetrics(0, 0, 0, 0, 0)),
            CostAnalysis: new CostAnalysis(0, 0, false),
            FalseExitAnalysis: new FalseExitAnalysis(0, 0, 0, 0),
            ValidationGates: new ValidationGates(false, false, false, false),
            ErrorMessage: (string?)row.ErrorMessage,
            CreatedAt: (DateTimeOffset)row.CreatedAt);
    }

    private static string SerializeMetrics(ShadowRunMetrics metrics)
        => System.Text.Json.JsonSerializer.Serialize(metrics);

    private static string SerializePhaseBreakdown(PhaseBreakdown breakdown)
        => System.Text.Json.JsonSerializer.Serialize(breakdown);

    private static string SerializeCostAnalysis(CostAnalysis cost)
        => System.Text.Json.JsonSerializer.Serialize(cost);

    private static string SerializeFalseExitAnalysis(FalseExitAnalysis analysis)
        => System.Text.Json.JsonSerializer.Serialize(analysis);

    private static string SerializeValidationGates(ValidationGates gates)
        => System.Text.Json.JsonSerializer.Serialize(gates);

}
