//! DQN Trainer constructor — `new_internal` and init helpers.

use std::collections::VecDeque;
use std::sync::Arc;
use std::time::Duration;

use anyhow::Result;
use ml_core::device::MlDevice;
use ml_core::fill_simulator::FillSimulator;
use risk::drawdown_monitor::DrawdownMonitor;
use risk::safety::position_limiter::HybridPositionLimiter;
use risk::safety::PositionLimiterConfig;
use rust_decimal::Decimal;
use tokio::sync::RwLock;
use tracing::info;

use crate::dqn::circuit_breaker::{CircuitBreaker, CircuitBreakerConfig};
use crate::dqn::curiosity::CuriosityModule;
use crate::dqn::dqn::{DQN, DQNConfig};
use crate::dqn::logging::{LoggingConfig, MetricsAggregator};
use crate::dqn::portfolio_tracker::PortfolioTracker;
use crate::dqn::regime_conditional::RegimeConditionalDQN;
use crate::dqn::reward::{RewardConfig, RewardFunction};
use crate::features::microstructure_features::*;
use crate::labeling::triple_barrier::TripleBarrierEngine;
use crate::memory_optimization::auto_batch_size::{AutoBatchSizer, BatchSizeConfig};
use crate::trainers::TargetUpdateMode;
use crate::TrainingMetrics;
use super::super::config::{DQNAgentType, DQNHyperparameters};
use super::DQNTrainer;

impl DQNTrainer {
    pub(crate) fn new_internal(mut hyperparams: DQNHyperparameters, debug_logging: bool, override_device: Option<MlDevice>) -> Result<Self> {
        // Validate batch size is non-zero
        if hyperparams.batch_size == 0 {
            return Err(anyhow::anyhow!(
                "Batch size must be greater than 0, got: {}",
                hyperparams.batch_size
            ));
        }

        // WAVE 26 P2.2: Validate gradient_accumulation_steps > 0
        if hyperparams.gradient_accumulation_steps == 0 {
            return Err(anyhow::anyhow!(
                "gradient_accumulation_steps must be greater than 0, got: {}",
                hyperparams.gradient_accumulation_steps
            ));
        }

        // Pre-compute hidden dims to get accurate model size for batch sizing.
        // Align input_dim to 8 so the log matches the actual model dimensions.
        // (device not yet created, so use the formula directly — CUDA always aligns)
        let ofi_pre = hyperparams.mbp10_data_dir.is_some();
        let input_dim: usize = if ofi_pre { 56 } else { 48 }; // (53+7)&!7=56, (45+7)&!7=48
        let output_dim: usize = 5;
        let hidden_dims: Vec<usize> = match hyperparams.hidden_dim_base {
            Some(base) => {
                let b = crate::cuda_pipeline::align_to_tensor_cores(base);
                vec![b, b] // Constant-width: no tapering, no silently-discarded narrow layer
            }
            None => {
                let profile = ml_core::gpu::profile::GpuProfile::load();
                let base = profile.training.hidden_dim_base;
                let b = crate::cuda_pipeline::align_to_tensor_cores(base);
                vec![b, b] // Constant-width: no tapering
            }
        };

        // Compute accurate model size from actual network dimensions
        let full_dims: Vec<usize> = std::iter::once(input_dim)
            .chain(hidden_dims.iter().copied())
            .chain(std::iter::once(output_dim))
            .collect();
        let param_count = crate::gpu::memory_profile::network_param_count(&full_dims);
        // FP32 params + AdamW state (2x for momentum/variance) + target network copy = ~4x
        let model_overhead_mb = (param_count as f64 * 4.0 * 4.0) / (1024.0 * 1024.0);
        info!(
            "DQN network: {:?} → {} params, {:.1} MB overhead",
            full_dims, param_count, model_overhead_mb
        );

        // Dynamic batch sizing: scale UP for larger GPUs, cap DOWN for smaller ones.
        // Uses HardwareBudget for consistent sizing across DQN/PPO.
        const STATIC_MAX_BATCH_SIZE: usize = 8192;
        let max_safe_batch = match AutoBatchSizer::new() {
            Ok(sizer) => {
                let config = BatchSizeConfig {
                    model_memory_mb: model_overhead_mb,
                    safety_margin: 0.15,
                    ..BatchSizeConfig::default()
                };
                let safe = sizer.max_safe_batch_size(&config);
                info!(
                    "AutoBatchSizer: GPU VRAM ceiling = {} (configured: {})",
                    safe, hyperparams.batch_size
                );
                safe
            }
            Err(e) => {
                info!(
                    "AutoBatchSizer unavailable ({}), using static cap: {}",
                    e, STATIC_MAX_BATCH_SIZE
                );
                STATIC_MAX_BATCH_SIZE
            }
        };

        // Cap to VRAM ceiling from AutoBatchSizer (no separate scale-UP —
        // AutoBatchSizer already accounts for model size and available VRAM)
        if hyperparams.batch_size > max_safe_batch {
            info!(
                "DQN batch_size capped from {} → {} (VRAM ceiling)",
                hyperparams.batch_size, max_safe_batch
            );
            hyperparams.batch_size = max_safe_batch;
        }

        // Use override device if provided (hyperopt shares one CUDA context),
        // otherwise require CUDA GPU — no CPU fallback in CUDA builds.
        let device = if let Some(dev) = override_device {
            dev
        } else {
            match MlDevice::cuda(0) {
                Ok(dev) => {
                    dev
                }
                Err(e) => {
                    tracing::error!("CUDA device init failed: {e}");
                    return Err(anyhow::anyhow!(
                        "DQNTrainer requires CUDA GPU. CUDA init failed: {e}"
                    ));
                }
            }
        };

        // Set CUDA stack size ONCE for all GPU kernels.
        // cuCtxSetLimit reserves stack_bytes × max_threads_per_SM × SMs of VRAM.
        // RTX 3050 (20 SMs × 1536 threads): 64KB → 1.88GB, 16KB → 480MB.
        // Stack size is driven by the GPU TOML profile (config/gpu/*.toml).
        #[cfg(feature = "cuda")]
        if let MlDevice::Cuda { ref context, .. } = device {
            use cudarc::driver::sys::{cuCtxSetLimit, CUlimit, CUresult};
            context.bind_to_thread().map_err(|e| anyhow::anyhow!("bind: {e}"))?;
            let gpu_profile = ml_core::gpu::profile::GpuProfile::load();
            let stack_bytes: usize = gpu_profile.cuda.cuda_stack_bytes;
            let result = unsafe { cuCtxSetLimit(CUlimit::CU_LIMIT_STACK_SIZE, stack_bytes) };
            if result != CUresult::CUDA_SUCCESS {
                tracing::warn!("cuCtxSetLimit(STACK_SIZE, {stack_bytes}) failed: {result:?} — may cause kernel crashes");
            } else {
                tracing::info!("CUDA stack: {stack_bytes} bytes/thread (num_atoms={}, profile)", hyperparams.num_atoms);
            }
        }

        // Fork a dedicated CudaStream once. All GPU components (experience collector,
        // fused trainer, portfolio sim, monitoring) share this single stream via
        // Arc::clone. This eliminates:
        // 1. Candle's default stream (stream 0) from the hot path
        // 2. Dual-stream cudarc event tracking conflicts during CUDA Graph capture
        // 3. Implicit serialization points between default and forked streams
        let cuda_stream: Option<Arc<cudarc::driver::CudaStream>> =
            if let MlDevice::Cuda { ref stream, .. } = device {
                let stream = stream.fork()
                    .map_err(|e| anyhow::anyhow!("Failed to fork CUDA stream: {e}"))?;
                info!("Forked dedicated CudaStream for all GPU components");
                Some(stream)
            } else {
                None
            };

        // Dynamic replay buffer sizing: scale replay capacity to available VRAM.
        // Only activates when replay_buffer_vram_fraction > 0 and GPU is detected.
        // Also computes the PER memory budget from actual VRAM — no hardcoded caps.
        //
        // Guard: skip auto-sizing when buffer_size < 100K (the sizer's own minimum).
        // A small explicit buffer_size means the caller wants that exact size
        // (e.g., smoke tests with buffer_size=1024). Without this, H100's 75 GB VRAM
        // inflates small buffers → 10M, causing empty-sample failures and multi-hour hangs.
        let original_buffer_size = hyperparams.buffer_size; // Save before AutoReplaySizer mutates it
        const AUTO_REPLAY_MIN_THRESHOLD: usize = 100_000;

        // Always probe VRAM for PER budget — no hardcoded fallback.
        let mut per_max_memory_bytes: usize = {
            use ml_core::memory_optimization::detect_gpu_hardware;
            match detect_gpu_hardware() {
                Ok(hw) => hw.per_max_buffer_bytes(),
                Err(e) => {
                    return Err(anyhow::anyhow!(
                        "GPU PER requires VRAM probe — detect_gpu_hardware failed: {}", e
                    ));
                }
            }
        };

        // Dynamic replay buffer sizing: scale replay capacity to available VRAM.
        // Only activates when replay_buffer_vram_fraction > 0, GPU detected, and
        // buffer_size >= 100K (sizer's own minimum — smaller values are intentional).
        if hyperparams.replay_buffer_vram_fraction > 0.0
            && matches!(device, MlDevice::Cuda { .. })
            && hyperparams.buffer_size >= AUTO_REPLAY_MIN_THRESHOLD
        {
            use ml_core::memory_optimization::detect_gpu_hardware;
            if let Ok(hw) = detect_gpu_hardware() {
                let raw_sd = if hyperparams.mbp10_data_dir.is_some() { 74 } else { 66 };
                let aligned_sd = (raw_sd + 7) & !7;
                let replay_cfg = hw.optimal_replay_config(
                    aligned_sd,
                    hyperparams.replay_buffer_vram_fraction,
                );
                per_max_memory_bytes = replay_cfg.per_max_buffer_bytes;
                if replay_cfg.capacity != hyperparams.buffer_size {
                    info!(
                        "AutoReplaySizer: replay buffer {} -> {} (VRAM={:.0}MB, fraction={:.0}%, PER budget={:.0}MB)",
                        hyperparams.buffer_size,
                        replay_cfg.capacity,
                        hw.free_memory_mb,
                        hyperparams.replay_buffer_vram_fraction * 100.0,
                        per_max_memory_bytes as f64 / (1024.0 * 1024.0),
                    );
                    hyperparams.buffer_size = replay_cfg.capacity;
                }
            }
        }

        info!(
            "Initializing DQN trainer on device: {:?}, using 5 exposure actions + OrderRouter",
            "CUDA GPU",
        );

        // Create DQN configuration
        // 42-feature architecture: OHLCV, technical, patterns, volume, time, statistical, regime
        // Portfolio features (3) are populated via PortfolioTracker → 45 total state_dim
        // With MBP-10 OFI features: +8 OFI features → 53 total
        //
        // GpuTensor core alignment: state_dim is rounded up to the next multiple of 8
        // (53→56, 45→48) so that cuBLAS dispatches BF16 HMMA instructions instead
        // of falling back to scalar FMA. The extra columns are zero-padded at the
        // data pipeline boundaries (GpuPreloadedData and train_batch CPU path).
        let ofi_enabled = hyperparams.mbp10_data_dir.is_some();
        let raw_state_dim = if ofi_enabled { 74 } else { 66 }; // 42 market + 8 portfolio + (8 OFI)
        let state_dim = (raw_state_dim + 7) & !7; // aligned: 64 with OFI, 56 without
        let config = DQNConfig {
            state_dim,
            num_actions: 9,                  // 9 exposure levels (S100..L100 in 25% steps)
            hidden_dims,
            learning_rate: hyperparams.learning_rate,
            gamma: hyperparams.gamma as f32,
            epsilon_start: hyperparams.epsilon_start as f32,
            epsilon_end: hyperparams.epsilon_end as f32,
            epsilon_decay: hyperparams.epsilon_decay as f32,
            replay_buffer_capacity: hyperparams.buffer_size,
            collapse_warmup_capacity: original_buffer_size,
            batch_size: hyperparams.batch_size,
            min_replay_size: hyperparams.min_replay_size.min(hyperparams.buffer_size), // Cap at buffer_size to prevent deadlock
            target_update_freq: hyperparams.target_update_frequency, // Use hyperparameter instead of hardcoded 1000
            huber_delta: hyperparams.huber_delta as f32,
            leaky_relu_alpha: 0.01, // Standard LeakyReLU alpha (prevents dead neurons)
            gradient_clip_norm: hyperparams.gradient_clip_norm.unwrap_or(10.0), // Wave 11 Bug #1 fix: Dynamic clipping

            // WAVE 16 (Agent 36): Target update configuration
            tau: hyperparams.tau,
            tau_final: hyperparams.tau * 0.1,  // Anneal to 10% of base tau
            tau_anneal_steps: 100_000,
            use_soft_updates: matches!(hyperparams.target_update_mode, TargetUpdateMode::Soft),

            // Rainbow DQN warmup period
            warmup_steps: hyperparams.warmup_steps,

            // PER configuration
            initial_capital: hyperparams.initial_capital as f64,

            per_alpha: hyperparams.per_alpha,
            per_beta_start: hyperparams.per_beta_start,
            per_beta_max: 1.0,
            per_beta_annealing_steps: hyperparams.epochs * 500, // reach beta=1.0 by ~25% of training (was 2000 → too slow)
            per_max_memory_bytes,

            // Wave 2.1: Dueling Networks (ENABLED BY DEFAULT - Wave 6.4)
            dueling_hidden_dim: hyperparams.dueling_hidden_dim,

            // Wave 2.2: Multi-Step Returns (N-step TD) (ENABLED BY DEFAULT - Wave 6.4)
            n_steps: hyperparams.n_steps,  // Default: 3 (Rainbow DQN standard)

            // Wave 2.3: Distributional RL (C51) (always enabled)
            num_atoms: hyperparams.num_atoms,                    // Rainbow DQN standard: 51 atoms
            v_min: hyperparams.v_min as f32,                     // Minimum value for distribution support
            v_max: hyperparams.v_max as f32,                     // Maximum value for distribution support

            // Wave 2.4: Noisy Networks for Exploration (always enabled)
            noisy_sigma_init: hyperparams.noisy_sigma_init,      // Rainbow DQN standard: 0.5

            // BUG #37 FIX: Q-value clipping (prevents step-level explosions)
            enable_q_value_clipping: true,
            q_value_clip_min: -500.0,
            q_value_clip_max: 500.0,

            // WAVE 23 P0 Fix #1: Adaptive gradient collapse threshold (from hyperparams)
            gradient_collapse_multiplier: hyperparams.gradient_collapse_multiplier,
            gradient_collapse_patience: hyperparams.gradient_collapse_patience,

            cql_alpha: hyperparams.cql_alpha,
            iqn_num_quantiles: hyperparams.num_quantiles,       // Controlled by hyperopt
            iqn_kappa: hyperparams.qr_kappa as f32,            // Controlled by hyperopt (f64→f32)
            iqn_embedding_dim: 64,                              // Fixed (not in search space)
            iqn_lambda: hyperparams.iqn_lambda as f32,          // IQN dual-head loss weight (f64→f32)
            branch_hidden_dim: hyperparams.branch_hidden_dim,
            use_regime_conditioning: true, // Always enable per-regime IS weights for branching loss
            use_cvar_action_selection: hyperparams.use_cvar_action_selection,
            cvar_alpha: hyperparams.cvar_alpha,

            #[allow(clippy::cast_possible_truncation)]
            minimum_profit_factor: hyperparams.minimum_profit_factor as f32,
            weight_decay: hyperparams.weight_decay,
            dropout_rate: if hyperparams.enable_dropout_scheduler { hyperparams.dropout_initial } else { 0.0 },
            entropy_coefficient: hyperparams.entropy_coefficient.unwrap_or(0.01),
            noisy_epsilon_floor: hyperparams.noisy_epsilon_floor.unwrap_or(0.0) as f32, // C2: NoisyNet handles exploration
            use_count_bonus: hyperparams.count_bonus_coefficient.unwrap_or(0.0) > 0.0, // C3 FIX: enable when coefficient > 0
            count_bonus_coefficient: hyperparams.count_bonus_coefficient.unwrap_or(0.0),
            ..DQNConfig::default()
        };

        // Extract curiosity dims before config is moved into the agent
        let curiosity_market_dim = config.curiosity_market_dim;
        let curiosity_hidden_dim = config.curiosity_hidden_dim;

        // Create DQN agent
        let agent = if hyperparams.enable_regime_qnetwork {
            info!("Creating regime-conditional DQN with 3 heads (Trending, Ranging, Volatile)");
            info!("  - Regime detection: ADX (raw index 40) + CUSUM direction (raw index 41)");
            info!("  - Classification: Trending (ADX>0.25), Volatile (ADX≤0.25 & |CUSUM|>0.7), Ranging (otherwise)");
            let regime_agent = RegimeConditionalDQN::new_on_device(config, device.clone())
                .map_err(|e| anyhow::anyhow!("Failed to create regime-conditional DQN: {}", e))?;
            DQNAgentType::RegimeConditional(regime_agent)
        } else {
            info!("Creating standard DQN with single Q-network head");
            let standard_agent = DQN::new_on_device(config, device.clone())
                .map_err(|e| anyhow::anyhow!("Failed to create DQN agent: {}", e))?;
            DQNAgentType::Standard(standard_agent)
        };
        

        // Initialize portfolio tracker with $100k starting capital and 1 basis point spread
        // Bug #2 fix: Portfolio features were hardcoded as [0.0, 0.0, 0.0] at line 1528
        let portfolio_tracker = PortfolioTracker::new(
            hyperparams.initial_capital, // P2-A: Configurable capital
            0.0001,    // 1 basis point spread (0.01%)
            hyperparams.cash_reserve_percent, // Cash reserve requirement
        );

        // Initialize reward function with hyperparameter-driven configuration
        // Reward normalization and DSR now live in the GPU composite reward kernel.
        let reward_config = RewardConfig {
            pnl_weight: Decimal::ONE,
            risk_weight: Decimal::try_from(0.1).unwrap_or(Decimal::ZERO),
            cost_weight: Decimal::ONE, // Bug #2 fix: 100% transaction cost weight (was 0.05, 20x too low)
            movement_threshold: Decimal::try_from(hyperparams.movement_threshold)
                .unwrap_or(Decimal::ZERO),
            diversity_weight: Decimal::try_from(-0.1).unwrap_or(Decimal::ZERO),
            use_percentage_pnl: true,   // Bug #17: Use percentage returns for scale-invariance
            circuit_breaker_config: CircuitBreakerConfig::default(),
            triple_barrier_profit_bonus: Decimal::try_from(0.5).unwrap_or(Decimal::ZERO),
            triple_barrier_stop_penalty: Decimal::try_from(0.5).unwrap_or(Decimal::ZERO),
            sharpe_weight: Decimal::ZERO, // WAVE 26 P1.3: Disabled by default
            sharpe_window: 20,            // WAVE 26 P1.3: Standard 20-period window
            dsr_eta: hyperparams.dsr_eta,
            initial_capital: hyperparams.initial_capital as f64,
        };
        let reward_fn = RewardFunction::new_with_debug(reward_config, debug_logging)?;

        // WAVE 1.1: Initialize triple barrier engine (max 1000 active trackers)
        let triple_barrier = Arc::new(RwLock::new(TripleBarrierEngine::new(1000)));
        info!("Triple barrier engine initialized with 1000 max trackers");

        // WAVE 16S: Initialize Kelly optimizer if enabled
        let kelly_optimizer = if hyperparams.enable_kelly_sizing {
            use crate::risk::kelly_optimizer::{KellyCriterionOptimizer, KellyOptimizerConfig};
            let kelly_config = KellyOptimizerConfig {
                max_fraction: hyperparams.kelly_max_fraction,
                min_fraction: 0.01,
                lookback_period: 252,
                confidence_threshold: 0.6,
                volatility_adjustment: true,
                drawdown_protection: true,
            };
            let optimizer = KellyCriterionOptimizer::new(kelly_config)
                .map_err(|e| anyhow::anyhow!("Failed to create Kelly optimizer: {}", e))?;
            info!("Kelly optimizer enabled (fractional={}, max={})",
                  hyperparams.kelly_fractional, hyperparams.kelly_max_fraction);
            Some(Arc::new(optimizer))
        } else {
            None
        };

        // Wave 16 Portfolio Features: Initialize action masking, entropy regularization, and stress testing
        let enable_action_masking = hyperparams.enable_action_masking;
        let max_position = hyperparams.max_position_absolute; // BLOCKER #2: Use hyperopt-tunable position limit

        // Entropy regularization: SAC-style computed directly on Q-values in DQN::compute_loss_internal
        if hyperparams.enable_entropy_regularization {
            let coeff = hyperparams.entropy_coefficient.unwrap_or(0.01);
            info!("Entropy regularization enabled (coefficient={coeff:.4}, applied to Q-value softmax in loss)");
        }

        // Multi-asset portfolio tracking (disabled -- single-asset is the current production mode)
        //
        // When expanding to multi-asset trading:
        //   1. Add `enable_multi_asset: bool` to DQNHyperparams (default false).
        //   2. Initialize MultiAssetPortfolioTracker here when the flag is set.
        //   3. Wire portfolio state into the DQN observation: expand state_dim to
        //      include per-asset position, PnL, and correlation features so the
        //      agent can learn cross-asset hedging and allocation.
        let multi_asset_portfolio: Option<Arc<crate::dqn::multi_asset::MultiAssetPortfolioTracker>> = None;

        // Stress testing for robustness validation
        // Initialized as None here; call init_stress_tester() after construction
        // to resolve the circular dependency (DQNStressTester needs a DQNTrainer).
        let stress_tester: Option<Arc<crate::dqn::stress_testing::DQNStressTester>> = None;

        if enable_action_masking {
            info!(
                "Action masking enabled (max_position=±{:.1}, 30-50% filtering expected)",
                max_position
            );
        } else {
            info!("Action masking disabled (all 5 exposure levels available)");
        }

        // Wave 16 Core Risk Features: Initialize drawdown monitor, position limiter, circuit breaker
        // These are ALWAYS enabled by default for production safety
        
        // 1. Drawdown Monitor (15% max drawdown, alerts at 10%, 12.5%, 15%)
        let drawdown_monitor = {
            // DrawdownMonitor will be configured in first training step
            // Config will be applied via async configure_alerts() in train_epoch
            info!("Drawdown monitor enabled (thresholds: 10%, 12.5%, 15%)");
            Some(Arc::new(DrawdownMonitor::new()))
        };

        // 2. Position Limiter (3-tier limits: ±10.0 absolute, 1M notional, 10% concentration)
        let position_limiter = {
            let config = PositionLimiterConfig {
                enabled: true,
                cache_ttl: Duration::from_secs(60),
                rpc_check_threshold_percent: 0.8,
                max_position_per_symbol: 10.0,     // ±10.0 absolute position limit
                max_order_value: 1_000_000.0,      // $1M notional limit
                max_daily_loss: 0.10,              // 10% concentration limit
            };
            let limiter = HybridPositionLimiter::new(config);
            info!("Position limiter enabled (abs=±10.0, notional=$1M, concentration=10%)");
            Some(Arc::new(limiter))
        };

        // 3. Circuit Breaker (5 consecutive failures, 60s cooldown)
        let circuit_breaker = {
            let config = CircuitBreakerConfig {
                failure_threshold: 5,
                success_threshold: 3,
                timeout_duration: Duration::from_secs(60),
                half_open_max_calls: 2,
            };
            let breaker = CircuitBreaker::new(config);
            info!("Circuit breaker enabled (threshold=5 failures, cooldown=60s)");
            Some(Arc::new(breaker))
        };

        // WAVE 24: Capture patience before hyperparams is moved
        let early_stopping_patience = hyperparams.gradient_collapse_patience;

        // WAVE 26 P1: Initialize advanced DQN features
        // P1.3: Sharpe ratio reward component
        let sharpe_weight = hyperparams.sharpe_weight;
        let sharpe_window = hyperparams.sharpe_window;

        // P1.6: Adaptive dropout scheduler
        let dropout_scheduler = hyperparams.enable_dropout_scheduler.then(|| {
            use crate::dqn::network::DropoutScheduler;
            info!("Dropout scheduler enabled (initial={}, final={}, steps={})",
                  hyperparams.dropout_initial, hyperparams.dropout_final, hyperparams.dropout_anneal_steps);
            DropoutScheduler::new(
                hyperparams.dropout_initial,
                hyperparams.dropout_final,
                hyperparams.dropout_anneal_steps,
            )
        });

        // P1.7: Hindsight Experience Replay (HER)
        let her_buffer = (hyperparams.her_ratio > 0.0)
            .then(|| {
                use crate::dqn::hindsight_replay::{HindsightReplayBuffer, HindsightReplayConfig, HindsightStrategy};
                use crate::dqn::prioritized_replay::PrioritizedReplayConfig;
                let her_strategy = match hyperparams.her_strategy.as_str() {
                    "final" => HindsightStrategy::Final,
                    _ => HindsightStrategy::Future, // Default to Future
                };
                let config = HindsightReplayConfig {
                    base_config: PrioritizedReplayConfig {
                        capacity: hyperparams.buffer_size,
                        ..Default::default()
                    },
                    her_ratio: hyperparams.her_ratio,
                    her_strategy,
                    goal_dim: 1, // Single goal dimension for trading (target return)
                    k_future: 4, // Sample 4 future goals for Future strategy
                    batch_size: hyperparams.batch_size,
                };
                info!("HER buffer enabled (ratio={}, strategy={:?}, capacity={})",
                      hyperparams.her_ratio, her_strategy, hyperparams.buffer_size);
                HindsightReplayBuffer::new(config)
                    .map(Arc::new)
                    .map_err(|e| anyhow::anyhow!("Failed to create HER buffer: {}", e))
            })
            .transpose()?;

        // P1.9: Generalized Advantage Estimation (GAE)
        let gae_calculator = hyperparams.enable_gae.then(|| {
            use crate::dqn::gae::GAECalculator;
            info!("GAE calculator enabled (lambda={}, gamma={})",
                  hyperparams.gae_lambda, hyperparams.gamma);
            GAECalculator::new(hyperparams.gae_lambda, hyperparams.gamma)
        });

        // P1.11: Noisy network sigma scheduling
        let noisy_sigma_scheduler = hyperparams.enable_noisy_sigma_scheduler.then(|| {
            use crate::dqn::noisy_sigma_scheduler::NoisySigmaScheduler;
            info!("Noisy sigma scheduler enabled (initial={}, final={}, steps={})",
                  hyperparams.noisy_sigma_initial, hyperparams.noisy_sigma_final, hyperparams.noisy_sigma_anneal_steps);
            NoisySigmaScheduler::new(
                hyperparams.noisy_sigma_initial,
                hyperparams.noisy_sigma_final,
                hyperparams.noisy_sigma_anneal_steps,
            )
        });

        // WAVE 26 P1.8: Initialize curiosity module if curiosity_weight > 0
        // Must be created BEFORE hyperparams and device are moved
        let curiosity_module = if hyperparams.curiosity_weight > 0.0 {
            if let Some(ref s) = cuda_stream {
                Some(CuriosityModule::new(
                    std::sync::Arc::clone(s),
                    0.001,  // Forward model learning rate
                    2.0,    // Max curiosity reward (clip to prevent noise exploitation)
                    curiosity_market_dim,
                    curiosity_hidden_dim,
                    3,      // Action categories (Short/Flat/Long)
                ).map_err(|e| anyhow::anyhow!("Failed to create curiosity module: {}", e))?)
            } else {
                None
            }
        } else {
            None
        };

        // WAVE 26 P0.6: Initialize learning rate scheduler with warmup
        // Must be created BEFORE hyperparams is moved
        let lr_scheduler = {
            use super::super::lr_scheduler::LRScheduler;
            LRScheduler::new(
                hyperparams.learning_rate,
                hyperparams.warmup_steps,
                hyperparams.lr_decay_type,
            )
        };

        // Capture values before hyperparams is moved into Self
        let initial_batch_size = hyperparams.batch_size;
        let base_tau = hyperparams.tau;

        // GPU pipeline: pre-allocate staging buffers on CUDA devices
        let buffer_pool = matches!(device, MlDevice::Cuda { .. }).then(|| {
            info!("GpuBufferPool: pre-allocated staging buffers (100k bars, 42 features, 4 targets)");
            crate::cuda_pipeline::GpuBufferPool::new(100_000, 42, 4)
        });

        // GPU pipeline: double-buffered loader for zero-downtime fold transitions
        let double_buffer = cuda_stream.as_ref().map(|s| {
            crate::cuda_pipeline::double_buffer::DoubleBufferedLoader::new(std::sync::Arc::clone(s))
        });

        // Multi-GPU: auto-detect if multiple CUDA devices are available
        let multi_gpu = crate::cuda_pipeline::multi_gpu::MultiGpuConfig::detect()
            .unwrap_or(None);
        if let Some(ref mg) = multi_gpu {
            info!("Multi-GPU: {} devices detected, data parallelism enabled", mg.world_size);
        }

        Ok(Self {
            agent: Arc::new(RwLock::new(agent)),
            hyperparams,
            device,
            cuda_stream,
            metrics: Arc::new(RwLock::new(TrainingMetrics::new())),
            loss_history: Vec::new(),
            q_value_history: Vec::new(),
            best_val_loss: f64::INFINITY, // Start with worst possible loss
            val_data: Vec::new(),
            val_loss_history: Vec::new(),
            sharpe_history: Vec::new(),
            best_sharpe: f64::NEG_INFINITY, // C4: Start with worst possible Sharpe
            best_epoch: 0,
            gradient_logging_step: 0,
            collapse_warmup_buffer_size: original_buffer_size,
            portfolio_tracker,
            feature_stats: None, // WAVE 3 FIX #2: Start with None, collect stats in epochs 0-10
            recent_actions: VecDeque::with_capacity(100),
            reward_fn,

            // WAVE 16S: Adaptive risk management
            kelly_optimizer,
            trade_history: VecDeque::with_capacity(500),
            volatility_returns: VecDeque::with_capacity(20), // Use default instead of moved hyperparams
            trade_stats_history: Vec::new(),

            // Wave 16 Portfolio Features
            enable_action_masking,
            max_position,
            current_epoch: 0,
            multi_asset_portfolio,
            stress_tester,

            // Wave 16 Core Risk Features
            drawdown_monitor,
            position_limiter,
            circuit_breaker,

            // WAVE 3.10: Microstructure feature calculators
            micro_high_low_spread: HighLowSpread::default(),
            micro_vw_spread: VolumeWeightedSpread::default(),
            micro_tick_count: TickCount::default(),
            micro_inter_arrival: InterArrivalTime::default(),
            micro_buy_sell_imbalance: BuySellImbalance::default(),
            micro_kyle_lambda: KyleLambda::default(),
            micro_price_impact: PriceImpact::default(),
            micro_variance_ratio: VarianceRatio::default(),
            last_timestamp_ns: 0,
            last_close: 0.0,

            // WAVE 1.1: Triple barrier integration
            triple_barrier,
            active_position_tracker: None,
            previous_simulated_position: 0.0,  // WAVE P3: Start with flat position

            // WAVE 1.2: Safety Infrastructure Integration (8 Systems)
            safety_loss_history: VecDeque::with_capacity(30),
            safety_loss_plateau_counter: 0,
            safety_action_counts: std::collections::HashMap::new(),
            safety_memory_manager: Arc::new(RwLock::new(
                crate::safety::memory_manager::SafeMemoryManager::new(
                    &crate::safety::MLSafetyConfig::default()
                )
            )),
            safety_level: crate::safety::SafetyLevel::Normal, // Default to Normal mode
            safety_step_counter: 0,

            feature_cache_dir: None,

            prev_epoch_q_mean: 0.0,
            adaptive_tau: base_tau,

            // WAVE 24 (Agent 17): Initialize patience-based early stopping
            // Use gradient_collapse_patience from hyperparams for consistency
            // Set min_delta to 0.001 (0.1% improvement threshold)
            early_stopping: super::super::early_stopping::EarlyStopping::new(
                early_stopping_patience, // Reuse patience parameter (default: 5)
                0.001, // 0.1% minimum improvement
            ),

            // WAVE 26 P0.6: Use pre-initialized learning rate scheduler
            lr_scheduler,

            // WAVE 26 P1.8: Use pre-initialized curiosity module
            curiosity_module,

            // WAVE 26 P1: Advanced DQN Features
            // P1.3: Sharpe ratio reward
            returns_history: VecDeque::with_capacity(sharpe_window),
            sharpe_weight,

            // P1.6: Adaptive dropout
            dropout_scheduler,

            // P1.7: Hindsight Experience Replay
            her_buffer,

            // P1.9: Generalized Advantage Estimation
            gae_calculator,

            // P1.11: Noisy sigma scheduler
            noisy_sigma_scheduler,

            // WAVE 30: Structured logging integration
            logging_config: LoggingConfig::default(),
            metrics_aggregator: MetricsAggregator::new(),

            // OOM recovery: track effective batch size
            current_batch_size: initial_batch_size,

            // Q-value estimation: periodic (every 50 steps) instead of every step
            cached_avg_q: 0.0,
            q_estimation_counter: 0,

            // GPU pipeline: pre-uploaded training data (initialized lazily at first epoch)
            gpu_data: None,
            targets_raw_cuda: None,
            features_raw_cuda: None,
            gpu_experience_collector: None,
            gpu_action_selector: None,
            training_guard: None,
            gpu_monitoring: None,
            reduction_kernels: None,
            cached_n_episodes: None,

            // GPU pipeline: staging buffer pool (auto-initialized on CUDA devices)
            buffer_pool,

            // GPU pipeline: double-buffered loader for fold transitions
            double_buffer,

            // GPU walk-forward: initialized lazily when enable_gpu_walk_forward=true
            gpu_walk_forward: None,

            // Multi-GPU: auto-detected data parallelism
            multi_gpu,

            // OFI features: populated during data loading when MBP-10 data is available
            ofi_features: None,
            ofi_val_offset: 0,
            val_features_gpu: None,
            val_closes_gpu: None,
            val_ofi_gpu: None,

            // Phase C: Fill simulation and smart order routing
            fill_simulator: FillSimulator::default(),
            vol_ema: 0.01,     // Initial volatility estimate (1% daily)
            median_vol: 0.01,  // Slowly adapting baseline

            // Fused CUDA training: lazy-initialized on first training step
            fused_ctx: None,
        })
    }

}
