//! 自动增量训练调度器 — Data-as-Training (Phase-70)
//!
//! 提供两种训练触发模式：
//!
//! - **实时模式 (on_insert)**: 新数据插入时立即 tokenize 放入缓冲区，
//!   缓冲区满 `batch_size` 后自动微调一步。
//! - **定期模式 (periodic)**: 按时间间隔或数据量阈值批量训练多步。
//!
//! 此外支持 QueryLog `feedback_score` 驱动的强化训练，
//! 自动 checkpoint、版本回滚、early stopping。
//!
//! ## 架构
//!
//! ```text
//! ┌─────────────┐     ┌───────────────────┐
//! │ INSERT data  │────►│  TrainingBuffer    │
//! │ (Trigger)    │     │  tokenize + append │
//! └─────────────┘     └────────┬──────────┘
//!                              │ buffer.len() >= batch_size?
//!                              ▼
//!                     ┌───────────────────┐
//!                     │  AutoTrainer       │
//!                     │  train_step()      │──► checkpoint (periodic)
//!                     │  early_stopping()  │
//!                     └───────────────────┘
//!                              │
//!                     ┌───────────────────┐
//!                     │  QueryLog          │
//!                     │  feedback_score    │──► 高评分 → 正样本 boost
//!                     └───────────────────┘
//! ```

use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};

use parking_lot::{Mutex, RwLock};
use serde::{Deserialize, Serialize};

use super::model_registry::{ModelRegistry, RegistryError, RegistryResult};
use super::{GptConfig, GptTrainer, MicroGpt, TokenizerManager, TrainConfig, TrainStepResult};
use crate::device::DeviceSelector;

// ============================================================================
// Configuration
// ============================================================================

/// 自动训练调度模式
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TrainScheduleMode {
    /// 实时模式：缓冲区满即微调
    OnInsert,
    /// 定期模式：按时间/数据量阈值批量训练
    Periodic,
    /// 双模式：on_insert + periodic
    Hybrid,
    /// 手动模式：仅通过 SQL 触发
    Manual,
}

impl Default for TrainScheduleMode {
    fn default() -> Self {
        Self::Hybrid
    }
}

/// 自动训练配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoTrainConfig {
    /// 调度模式
    pub mode: TrainScheduleMode,
    /// 模型名称
    pub model_name: String,
    /// 训练超参数
    pub train_config: TrainConfig,
    /// on_insert 缓冲区大小（满则触发一步训练）
    pub buffer_batch_size: usize,
    /// periodic 模式训练步数
    pub periodic_steps: usize,
    /// periodic 模式时间间隔（秒）
    pub periodic_interval_secs: u64,
    /// periodic 模式数据量阈值（新数据条数）
    pub periodic_data_threshold: usize,
    /// 最大 checkpoint 保留数
    pub max_checkpoints: usize,
    /// checkpoint 间隔步数
    pub checkpoint_every_steps: usize,
    /// Early stopping: 如果连续 N 步 loss 无改善则停止
    pub early_stopping_patience: usize,
    /// Early stopping: loss 最小改善幅度
    pub early_stopping_min_delta: f64,
    /// QueryLog feedback: 最小评分阈值（低于此值忽略）
    pub feedback_min_score: f64,
    /// QueryLog feedback: 高评分数据重复采样倍数
    pub feedback_boost_factor: usize,
}

impl Default for AutoTrainConfig {
    fn default() -> Self {
        Self {
            mode: TrainScheduleMode::Hybrid,
            model_name: "default".to_string(),
            train_config: TrainConfig::default(),
            buffer_batch_size: 32,
            periodic_steps: 50,
            periodic_interval_secs: 300, // 5 minutes
            periodic_data_threshold: 100,
            max_checkpoints: 5,
            checkpoint_every_steps: 100,
            early_stopping_patience: 10,
            early_stopping_min_delta: 1e-4,
            feedback_min_score: 0.5,
            feedback_boost_factor: 3,
        }
    }
}

// ============================================================================
// T70.5 — Training Metrics
// ============================================================================

/// 训练指标
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingMetrics {
    /// 总训练步数
    pub total_steps: usize,
    /// 总处理 token 数
    pub total_tokens: usize,
    /// 总训练时间（秒）
    pub total_train_secs: f64,
    /// 当前 loss
    pub current_loss: f64,
    /// 历史最佳 loss
    pub best_loss: f64,
    /// 缓冲区当前 token 数
    pub buffer_tokens: usize,
    /// 累计接收的文本条数
    pub total_inserts: usize,
    /// 累计 checkpoint 数
    pub total_checkpoints: usize,
    /// Early stopping 连续无改善步数
    pub no_improve_steps: usize,
    /// 是否已被 early stopping 停止
    pub early_stopped: bool,
    /// 平均 token/秒
    pub avg_tokens_per_sec: f64,
    /// 累计 feedback 样本数
    pub feedback_samples: usize,
}

impl Default for TrainingMetrics {
    fn default() -> Self {
        Self {
            total_steps: 0,
            total_tokens: 0,
            total_train_secs: 0.0,
            current_loss: f64::MAX,
            best_loss: f64::MAX,
            buffer_tokens: 0,
            total_inserts: 0,
            total_checkpoints: 0,
            no_improve_steps: 0,
            early_stopped: false,
            avg_tokens_per_sec: 0.0,
            feedback_samples: 0,
        }
    }
}

// ============================================================================
// T70.1 — Training Buffer
// ============================================================================

/// 训练数据缓冲区
///
/// 接收新数据（文本），tokenize 后存储为 token 序列。
/// 当 token 数量达到阈值时，可取出一个训练批次。
struct TrainingBuffer {
    /// token 缓冲区
    tokens: Vec<u32>,
    /// tokenizer
    tokenizer: TokenizerManager,
    /// 块大小（模型 block_size）
    block_size: usize,
}

impl TrainingBuffer {
    fn new(tokenizer: TokenizerManager, block_size: usize) -> Self {
        Self {
            tokens: Vec::with_capacity(block_size * 32),
            tokenizer,
            block_size,
        }
    }

    /// 将文本 tokenize 后追加到缓冲区
    fn push_text(&mut self, text: &str) -> Result<usize, String> {
        let new_tokens = self.tokenizer.encode(text)?;
        let count = new_tokens.len();
        self.tokens.extend(new_tokens);
        Ok(count)
    }

    /// 将已 tokenize 的 token 直接追加
    #[allow(dead_code)]
    fn push_tokens(&mut self, tokens: &[u32]) {
        self.tokens.extend_from_slice(tokens);
    }

    /// 当前缓冲区 token 数
    fn len(&self) -> usize {
        self.tokens.len()
    }

    /// 缓冲区是否有足够数据进行训练
    fn has_batch(&self, batch_size: usize) -> bool {
        self.tokens.len() >= (self.block_size + 1) * batch_size
    }

    /// 获取所有 token（不清除）
    fn all_tokens(&self) -> &[u32] {
        &self.tokens
    }

    /// 清空缓冲区
    #[allow(dead_code)]
    fn clear(&mut self) {
        self.tokens.clear();
    }

    /// 移除已消费的前 N 个 token
    fn drain_front(&mut self, n: usize) {
        if n >= self.tokens.len() {
            self.tokens.clear();
        } else {
            self.tokens.drain(0..n);
        }
    }
}

// ============================================================================
// T70.4 — Checkpoint Manager
// ============================================================================

/// Checkpoint 回滚策略
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointPolicy {
    /// 最大保留数
    pub max_keep: usize,
    /// 是否保留最佳 loss 的 checkpoint
    pub keep_best: bool,
}

impl Default for CheckpointPolicy {
    fn default() -> Self {
        Self {
            max_keep: 5,
            keep_best: true,
        }
    }
}

// ============================================================================
// T70.5 — Early Stopping
// ============================================================================

/// Early stopping 状态
struct EarlyStoppingState {
    patience: usize,
    min_delta: f64,
    best_loss: f64,
    counter: usize,
    stopped: bool,
}

impl EarlyStoppingState {
    fn new(patience: usize, min_delta: f64) -> Self {
        Self {
            patience,
            min_delta,
            best_loss: f64::MAX,
            counter: 0,
            stopped: false,
        }
    }

    /// 更新 early stopping 状态，返回是否应该停止
    fn update(&mut self, loss: f64) -> bool {
        if self.stopped {
            return true;
        }

        if loss < self.best_loss - self.min_delta {
            self.best_loss = loss;
            self.counter = 0;
        } else {
            self.counter += 1;
        }

        if self.counter >= self.patience {
            self.stopped = true;
            eprintln!(
                "[AutoTrainer] early stopping triggered: no improvement for {} steps (best_loss={:.4})",
                self.patience, self.best_loss
            );
        }

        self.stopped
    }

    fn reset(&mut self) {
        self.counter = 0;
        self.stopped = false;
        // Keep best_loss
    }
}

// ============================================================================
// T70.1-T70.5 — AutoTrainer (核心)
// ============================================================================

/// 自动增量训练器
///
/// 管理训练缓冲区、调度策略、checkpoint、early stopping。
/// 线程安全：所有可变状态通过锁保护。
pub struct AutoTrainer {
    /// 配置
    config: AutoTrainConfig,
    /// 训练数据缓冲区
    buffer: Mutex<TrainingBuffer>,
    /// 训练指标
    metrics: RwLock<TrainingMetrics>,
    /// Early stopping 状态
    early_stopping: Mutex<EarlyStoppingState>,
    /// 上次 periodic 训练时间
    last_periodic_train: Mutex<Instant>,
    /// 上次 periodic 以来的新数据条数
    new_data_since_periodic: AtomicUsize,
    /// 是否正在训练中（防止并发训练）
    is_training: AtomicBool,
    /// 全局步数
    global_step: AtomicU64,
    /// 是否已暂停
    paused: AtomicBool,
}

impl AutoTrainer {
    /// 创建自动训练器
    pub fn new(config: AutoTrainConfig, block_size: usize) -> Self {
        let tokenizer = TokenizerManager::char_level();
        let buffer = TrainingBuffer::new(tokenizer, block_size);
        let early_stopping = EarlyStoppingState::new(
            config.early_stopping_patience,
            config.early_stopping_min_delta,
        );

        Self {
            config,
            buffer: Mutex::new(buffer),
            metrics: RwLock::new(TrainingMetrics::default()),
            early_stopping: Mutex::new(early_stopping),
            last_periodic_train: Mutex::new(Instant::now()),
            new_data_since_periodic: AtomicUsize::new(0),
            is_training: AtomicBool::new(false),
            global_step: AtomicU64::new(0),
            paused: AtomicBool::new(false),
        }
    }

    /// 获取当前配置
    pub fn config(&self) -> &AutoTrainConfig {
        &self.config
    }

    /// 获取训练指标快照
    pub fn metrics(&self) -> TrainingMetrics {
        self.metrics.read().clone()
    }

    /// 暂停自动训练
    pub fn pause(&self) {
        self.paused.store(true, Ordering::Release);
        eprintln!("[AutoTrainer] paused");
    }

    /// 恢复自动训练
    pub fn resume(&self) {
        self.paused.store(false, Ordering::Release);
        self.early_stopping.lock().reset();
        eprintln!("[AutoTrainer] resumed");
    }

    /// 是否暂停
    pub fn is_paused(&self) -> bool {
        self.paused.load(Ordering::Acquire)
    }

    // ========================================================================
    // T70.1 — on_insert 钩子
    // ========================================================================

    /// 接收新数据（on_insert 钩子入口）
    ///
    /// 将文本 tokenize 后放入缓冲区。如果缓冲区满且为实时/混合模式，
    /// 自动触发一步训练。
    ///
    /// # Returns
    /// `Ok(Some(result))` 如果触发了训练步，`Ok(None)` 如果只是缓冲
    pub fn on_insert(
        &self,
        text: &str,
        registry: &ModelRegistry,
    ) -> RegistryResult<Option<TrainStepResult>> {
        if self.is_paused() || self.early_stopping.lock().stopped {
            return Ok(None);
        }

        // Tokenize and buffer
        let token_count = {
            let mut buf = self.buffer.lock();
            buf.push_text(text)
                .map_err(|e| RegistryError::Training(e))?
        };

        // Update metrics
        {
            let mut m = self.metrics.write();
            m.total_inserts += 1;
            m.buffer_tokens += token_count;
        }
        self.new_data_since_periodic.fetch_add(1, Ordering::Relaxed);

        // Check if should trigger immediate training
        let should_train = matches!(
            self.config.mode,
            TrainScheduleMode::OnInsert | TrainScheduleMode::Hybrid
        );

        if should_train {
            let batch_ready = self.buffer.lock().has_batch(self.config.buffer_batch_size);
            if batch_ready {
                return self.train_one_step(registry).map(Some);
            }
        }

        Ok(None)
    }

    // ========================================================================
    // T70.2 — Periodic check
    // ========================================================================

    /// 检查是否应触发定期训练
    ///
    /// 适合在定时器或后台任务中调用。
    pub fn check_periodic(&self, registry: &ModelRegistry) -> RegistryResult<Option<Vec<TrainStepResult>>> {
        if self.is_paused() || self.early_stopping.lock().stopped {
            return Ok(None);
        }

        if !matches!(
            self.config.mode,
            TrainScheduleMode::Periodic | TrainScheduleMode::Hybrid
        ) {
            return Ok(None);
        }

        let should_train = {
            let elapsed = self.last_periodic_train.lock().elapsed();
            let new_data = self.new_data_since_periodic.load(Ordering::Relaxed);

            elapsed >= Duration::from_secs(self.config.periodic_interval_secs)
                || new_data >= self.config.periodic_data_threshold
        };

        if !should_train {
            return Ok(None);
        }

        // Reset counters
        *self.last_periodic_train.lock() = Instant::now();
        self.new_data_since_periodic.store(0, Ordering::Relaxed);

        // Run multiple training steps
        let results = self.train_n_steps(registry, self.config.periodic_steps)?;
        Ok(Some(results))
    }

    // ========================================================================
    // T70.3 — Feedback-driven training
    // ========================================================================

    /// 接收 QueryLog 中的 feedback 数据用于强化训练
    ///
    /// 高评分的查询结果文本会被重复加入训练缓冲区（boost），
    /// 使模型偏向生成高质量的回复。
    pub fn on_feedback(
        &self,
        text: &str,
        score: f64,
    ) -> Result<usize, String> {
        if score < self.config.feedback_min_score {
            return Ok(0);
        }

        let repeat = if score >= 0.9 {
            self.config.feedback_boost_factor
        } else if score >= 0.7 {
            (self.config.feedback_boost_factor + 1) / 2
        } else {
            1
        };

        let mut total_tokens = 0;
        let mut buf = self.buffer.lock();
        for _ in 0..repeat {
            total_tokens += buf.push_text(text)?;
        }

        self.metrics.write().feedback_samples += repeat;

        Ok(total_tokens)
    }

    // ========================================================================
    // Core training logic
    // ========================================================================

    /// 执行一步训练
    fn train_one_step(
        &self,
        registry: &ModelRegistry,
    ) -> RegistryResult<TrainStepResult> {
        // Prevent concurrent training
        if self
            .is_training
            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
            .is_err()
        {
            return Err(RegistryError::Training("training already in progress".into()));
        }

        let result = self.do_train_step(registry);
        self.is_training.store(false, Ordering::Release);
        result
    }

    /// 执行 N 步训练
    fn train_n_steps(
        &self,
        registry: &ModelRegistry,
        n: usize,
    ) -> RegistryResult<Vec<TrainStepResult>> {
        if self
            .is_training
            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
            .is_err()
        {
            return Err(RegistryError::Training("training already in progress".into()));
        }

        let mut results = Vec::with_capacity(n);
        for _ in 0..n {
            // Check early stopping
            if self.early_stopping.lock().stopped {
                break;
            }

            // Check buffer has enough data
            if !self.buffer.lock().has_batch(1) {
                break;
            }

            match self.do_train_step(registry) {
                Ok(r) => results.push(r),
                Err(e) => {
                    eprintln!("[AutoTrainer] step failed: {}", e);
                    break;
                }
            }
        }

        self.is_training.store(false, Ordering::Release);
        Ok(results)
    }

    /// 内部：执行单步训练
    fn do_train_step(
        &self,
        registry: &ModelRegistry,
    ) -> RegistryResult<TrainStepResult> {
        let model_name = &self.config.model_name;
        let selector = DeviceSelector::auto();
        let (device, _is_gpu) = selector.candle_device_with_fallback("auto_train");

        // Evict cached model to get fresh instance
        registry.evict_model(model_name);
        let loaded = registry.load_model(model_name, None, &device)?;

        let block_size = loaded.model.config().block_size;
        let batch_size = 1; // Single batch for incremental training

        // Get tokens from buffer
        let tokens: Vec<u32> = {
            let buf = self.buffer.lock();
            buf.all_tokens().to_vec()
        };

        if tokens.len() < block_size + 1 {
            return Err(RegistryError::Training(format!(
                "Not enough tokens: {} < {}",
                tokens.len(),
                block_size + 1
            )));
        }

        // Create trainer
        let mut trainer = GptTrainer::new(&loaded.model, self.config.train_config.clone())
            .map_err(|e| RegistryError::Candle(e.to_string()))?;

        let start = Instant::now();

        let (input_ids, target_ids) =
            GptTrainer::prepare_batch(&tokens, block_size, batch_size, &device)
                .map_err(|e| RegistryError::Candle(e.to_string()))?;

        let result = trainer
            .train_step(&loaded.model, &input_ids, &target_ids)
            .map_err(|e| RegistryError::Candle(e.to_string()))?;

        let elapsed = start.elapsed().as_secs_f64();
        let step = self.global_step.fetch_add(1, Ordering::Relaxed) as usize + 1;

        // Update early stopping
        let early_stopped = self.early_stopping.lock().update(result.loss);

        // Update metrics
        {
            let mut m = self.metrics.write();
            m.total_steps = step;
            m.total_tokens += block_size * batch_size;
            m.total_train_secs += elapsed;
            m.current_loss = result.loss;
            if result.loss < m.best_loss {
                m.best_loss = result.loss;
            }
            m.no_improve_steps = self.early_stopping.lock().counter;
            m.early_stopped = early_stopped;
            if m.total_train_secs > 0.0 {
                m.avg_tokens_per_sec = m.total_tokens as f64 / m.total_train_secs;
            }
        }

        // Periodic checkpoint
        if step % self.config.checkpoint_every_steps == 0 {
            let _ = registry.save_checkpoint(model_name, &loaded.model, result.loss, step);
            self.metrics.write().total_checkpoints += 1;
        }

        // Drain consumed tokens from buffer
        {
            let consumed = block_size * batch_size;
            let mut buf = self.buffer.lock();
            buf.drain_front(consumed);
            self.metrics.write().buffer_tokens = buf.len();
        }

        // Evict so next call gets updated weights
        registry.evict_model(model_name);

        Ok(result)
    }

    /// 强制保存 checkpoint
    pub fn force_checkpoint(
        &self,
        registry: &ModelRegistry,
    ) -> RegistryResult<()> {
        let model_name = &self.config.model_name;
        let selector = DeviceSelector::auto();
        let (device, _) = selector.candle_device_with_fallback("auto_ckpt");

        let loaded = registry.load_model(model_name, None, &device)?;
        let step = self.global_step.load(Ordering::Relaxed) as usize;
        let loss = self.metrics.read().current_loss;

        registry.save_checkpoint(model_name, &loaded.model, loss, step)?;
        self.metrics.write().total_checkpoints += 1;

        Ok(())
    }

    /// 回滚到指定版本
    pub fn rollback(
        &self,
        registry: &ModelRegistry,
        version: usize,
    ) -> RegistryResult<()> {
        let model_name = &self.config.model_name;
        let selector = DeviceSelector::auto();
        let (device, _) = selector.candle_device_with_fallback("auto_rollback");

        // Verify version exists by loading it
        let _loaded = registry.load_model(model_name, Some(version), &device)?;

        // Reset early stopping
        self.early_stopping.lock().reset();

        eprintln!(
            "[AutoTrainer] rolled back '{}' to version {}",
            model_name, version
        );

        // Evict cached instance
        registry.evict_model(model_name);

        Ok(())
    }

    /// 获取缓冲区当前 token 数
    pub fn buffer_len(&self) -> usize {
        self.buffer.lock().len()
    }
}

// ============================================================================
// GptTrainingTrigger — 集成到 triggers 系统
// ============================================================================

/// GPT 训练触发器 — 注册为 PostWrite trigger
///
/// 在数据写入后异步将文本喂入 AutoTrainer 的缓冲区。
pub struct GptTrainingTrigger {
    trainer: Arc<AutoTrainer>,
    registry: Arc<ModelRegistry>,
}

impl GptTrainingTrigger {
    pub fn new(trainer: Arc<AutoTrainer>, registry: Arc<ModelRegistry>) -> Self {
        Self { trainer, registry }
    }
}

impl crate::triggers::RustTrigger for GptTrainingTrigger {
    fn id(&self) -> &str {
        "gpt-auto-training"
    }

    fn name(&self) -> &str {
        "GPT Auto Training Trigger"
    }

    fn phase(&self) -> crate::triggers::TriggerPhase {
        crate::triggers::TriggerPhase::PostWrite
    }

    fn priority(&self) -> crate::triggers::TriggerPriority {
        crate::triggers::TriggerPriority::LOWEST // 低优先级，最后执行
    }

    fn execute(&self, ctx: &crate::triggers::TriggerContext) -> crate::triggers::TriggerResult {
        // 提取节点内容
        let content = &ctx.node.content;
        if content.is_empty() {
            return crate::triggers::TriggerResult::Continue;
        }

        // 异步喂入缓冲区（不阻塞写入路径）
        match self.trainer.on_insert(content, &self.registry) {
            Ok(Some(result)) => {
                eprintln!(
                    "[GptTrainingTrigger] auto-train step: loss={:.4}",
                    result.loss
                );
            }
            Ok(None) => { /* buffered, no training triggered */ }
            Err(e) => {
                eprintln!("[GptTrainingTrigger] error: {}", e);
            }
        }

        crate::triggers::TriggerResult::Continue
    }
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;

    fn setup() -> (ModelRegistry, AutoTrainConfig) {
        let dir = tempdir().unwrap();
        let registry = ModelRegistry::new(dir.into_path()).unwrap();
        let config = GptConfig::tiny();
        registry.create_model("test_auto", config).unwrap();

        let auto_config = AutoTrainConfig {
            model_name: "test_auto".to_string(),
            buffer_batch_size: 4,
            periodic_steps: 5,
            periodic_interval_secs: 1,
            periodic_data_threshold: 3,
            checkpoint_every_steps: 5,
            early_stopping_patience: 3,
            early_stopping_min_delta: 0.01,
            train_config: TrainConfig {
                learning_rate: 1e-3,
                ..Default::default()
            },
            ..Default::default()
        };

        (registry, auto_config)
    }

    #[test]
    fn test_buffer_push_and_drain() {
        let tokenizer = TokenizerManager::char_level();
        let mut buffer = TrainingBuffer::new(tokenizer, 32);

        let count = buffer.push_text("Hello World!").unwrap();
        assert!(count > 0);
        assert_eq!(buffer.len(), count);

        buffer.drain_front(5);
        assert_eq!(buffer.len(), count - 5);

        buffer.clear();
        assert_eq!(buffer.len(), 0);
    }

    #[test]
    fn test_buffer_has_batch() {
        let tokenizer = TokenizerManager::char_level();
        let mut buffer = TrainingBuffer::new(tokenizer, 8); // block_size=8

        // Need (8+1)*2 = 18 tokens for batch_size=2
        buffer.push_text("abcdefghij").unwrap(); // 10 tokens
        assert!(!buffer.has_batch(2));

        buffer.push_text("klmnopqrst").unwrap(); // +10 = 20 tokens
        assert!(buffer.has_batch(2));
    }

    #[test]
    fn test_early_stopping() {
        let mut es = EarlyStoppingState::new(3, 0.01);

        // Loss improving
        assert!(!es.update(5.0));
        assert!(!es.update(4.0));
        assert!(!es.update(3.0));

        // Loss not improving
        assert!(!es.update(3.005)); // within delta
        assert!(!es.update(3.01));
        assert!(es.update(3.02)); // 3rd no-improve → stop

        assert!(es.stopped);
    }

    #[test]
    fn test_early_stopping_reset() {
        let mut es = EarlyStoppingState::new(2, 0.01);
        es.update(5.0);
        es.update(5.5);
        es.update(5.6); // stopped

        assert!(es.stopped);
        es.reset();
        assert!(!es.stopped);
        assert_eq!(es.counter, 0);
        assert_eq!(es.best_loss, 5.0); // preserved
    }

    #[test]
    fn test_on_insert_buffering() {
        let (registry, auto_config) = setup();
        let block_size = registry.get_model("test_auto").unwrap().config.block_size;
        let trainer = AutoTrainer::new(auto_config, block_size);

        // Insert small text — should buffer only
        let result = trainer.on_insert("Hello", &registry).unwrap();
        assert!(result.is_none());
        assert!(trainer.buffer_len() > 0);

        let m = trainer.metrics();
        assert_eq!(m.total_inserts, 1);
        assert_eq!(m.total_steps, 0);
    }

    #[test]
    fn test_on_insert_triggers_training() {
        let (registry, auto_config) = setup();
        let block_size = registry.get_model("test_auto").unwrap().config.block_size;
        let trainer = AutoTrainer::new(auto_config, block_size);

        // Insert lots of text to fill buffer
        let large_text = "abcdefghijklmnopqrstuvwxyz0123456789".repeat(100);
        let result = trainer.on_insert(&large_text, &registry).unwrap();

        // Should have triggered training
        assert!(result.is_some());

        let m = trainer.metrics();
        assert_eq!(m.total_steps, 1);
        assert!(m.current_loss < f64::MAX);
    }

    #[test]
    fn test_periodic_training() {
        let (registry, mut auto_config) = setup();
        auto_config.mode = TrainScheduleMode::Periodic;
        auto_config.periodic_interval_secs = 0; // immediate
        auto_config.periodic_data_threshold = 1;
        auto_config.periodic_steps = 3;

        let block_size = registry.get_model("test_auto").unwrap().config.block_size;
        let trainer = AutoTrainer::new(auto_config, block_size);

        // Fill buffer
        let text = "abcdefghijklmnopqrstuvwxyz".repeat(100);
        let _ = trainer.on_insert(&text, &registry); // buffered only (Periodic mode)

        // Check periodic — should trigger
        let results = trainer.check_periodic(&registry).unwrap();
        assert!(results.is_some());

        let steps = results.unwrap();
        assert!(steps.len() <= 3);
        assert!(trainer.metrics().total_steps > 0);
    }

    #[test]
    fn test_feedback_boost() {
        let (registry, auto_config) = setup();
        let block_size = registry.get_model("test_auto").unwrap().config.block_size;
        let trainer = AutoTrainer::new(auto_config, block_size);

        // High score → boosted
        let tokens = trainer.on_feedback("great answer!", 0.95).unwrap();
        assert!(tokens > 0);
        assert_eq!(trainer.metrics().feedback_samples, 3); // boost_factor=3

        // Low score → ignored
        let tokens = trainer.on_feedback("bad answer", 0.3).unwrap();
        assert_eq!(tokens, 0);
    }

    #[test]
    fn test_pause_resume() {
        let (registry, auto_config) = setup();
        let block_size = registry.get_model("test_auto").unwrap().config.block_size;
        let trainer = AutoTrainer::new(auto_config, block_size);

        trainer.pause();
        assert!(trainer.is_paused());

        let text = "abcdefghijklmnopqrstuvwxyz".repeat(100);
        let result = trainer.on_insert(&text, &registry).unwrap();
        assert!(result.is_none()); // paused, no training

        trainer.resume();
        assert!(!trainer.is_paused());
    }

    #[test]
    fn test_early_stopping_integration() {
        let (registry, mut auto_config) = setup();
        auto_config.early_stopping_patience = 2;
        auto_config.early_stopping_min_delta = 100.0; // absurdly high → will always trigger

        let block_size = registry.get_model("test_auto").unwrap().config.block_size;
        let trainer = AutoTrainer::new(auto_config, block_size);

        let text = "abcdefghijklmnopqrstuvwxyz".repeat(200);
        // First insert triggers training
        let _ = trainer.on_insert(&text, &registry);
        // Second insert triggers another step
        let text2 = "0123456789".repeat(200);
        let _ = trainer.on_insert(&text2, &registry);
        // Third should be early-stopped (patience=2)
        let text3 = "zyxwvutsrq".repeat(200);
        let _ = trainer.on_insert(&text3, &registry);

        let m = trainer.metrics();
        // After patience=2 with impossible delta, early stopping should kick in
        assert!(m.no_improve_steps >= 1 || m.early_stopped);
    }

    #[test]
    fn test_metrics_tracking() {
        let (registry, auto_config) = setup();
        let block_size = registry.get_model("test_auto").unwrap().config.block_size;
        let trainer = AutoTrainer::new(auto_config, block_size);

        let text = "abcdefghijklmnopqrstuvwxyz".repeat(100);
        let _ = trainer.on_insert(&text, &registry);

        let m = trainer.metrics();
        assert!(m.total_inserts >= 1);
        assert!(m.buffer_tokens > 0 || m.total_tokens > 0);
    }

    #[test]
    fn test_gpt_training_trigger() {
        let dir = tempdir().unwrap();
        let registry = Arc::new(ModelRegistry::new(dir.into_path()).unwrap());
        registry.create_model("trigger_test", GptConfig::tiny()).unwrap();

        let block_size = registry.get_model("trigger_test").unwrap().config.block_size;
        let auto_config = AutoTrainConfig {
            model_name: "trigger_test".to_string(),
            ..Default::default()
        };
        let trainer = Arc::new(AutoTrainer::new(auto_config, block_size));

        let trigger = GptTrainingTrigger::new(trainer.clone(), registry.clone());

        // Simulate trigger execution
        use crate::triggers::{TriggerContext, TriggerPhase, RustTrigger};
        use crate::types::MemoryNode;

        let node = MemoryNode::new("This is test content for GPT training");
        let ctx = TriggerContext::new(TriggerPhase::PostWrite, node);

        let result = trigger.execute(&ctx);
        assert!(result.should_continue());
        assert!(trainer.buffer_len() > 0);
    }
}
