using Dapper;
using KArtSell.BuildingBlocks.Data;
using KArtSell.BuildingBlocks.Time;
using Microsoft.Extensions.Logging;

namespace KArtSell.Host.Features.ShadowRun;

/// <summary>
/// Query shadow run status and results from database.
/// PIT-safe: uses published_at <= @cutoff for read consistency.
/// </summary>
public sealed class GetShadowRunQuery(
    IDbConnectionFactory connectionFactory,
    IClock clock,
    ILogger<GetShadowRunQuery> logger)
{
    private static readonly Action<ILogger, Guid, Exception?> LogQuerying =
        LoggerMessage.Define<Guid>(
            LogLevel.Debug,
            new EventId(1, nameof(LogQuerying)),
            "Querying shadow run {RunId}");

    private static readonly Action<ILogger, Guid, Exception?> LogNotFound =
        LoggerMessage.Define<Guid>(
            LogLevel.Information,
            new EventId(2, nameof(LogNotFound)),
            "Shadow run {RunId} not found");

    public async Task<GetShadowRunResponse?> GetAsync(Guid runId, CancellationToken cancellationToken)
    {
        LogQuerying(logger, runId, null);

        const string sql = """
            select
                run_id as RunId,
                model_id as ModelId,
                status as Status,
                created_at as CreatedAt,
                published_at as CompletedAt,
                metrics_json as MetricsJson,
                validation_gates_json as ValidationGatesJson,
                phase_analysis_json as PhaseAnalysisJson,
                cost_analysis_json as CostAnalysisJson,
                error_message as ErrorMessage
            from model_operations.shadow_run
            where run_id = @RunId
              and published_at <= @Cutoff
            """;

        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        var cutoff = clock.UtcNow;

        var row = await connection.QuerySingleOrDefaultAsync<dynamic>(
            new CommandDefinition(
                sql,
                new { RunId = runId, Cutoff = cutoff },
                cancellationToken: cancellationToken));

        if (row == null)
        {
            LogNotFound(logger, runId, null);
            return null;
        }

        // Deserialize JSONB fields
        var metrics = string.IsNullOrEmpty(row.MetricsJson)
            ? null
            : DeserializeMetrics(row.MetricsJson);

        var gates = string.IsNullOrEmpty(row.ValidationGatesJson)
            ? null
            : DeserializeGates(row.ValidationGatesJson);

        return new GetShadowRunResponse(
            RunId: (Guid)row.RunId,
            ModelId: (Guid)row.ModelId,
            Status: (string)row.Status,
            CreatedAt: (DateTimeOffset)row.CreatedAt,
            CompletedAt: (DateTimeOffset?)row.CompletedAt,
            Metrics: metrics,
            ValidationGates: gates,
            ErrorMessage: (string?)row.ErrorMessage);
    }

    private static ShadowRunMetricsDto? DeserializeMetrics(string json)
    {
        try
        {
            return System.Text.Json.JsonSerializer.Deserialize<ShadowRunMetricsDto>(json);
        }
        catch
        {
            return null;
        }
    }

    private static ValidationGatesDto? DeserializeGates(string json)
    {
        try
        {
            return System.Text.Json.JsonSerializer.Deserialize<ValidationGatesDto>(json);
        }
        catch
        {
            return null;
        }
    }
}

public sealed record ShadowRunMetricsDto(
    decimal TotalReturn,
    decimal SharpeRatio,
    decimal CalmurRatio,
    decimal MaximumDrawdown,
    decimal WinRate,
    decimal ProbOfBacktestOverfit,
    decimal DailySharePercentile,
    int TradingDays);

public sealed record ValidationGatesDto(
    bool PboUnder20,
    bool DsrAbove95,
    bool CostTwoXPositive,
    bool AllGatesPassed);
