using Hangfire;
using KArtSell.BuildingBlocks.Data;
using KArtSell.BuildingBlocks.Reliability;
using KArtSell.BuildingBlocks.Time;
using KArtSell.Modules.ModelOperations.ShadowRun;
using Microsoft.Extensions.Logging;

namespace KArtSell.Host.Jobs;

/// <summary>
/// Orchestrates 252+ trading-day shadow run for model validation.
///
/// Workflow:
/// 1. DataBackfill: Fetch OHLCV, FeeSchedule, MarketCalendar
/// 2. Replay: Simulate model signals, orders, fills across window
/// 3. EvaluationMetrics: Calculate Sharpe, PBO, DSR, etc.
/// 4. Validation: Check all gates (PBO ≤ 20%, DSR ≥ 95%, Cost 2x positive)
/// 5. Persist: Store result in database
///
/// Idempotency: IdempotencyKey + CorrelationId allow safe replay.
/// Queue: q-research (non-critical, can wait for market data)
/// Retry: Transient failures (network) trigger retry; permanent (bad model) logged.
/// </summary>
public sealed class ShadowRunJob(
    DataBackfiller backfiller,
    ReplayEngine replay,
    MetricsCalculator calculator,
    ShadowRunQueries queries,
    IDbConnectionFactory connectionFactory,
    IOutboxWriter outboxWriter,
    IClock clock,
    ILogger<ShadowRunJob> logger)
{
    private const int MaxAttempts = 3;

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

    private static readonly Action<ILogger, Guid, Exception?> LogPhase1Complete =
        LoggerMessage.Define<Guid>(
            LogLevel.Information,
            new EventId(2, nameof(LogPhase1Complete)),
            "Shadow run {RunId} phase 1 (backfill) complete");

    private static readonly Action<ILogger, Guid, Exception?> LogPhase2Complete =
        LoggerMessage.Define<Guid>(
            LogLevel.Information,
            new EventId(3, nameof(LogPhase2Complete)),
            "Shadow run {RunId} phase 2 (replay) complete");

    private static readonly Action<ILogger, Guid, Exception?> LogPhase3Complete =
        LoggerMessage.Define<Guid>(
            LogLevel.Information,
            new EventId(4, nameof(LogPhase3Complete)),
            "Shadow run {RunId} phase 3 (evaluation) complete");

    private static readonly Action<ILogger, Guid, bool, Exception?> LogComplete =
        LoggerMessage.Define<Guid, bool>(
            LogLevel.Information,
            new EventId(5, nameof(LogComplete)),
            "Shadow run {RunId} complete; all gates passed: {AllGatesPassed}");

    private static readonly Action<ILogger, Guid, string, Exception?> LogError =
        LoggerMessage.Define<Guid, string>(
            LogLevel.Error,
            new EventId(6, nameof(LogError)),
            "Shadow run {RunId} failed: {ErrorMessage}");

    private static readonly Action<ILogger, Guid, Exception?> LogPhase4Complete =
        LoggerMessage.Define<Guid>(
            LogLevel.Information,
            new EventId(7, nameof(LogPhase4Complete)),
            "Shadow run {RunId} phase 4 (phase segmentation) complete");

    [Queue("q-evaluation")]
    [DisableConcurrentExecution(timeoutInSeconds: 1800)]  // 30 min for bulk historical (252+ days)
    [AutomaticRetry(Attempts = MaxAttempts, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
    public async Task ExecuteAsync(ShadowRunCommand command, CancellationToken cancellationToken = default)
    {
        LogStarted(logger, command.RunId, null);

        try
        {
            // Phase 1: Backfill data
            var ohlcvBars = await backfiller.BackfillOhlcvAsync(
                command.WindowStartDate, command.WindowEndDate,
                new[] { "KOSPI", "KOSDAQ" }.ToList(),  // Simplified: hardcoded tickers
                cancellationToken);

            var feeSchedule = await backfiller.BackfillFeeScheduleAsync(
                command.WindowStartDate, command.WindowEndDate, cancellationToken);

            LogPhase1Complete(logger, command.RunId, null);

            // Phase 2: Replay model
            var tradingSessions = ohlcvBars
                .Select(b => b.Date)
                .Distinct()
                .OrderBy(d => d)
                .ToList();

            var replayResult = await replay.ReplayAsync(
                command.ModelId, ohlcvBars, feeSchedule,
                initialCashBalance: 10_000_000m,  // 10M starting cash
                tradingSessions, cancellationToken);

            LogPhase2Complete(logger, command.RunId, null);

            // Phase 3: Calculate metrics
            var metrics = await calculator.CalculateAsync(
                replayResult, ohlcvBars, feeSchedule, cancellationToken);

            // CRITICAL: Validate metrics are not null (prevents silent failures)
            if (metrics == null)
            {
                logger.LogError("Shadow run {RunId} metrics calculation returned NULL", command.RunId);
                throw new InvalidOperationException($"Metrics cannot be null for run {command.RunId}");
            }

            LogPhase3Complete(logger, command.RunId, null);

            // Phase 4: Phase segmentation
            var phaseBreakdownDto = PhaseSegmentation.Segment(
                replayResult.DailyReturns.ToList());

            var phaseBreakdown = new PhaseBreakdown(
                BullMarket: ConvertPhaseMetrics(phaseBreakdownDto.BullMarket),
                BearMarket: ConvertPhaseMetrics(phaseBreakdownDto.BearMarket),
                Sideways: ConvertPhaseMetrics(phaseBreakdownDto.Sideways),
                HighVolatility: ConvertPhaseMetrics(phaseBreakdownDto.HighVolatility));

            LogPhase4Complete(logger, command.RunId, null);

            var costAnalysis = new CostAnalysis(
                BaseScenarioReturn: metrics.TotalReturn,
                TwoXCostReturn: metrics.TotalReturn * 0.5m,  // Simplified: linear cost impact
                PassesTwoXPositive: metrics.TotalReturn * 0.5m > 0);

            var falseExitAnalysis = new FalseExitAnalysis(
                FalseExitCount: 0,              // TODO: Computed from signals
                ReentrySuccessCount: 0,
                ReentrySuccessRate: 0,
                AverageDaysOutOfPosition: 0);

            var validationGates = new ValidationGates(
                PboUnder20: metrics.ProbOfBacktestOverfit <= 0.20m,
                DsrAbove95: metrics.DailySharePercentile >= 0.95m,
                CostTwoXPositive: costAnalysis.PassesTwoXPositive,
                AllGatesPassed: metrics.ProbOfBacktestOverfit <= 0.20m
                    && metrics.DailySharePercentile >= 0.95m
                    && costAnalysis.PassesTwoXPositive);

            var result = new ShadowRunResult(
                RunId: command.RunId,
                ModelId: command.ModelId,
                WindowStartDate: command.WindowStartDate,
                WindowEndDate: command.WindowEndDate,
                Status: validationGates.AllGatesPassed
                    ? ShadowRunStatus.EvaluationComplete
                    : ShadowRunStatus.EvaluationComplete,
                Metrics: metrics,
                PhaseAnalysis: phaseBreakdown,
                CostAnalysis: costAnalysis,
                FalseExitAnalysis: falseExitAnalysis,
                ValidationGates: validationGates,
                CreatedAt: clock.UtcNow);

            // Phase 5-6: Persist shadow run result + emit completion event (transactional)
            await EmitShadowRunCompletedEventAsync(
                result, command.CorrelationId, validationGates.AllGatesPassed, cancellationToken);

            LogComplete(logger, command.RunId, validationGates.AllGatesPassed, null);
        }
        catch (Exception ex)
        {
            LogError(logger, command.RunId, ex.Message, ex);
            throw;  // Hangfire will classify as transient/permanent based on exception type
        }
    }

    private async Task EmitShadowRunCompletedEventAsync(
        ShadowRunResult result,
        Guid correlationId,
        bool allGatesPassed,
        CancellationToken cancellationToken)
    {
        try
        {
            // CRITICAL: InsertShadowRunAsync MUST be called to persist metrics
            logger.LogDebug("Inserting shadow run {RunId} with metrics to database", result.RunId);
            await queries.InsertShadowRunAsync(result, cancellationToken);
            logger.LogDebug("Shadow run {RunId} metrics persisted successfully to database", result.RunId);

            var eventMessage = new OutboxMessage(
                MessageId: Guid.NewGuid(),
                EventType: "ShadowRunCompleted",
                SchemaVersion: 1,
                PayloadJson: System.Text.Json.JsonSerializer.Serialize(new
                {
                    result.RunId,
                    result.ModelId,
                    CorrelationId = correlationId,
                    AllGatesPassed = allGatesPassed,
                    result.Metrics.TotalReturn,
                    result.Metrics.SharpeRatio,
                    result.Metrics.ProbOfBacktestOverfit,
                    result.Metrics.DailySharePercentile,
                    CompletedAt = clock.UtcNow
                }),
                CorrelationId: correlationId.ToString(),
                OccurredAt: clock.UtcNow,
                PayloadHash: GeneratePayloadHash(result.RunId.ToString()));

            await using var connection = await connectionFactory.OpenAsync(cancellationToken);
            await using var transaction = await connection.BeginTransactionAsync(cancellationToken);

            try
            {
                await outboxWriter.AddAsync(connection, transaction, eventMessage, cancellationToken);
                await transaction.CommitAsync(cancellationToken);

                logger.LogInformation(
                    "Shadow run {RunId} completed; event emitted to outbox for async consumers",
                    result.RunId);
            }
            catch
            {
                await transaction.RollbackAsync(cancellationToken);
                throw;
            }
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to emit ShadowRunCompleted event for {RunId}", result.RunId);
            throw;
        }
    }

    private static string GeneratePayloadHash(string payload)
    {
        var hash = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(payload));
        return System.Convert.ToBase64String(hash);
    }

    private static PhaseMetrics ConvertPhaseMetrics(PhaseMetricsDto dto)
        => new PhaseMetrics(
            TradingDays: dto.TradingDays,
            Return: dto.Return,
            Sharpe: dto.Sharpe,
            WinRate: dto.WinRate,
            MaxDrawdown: dto.MaxDrawdown);
}
