//! SiFu 训练模块 — 赫布学习 + 对比学习 + 权重晋升
//!
//! Phase-50: 使数据库能通过使用数据自动优化传导权重
//!
//! - **HebbianLearner**: 在线实时学习触发器（on-access 共激活更新）
//! - **ContrastiveFitter**: 离线批量 fit() 对比学习训练器（InfoNCE 损失）
//! - **ProjectorTrainer**: PCA/锚点投影矩阵自动训练
//! - **SiFuTrainer**: 统一训练管理器
//! - **PromotionScanner**: 权重晋升/降级扫描

use std::collections::HashMap;
use std::sync::Arc;

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

use crate::query::sifu::{EdgeWeightStore, EdgeWeight};
use crate::semantic::projector::{
    PcaProjector, AnchorProjector, SemanticProjection, save_projector,
};
use crate::types::SemanticAxes;

// ============================================================================
// T50.1 — HebbianLearner 在线学习
// ============================================================================

/// 赫布学习配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HebbianConfig {
    /// 学习率：每次共激活增加的信号强度
    pub learning_rate: f64,
    /// 遗忘因子：每次全局衰减扫描时的衰减比例 (0.0-1.0)
    pub forgetting_factor: f64,
    /// 最大信号强度上限
    pub max_signal_strength: f64,
    /// 最小信号强度下限（低于此值认为"遗忘"）
    pub min_signal_strength: f64,
}

impl Default for HebbianConfig {
    fn default() -> Self {
        Self {
            learning_rate: 0.1,
            forgetting_factor: 0.01,
            max_signal_strength: 10.0,
            min_signal_strength: 0.01,
        }
    }
}

/// 赫布学习器 — 在线实时学习
///
/// 核心原理：共同激活的节点之间连接权重增强（Hebb's Rule）
///
/// "Cells that fire together wire together"
pub struct HebbianLearner {
    /// 边权重存储（共享）
    edge_weight_store: Arc<RwLock<EdgeWeightStore>>,
    /// 配置
    config: HebbianConfig,
    /// 学习统计
    stats: RwLock<HebbianStats>,
}

/// 赫布学习统计
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HebbianStats {
    /// 总共激活次数
    pub total_co_activations: u64,
    /// 受影响的边数
    pub edges_updated: u64,
    /// 全局衰减扫描次数
    pub decay_scans: u64,
    /// 被遗忘（删除）的边数
    pub edges_forgotten: u64,
}

impl HebbianLearner {
    /// 创建新的赫布学习器
    pub fn new(
        edge_weight_store: Arc<RwLock<EdgeWeightStore>>,
        config: HebbianConfig,
    ) -> Self {
        Self {
            edge_weight_store,
            config,
            stats: RwLock::new(HebbianStats::default()),
        }
    }

    /// 记录共激活事件
    ///
    /// 当查询结果中同时出现多个节点时调用
    pub fn on_co_activation(&self, node_ids: &[String], relation_type: &str) {
        let mut store = self.edge_weight_store.write();
        let mut stats = self.stats.write();

        // 对结果中的每一对节点记录共激活
        for i in 0..node_ids.len() {
            for j in (i + 1)..node_ids.len() {
                store.record_co_activation(&node_ids[i], &node_ids[j], relation_type);

                // 增强信号强度
                if let Some(ew) = store.get_edge_weight_mut(&node_ids[i], &node_ids[j]) {
                    let new_strength = (ew.signal_strength + self.config.learning_rate)
                        .min(self.config.max_signal_strength);
                    ew.signal_strength = new_strength;
                }

                stats.total_co_activations += 1;
                stats.edges_updated += 1;
            }
        }
    }

    /// 记录查询结果的共激活（简化接口）
    ///
    /// 传入查询结果节点 ID 列表，自动生成共激活对
    pub fn on_query_result(&self, result_ids: &[String]) {
        if result_ids.len() < 2 {
            return;
        }
        self.on_co_activation(result_ids, "co_accessed");
    }

    /// 全局衰减扫描
    ///
    /// 对所有边权重进行衰减，模拟"遗忘"机制。
    /// 建议定期调用（如每 N 次查询或定时任务）。
    pub fn decay_scan(&self) -> DecayScanResult {
        let mut store = self.edge_weight_store.write();
        let mut stats = self.stats.write();
        let mut result = DecayScanResult::default();

        let all_weights = store.all_edge_weights_cloned();
        let keys: Vec<(String, String)> = all_weights
            .iter()
            .map(|ew| (ew.source_id.clone(), ew.target_id.clone()))
            .collect();

        for (source, target) in keys {
            if let Some(ew) = store.get_edge_weight_mut(&source, &target) {
                // 应用衰减
                ew.signal_strength *= 1.0 - self.config.forgetting_factor;
                result.edges_decayed += 1;

                // 检查是否低于遗忘阈值
                if ew.signal_strength < self.config.min_signal_strength {
                    result.edges_to_forget.push((source.clone(), target.clone()));
                }
            }
        }

        // 删除已遗忘的边
        for (source, target) in &result.edges_to_forget {
            store.remove_edge_weight(source, target);
            stats.edges_forgotten += 1;
        }

        stats.decay_scans += 1;
        result
    }

    /// 获取学习统计
    pub fn stats(&self) -> HebbianStats {
        self.stats.read().clone()
    }
}

/// 衰减扫描结果
#[derive(Debug, Clone, Default)]
pub struct DecayScanResult {
    /// 衰减的边数
    pub edges_decayed: usize,
    /// 被遗忘（删除）的边
    pub edges_to_forget: Vec<(String, String)>,
}

// ============================================================================
// T50.2 — ContrastiveFitter 对比学习训练器
// ============================================================================

/// 对比学习训练配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContrastiveConfig {
    /// 学习率
    pub learning_rate: f64,
    /// 训练轮数
    pub epochs: usize,
    /// InfoNCE 温度参数（越小越关注困难负例）
    pub temperature: f64,
    /// 负例采样比例
    pub negative_ratio: usize,
    /// L2 正则化系数
    pub l2_lambda: f64,
}

impl Default for ContrastiveConfig {
    fn default() -> Self {
        Self {
            learning_rate: 0.01,
            epochs: 50,
            temperature: 0.07,
            negative_ratio: 5,
            l2_lambda: 0.001,
        }
    }
}

/// 训练样本
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingSample {
    /// 源节点 ID
    pub source_id: String,
    /// 目标节点 ID
    pub target_id: String,
    /// 关系类型
    pub relation_type: String,
    /// 标签：正例=true, 负例=false
    pub is_positive: bool,
}

/// 训练结果
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TrainingResult {
    /// 初始损失
    pub initial_loss: f64,
    /// 最终损失
    pub final_loss: f64,
    /// 训练轮数
    pub epochs_completed: usize,
    /// 更新的边数
    pub edges_updated: usize,
    /// 每轮损失记录
    pub loss_history: Vec<f64>,
}

/// 对比学习训练器 — 离线批量 fit()
///
/// 使用 InfoNCE 对比损失优化边的 signal_strength：
/// - 正例对的边权重增大
/// - 负例对的边权重减小
pub struct ContrastiveFitter {
    edge_weight_store: Arc<RwLock<EdgeWeightStore>>,
    config: ContrastiveConfig,
}

impl ContrastiveFitter {
    /// 创建对比学习训练器
    pub fn new(
        edge_weight_store: Arc<RwLock<EdgeWeightStore>>,
        config: ContrastiveConfig,
    ) -> Self {
        Self { edge_weight_store, config }
    }

    /// 执行训练
    ///
    /// 使用正例/负例训练样本优化边权重
    pub fn fit(&self, samples: &[TrainingSample]) -> TrainingResult {
        if samples.is_empty() {
            return TrainingResult::default();
        }

        let mut result = TrainingResult::default();
        let positives: Vec<&TrainingSample> = samples.iter().filter(|s| s.is_positive).collect();
        let negatives: Vec<&TrainingSample> = samples.iter().filter(|s| !s.is_positive).collect();

        if positives.is_empty() {
            return result;
        }

        // 计算初始损失
        result.initial_loss = self.compute_loss(&positives, &negatives);
        result.loss_history.push(result.initial_loss);

        // 梯度下降训练
        for epoch in 0..self.config.epochs {
            let mut store = self.edge_weight_store.write();

            // 对每个正例对更新权重
            for pos in &positives {
                // 正例：增加 signal_strength
                let key = (pos.source_id.clone(), pos.target_id.clone());
                let ew = store.edge_weights_mut().entry(key).or_insert_with(|| {
                    EdgeWeight::new(&pos.source_id, &pos.target_id, &pos.relation_type)
                });

                let grad = self.positive_gradient(ew.signal_strength);
                ew.signal_strength += self.config.learning_rate * grad;
                ew.signal_strength = ew.signal_strength.max(0.01).min(10.0);
            }

            // 对每个负例对更新权重
            for neg in &negatives {
                let key = (neg.source_id.clone(), neg.target_id.clone());
                if let Some(ew) = store.edge_weights_mut().get_mut(&key) {
                    let grad = self.negative_gradient(ew.signal_strength);
                    ew.signal_strength += self.config.learning_rate * grad;
                    ew.signal_strength = ew.signal_strength.max(0.01).min(10.0);
                }
            }

            drop(store);

            // 记录损失
            let loss = self.compute_loss(&positives, &negatives);
            result.loss_history.push(loss);
            result.epochs_completed = epoch + 1;

            // 早停：如果损失不再下降
            if epoch > 5 && result.loss_history.len() >= 3 {
                let recent = &result.loss_history[result.loss_history.len() - 3..];
                if (recent[0] - recent[2]).abs() < 1e-6 {
                    break;
                }
            }
        }

        result.final_loss = *result.loss_history.last().unwrap_or(&0.0);
        result.edges_updated = positives.len() + negatives.len();
        result
    }

    /// 从查询日志自动生成训练样本
    ///
    /// 正例：同一查询结果中共现的节点对
    /// 负例：随机采样的非共现节点对
    pub fn generate_samples_from_log(
        &self,
        query_log: &[QueryLogEntry],
        all_node_ids: &[String],
    ) -> Vec<TrainingSample> {
        let mut samples = Vec::new();
        let mut rng_state: u64 = 42;

        for entry in query_log {
            let ids = &entry.result_ids;

            // 正例：同一查询中共现的节点对
            for i in 0..ids.len() {
                for j in (i + 1)..ids.len() {
                    samples.push(TrainingSample {
                        source_id: ids[i].clone(),
                        target_id: ids[j].clone(),
                        relation_type: "co_accessed".to_string(),
                        is_positive: true,
                    });
                }
            }

            // 负例：随机采样
            for id in ids {
                for _ in 0..self.config.negative_ratio.min(all_node_ids.len()) {
                    rng_state ^= rng_state << 13;
                    rng_state ^= rng_state >> 7;
                    rng_state ^= rng_state << 17;
                    let idx = (rng_state as usize) % all_node_ids.len();
                    let neg_id = &all_node_ids[idx];

                    if !ids.contains(neg_id) {
                        samples.push(TrainingSample {
                            source_id: id.clone(),
                            target_id: neg_id.clone(),
                            relation_type: "co_accessed".to_string(),
                            is_positive: false,
                        });
                    }
                }
            }
        }

        samples
    }

    /// 计算 InfoNCE 风格损失
    fn compute_loss(&self, positives: &[&TrainingSample], negatives: &[&TrainingSample]) -> f64 {
        let store = self.edge_weight_store.read();
        let mut total_loss = 0.0;

        for pos in positives {
            let pos_score = store
                .get_edge_weight(&pos.source_id, &pos.target_id)
                .map(|ew| ew.signal_strength)
                .unwrap_or(1.0);

            let pos_exp = (pos_score / self.config.temperature).exp();

            let neg_sum: f64 = negatives.iter()
                .take(self.config.negative_ratio)
                .map(|neg| {
                    let neg_score = store
                        .get_edge_weight(&neg.source_id, &neg.target_id)
                        .map(|ew| ew.signal_strength)
                        .unwrap_or(1.0);
                    (neg_score / self.config.temperature).exp()
                })
                .sum();

            let denom = pos_exp + neg_sum;
            if denom > 0.0 {
                total_loss += -(pos_exp / denom).ln();
            }
        }

        // L2 正则化
        let l2_penalty: f64 = store.all_edge_weights().iter()
            .map(|ew| ew.signal_strength * ew.signal_strength)
            .sum::<f64>() * self.config.l2_lambda;

        total_loss / positives.len().max(1) as f64 + l2_penalty
    }

    /// 正例梯度（增大方向）
    fn positive_gradient(&self, signal_strength: f64) -> f64 {
        // sigmoid-like: 远离最优值时梯度大
        let x = signal_strength.min(10.0).max(0.01);
        1.0 / (1.0 + x) // 信号弱时梯度大，强时梯度小
    }

    /// 负例梯度（减小方向）
    fn negative_gradient(&self, signal_strength: f64) -> f64 {
        let x = signal_strength.min(10.0).max(0.01);
        -x / (1.0 + x) // 信号强时减小幅度大
    }
}

// ============================================================================
// T50.3 — SemanticProjector 自动投影训练
// ============================================================================

/// 投影训练配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectorTrainConfig {
    /// PCA 最大迭代次数
    pub pca_max_iterations: usize,
    /// 锚点训练学习率
    pub anchor_learning_rate: f32,
    /// 锚点训练轮数
    pub anchor_epochs: usize,
}

impl Default for ProjectorTrainConfig {
    fn default() -> Self {
        Self {
            pca_max_iterations: 200,
            anchor_learning_rate: 0.001,
            anchor_epochs: 500,
        }
    }
}

/// 投影器训练器
pub struct ProjectorTrainer;

impl ProjectorTrainer {
    /// 从数据集训练 PCA 投影器
    ///
    /// 收集所有 embedding 数据，使用幂迭代法提取前 3 个主成分
    pub fn fit_pca(
        embeddings: &[Vec<f32>],
        input_dim: usize,
        config: &ProjectorTrainConfig,
    ) -> PcaProjector {
        let refs: Vec<&[f32]> = embeddings.iter().map(|e| e.as_slice()).collect();
        PcaProjector::fit(&refs, input_dim, config.pca_max_iterations)
    }

    /// 从锚点数据训练投影器
    ///
    /// 用户标注 (embedding, 语义轴值) 对，通过最小二乘法拟合
    pub fn fit_anchors(
        anchors: &[(Vec<f32>, SemanticAxes)],
        input_dim: usize,
        config: &ProjectorTrainConfig,
    ) -> AnchorProjector {
        AnchorProjector::fit(anchors, input_dim, config.anchor_learning_rate, config.anchor_epochs)
    }

    /// 保存投影器到文件
    pub fn save(projector: &dyn SemanticProjection, path: &std::path::Path) -> std::io::Result<()> {
        save_projector(projector, path)
    }
}

// ============================================================================
// T50.4 — QueryLog + SiFuTrainer 统一训练管理器
// ============================================================================

/// 查询日志条目
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryLogEntry {
    /// 查询时间戳 (ms)
    pub timestamp: u64,
    /// 查询类型
    pub query_type: String,
    /// 查询中使用的 embedding（可选）
    pub embedding: Option<Vec<f32>>,
    /// 结果节点 ID 列表
    pub result_ids: Vec<String>,
    /// 用户反馈评分（可选，用于监督学习）
    pub feedback_score: Option<f64>,
}

impl QueryLogEntry {
    /// 创建新的查询日志条目
    pub fn new(query_type: impl Into<String>, result_ids: Vec<String>) -> Self {
        Self {
            timestamp: chrono_now_ms(),
            query_type: query_type.into(),
            embedding: None,
            result_ids,
            feedback_score: None,
        }
    }

    /// 设置 embedding
    pub fn with_embedding(mut self, embedding: Vec<f32>) -> Self {
        self.embedding = Some(embedding);
        self
    }

    /// 设置反馈评分
    pub fn with_feedback(mut self, score: f64) -> Self {
        self.feedback_score = Some(score);
        self
    }
}

/// 查询日志存储
pub struct QueryLog {
    entries: RwLock<Vec<QueryLogEntry>>,
    max_entries: usize,
}

impl QueryLog {
    /// 创建查询日志
    pub fn new(max_entries: usize) -> Self {
        Self {
            entries: RwLock::new(Vec::new()),
            max_entries,
        }
    }

    /// 记录查询
    pub fn record(&self, entry: QueryLogEntry) {
        let mut entries = self.entries.write();
        entries.push(entry);

        // 超出上限时删除最早的记录
        if entries.len() > self.max_entries {
            let excess = entries.len() - self.max_entries;
            entries.drain(0..excess);
        }
    }

    /// 获取所有日志条目
    pub fn entries(&self) -> Vec<QueryLogEntry> {
        self.entries.read().clone()
    }

    /// 获取日志数量
    pub fn len(&self) -> usize {
        self.entries.read().len()
    }

    /// 是否为空
    pub fn is_empty(&self) -> bool {
        self.entries.read().is_empty()
    }

    /// 清空日志
    pub fn clear(&self) {
        self.entries.write().clear();
    }

    /// 获取含有 embedding 的日志（用于 PCA 训练）
    pub fn entries_with_embeddings(&self) -> Vec<QueryLogEntry> {
        self.entries
            .read()
            .iter()
            .filter(|e| e.embedding.is_some())
            .cloned()
            .collect()
    }
}

/// SiFu 统一训练管理器
///
/// 整合 HebbianLearner + ContrastiveFitter + ProjectorTrainer
pub struct SiFuTrainer {
    /// 赫布学习器
    hebbian: HebbianLearner,
    /// 对比学习训练器
    contrastive: ContrastiveFitter,
    /// 查询日志
    query_log: Arc<QueryLog>,
    /// 边权重存储（共享引用）
    edge_weight_store: Arc<RwLock<EdgeWeightStore>>,
    /// 投影训练配置
    projector_config: ProjectorTrainConfig,
}

/// 全量训练结果
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FullTrainingResult {
    /// 赫布学习统计
    pub hebbian_stats: HebbianStats,
    /// 对比学习结果
    pub contrastive_result: TrainingResult,
    /// 晋升扫描结果
    pub promotion_result: PromotionScanResult,
    /// 训练总耗时 (ms)
    pub duration_ms: u64,
}

impl SiFuTrainer {
    /// 创建统一训练管理器
    pub fn new(
        edge_weight_store: Arc<RwLock<EdgeWeightStore>>,
        hebbian_config: HebbianConfig,
        contrastive_config: ContrastiveConfig,
        projector_config: ProjectorTrainConfig,
        query_log: Arc<QueryLog>,
    ) -> Self {
        let hebbian = HebbianLearner::new(edge_weight_store.clone(), hebbian_config);
        let contrastive = ContrastiveFitter::new(edge_weight_store.clone(), contrastive_config);

        Self {
            hebbian,
            contrastive,
            query_log,
            edge_weight_store,
            projector_config,
        }
    }

    /// 记录查询结果（在线学习）
    ///
    /// 自动触发赫布学习并记录日志
    pub fn on_query(&self, entry: QueryLogEntry) {
        // 赫布学习
        self.hebbian.on_query_result(&entry.result_ids);
        // 记录日志
        self.query_log.record(entry);
    }

    /// 增量训练（轻量级，适合定期调用）
    ///
    /// 执行赫布衰减扫描 + 基于最近日志的小批量对比学习
    pub fn fit_incremental(&self, all_node_ids: &[String]) -> TrainingResult {
        // 衰减扫描
        self.hebbian.decay_scan();

        // 从最近日志生成训练样本
        let log_entries = self.query_log.entries();
        let samples = self.contrastive.generate_samples_from_log(&log_entries, all_node_ids);

        // 对比学习
        self.contrastive.fit(&samples)
    }

    /// 全量训练
    ///
    /// 执行完整训练流程：衰减 → 对比学习 → 晋升扫描
    pub fn fit_all(
        &self,
        all_node_ids: &[String],
        promotion_config: &PromotionConfig,
    ) -> FullTrainingResult {
        let start = std::time::Instant::now();

        // 1. 衰减扫描
        self.hebbian.decay_scan();

        // 2. 对比学习
        let log_entries = self.query_log.entries();
        let samples = self.contrastive.generate_samples_from_log(&log_entries, all_node_ids);
        let contrastive_result = self.contrastive.fit(&samples);

        // 3. 晋升扫描
        let promotion_result = run_promotion_scan(&self.edge_weight_store, promotion_config);

        let duration_ms = start.elapsed().as_millis() as u64;

        FullTrainingResult {
            hebbian_stats: self.hebbian.stats(),
            contrastive_result,
            promotion_result,
            duration_ms,
        }
    }

    /// 训练 PCA 投影器
    pub fn fit_pca_projector(&self, input_dim: usize) -> Option<PcaProjector> {
        let entries = self.query_log.entries_with_embeddings();
        if entries.is_empty() {
            return None;
        }

        let embeddings: Vec<Vec<f32>> = entries
            .iter()
            .filter_map(|e| e.embedding.clone())
            .collect();

        if embeddings.is_empty() {
            return None;
        }

        Some(ProjectorTrainer::fit_pca(&embeddings, input_dim, &self.projector_config))
    }

    /// 获取赫布学习器引用
    pub fn hebbian(&self) -> &HebbianLearner {
        &self.hebbian
    }

    /// 获取查询日志引用
    pub fn query_log(&self) -> &QueryLog {
        &self.query_log
    }
}

// ============================================================================
// T50.5 — 权重晋升/降级扫描
// ============================================================================

/// 权重晋升配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromotionConfig {
    /// 晋升到类型共享层的最小共激活次数
    pub promote_to_type_threshold: u64,
    /// 降级回全局默认的最大非活跃时间 (ms)
    pub demote_inactive_ms: i64,
    /// 个体边的最大数量上限
    pub max_individual_edges: usize,
    /// 晋升时的信号强度增幅
    pub promotion_boost: f64,
    /// 降级时的信号强度衰减
    pub demotion_penalty: f64,
}

impl Default for PromotionConfig {
    fn default() -> Self {
        Self {
            promote_to_type_threshold: 10,
            demote_inactive_ms: 7 * 24 * 3600 * 1000_i64, // 7天
            max_individual_edges: 100_000,
            promotion_boost: 0.2,
            demotion_penalty: 0.5,
        }
    }
}

/// 晋升扫描结果
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PromotionScanResult {
    /// 晋升的边数
    pub promoted: usize,
    /// 降级的边数
    pub demoted: usize,
    /// 删除的边数（超过上限）
    pub pruned: usize,
    /// 扫描的总边数
    pub total_scanned: usize,
}

/// 执行权重晋升/降级扫描
///
/// 规则：
/// 1. co_activation_count >= 阈值 → 信号强度晋升（boost）
/// 2. 长期不活跃 → 信号强度降级（penalty）
/// 3. 总边数超过上限 → 按活跃度排序后剪枝
pub fn run_promotion_scan(
    edge_weight_store: &Arc<RwLock<EdgeWeightStore>>,
    config: &PromotionConfig,
) -> PromotionScanResult {
    let mut store = edge_weight_store.write();
    let mut result = PromotionScanResult::default();

    let now = {
        use std::time::{SystemTime, UNIX_EPOCH};
        SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64
    };
    let all_weights = store.all_edge_weights_cloned();
        result.total_scanned = all_weights.len();

        // 收集需要处理的边
        let mut to_promote = Vec::new();
        let mut to_demote = Vec::new();

        for ew in &all_weights {
            // 晋升检查：共激活次数达标
            if ew.co_activation_count >= config.promote_to_type_threshold {
                to_promote.push((ew.source_id.clone(), ew.target_id.clone()));
            }
            // 降级检查：长时间不活跃
            else if (now - ew.updated_at) > config.demote_inactive_ms {
                to_demote.push((ew.source_id.clone(), ew.target_id.clone()));
            }
        }

    // 执行晋升
    for (source, target) in &to_promote {
        if let Some(ew) = store.get_edge_weight_mut(source, target) {
            ew.signal_strength = (ew.signal_strength + config.promotion_boost).min(10.0);
            result.promoted += 1;
        }
    }

    // 执行降级
    for (source, target) in &to_demote {
        if let Some(ew) = store.get_edge_weight_mut(source, target) {
            ew.signal_strength *= config.demotion_penalty;
            result.demoted += 1;
        }
    }

    // 剪枝：超过上限时删除最不活跃的边
    let current_count = store.edge_count();
    if current_count > config.max_individual_edges {
        let excess = current_count - config.max_individual_edges;
        // 按共激活次数排序，删除最不活跃的
        let mut sorted_weights = store.all_edge_weights_cloned();
        sorted_weights.sort_by_key(|ew| ew.co_activation_count);

        for ew in sorted_weights.iter().take(excess) {
            store.remove_edge_weight(&ew.source_id, &ew.target_id);
            result.pruned += 1;
        }
    }

    result
}

// ============================================================================
// Helpers
// ============================================================================

fn chrono_now_ms() -> u64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis() as u64
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::query::sifu::EdgeWeightStore;

    // ---- T50.1 HebbianLearner ----

    #[test]
    fn test_hebbian_co_activation() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let config = HebbianConfig {
            learning_rate: 0.5,
            ..Default::default()
        };
        let learner = HebbianLearner::new(ew_store.clone(), config);

        let ids = vec!["a".to_string(), "b".to_string(), "c".to_string()];
        learner.on_co_activation(&ids, "co_accessed");

        // 应该产生 3 条边：(a,b), (a,c), (b,c)
        let store = ew_store.read();
        assert_eq!(store.edge_count(), 3);

        // signal_strength 应该增加了 0.5
        let (strength, _) = store.resolve_weight("a", "b", "co_accessed");
        assert!(strength > 1.0, "Signal strength should be boosted: {}", strength);
    }

    #[test]
    fn test_hebbian_repeated_co_activation() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let config = HebbianConfig {
            learning_rate: 0.3,
            max_signal_strength: 5.0,
            ..Default::default()
        };
        let learner = HebbianLearner::new(ew_store.clone(), config);

        // 多次共激活
        for _ in 0..10 {
            learner.on_query_result(&["x".to_string(), "y".to_string()]);
        }

        let store = ew_store.read();
        let ew = store.get_edge_weight("x", "y").unwrap();

        // co_activation_count 应该是 10
        assert_eq!(ew.co_activation_count, 10);
        // signal_strength 应该增加了 10 * 0.3 = 3.0，加初始 1.0 = 4.0
        assert!(ew.signal_strength > 2.0, "Should have grown: {}", ew.signal_strength);
        // 不应超过 max
        assert!(ew.signal_strength <= 5.0, "Should not exceed max: {}", ew.signal_strength);
    }

    #[test]
    fn test_hebbian_decay_scan() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let config = HebbianConfig {
            learning_rate: 0.5,
            forgetting_factor: 0.5, // 每次衰减 50%
            min_signal_strength: 0.1,
            ..Default::default()
        };
        let learner = HebbianLearner::new(ew_store.clone(), config);

        // 创建一条边
        learner.on_query_result(&["a".to_string(), "b".to_string()]);

        // 多次衰减
        for _ in 0..20 {
            learner.decay_scan();
        }

        // 边应该被遗忘
        let store = ew_store.read();
        assert_eq!(store.edge_count(), 0, "Edge should be forgotten after many decay scans");
    }

    #[test]
    fn test_hebbian_stats() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let learner = HebbianLearner::new(ew_store, HebbianConfig::default());

        learner.on_query_result(&["a".to_string(), "b".to_string(), "c".to_string()]);

        let stats = learner.stats();
        assert_eq!(stats.total_co_activations, 3); // 3 pairs: (a,b), (a,c), (b,c)
        assert_eq!(stats.edges_updated, 3);
    }

    // ---- T50.2 ContrastiveFitter ----

    #[test]
    fn test_contrastive_fit_basic() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let config = ContrastiveConfig {
            epochs: 20,
            learning_rate: 0.05,
            ..Default::default()
        };
        let fitter = ContrastiveFitter::new(ew_store.clone(), config);

        let samples = vec![
            TrainingSample {
                source_id: "a".to_string(),
                target_id: "b".to_string(),
                relation_type: "similar".to_string(),
                is_positive: true,
            },
            TrainingSample {
                source_id: "a".to_string(),
                target_id: "c".to_string(),
                relation_type: "similar".to_string(),
                is_positive: false,
            },
        ];

        let result = fitter.fit(&samples);

        assert!(result.epochs_completed > 0, "Should complete at least 1 epoch");
        assert!(result.edges_updated > 0, "Should update some edges");
        // 损失应该有所下降或保持稳定
        assert!(result.loss_history.len() > 1, "Should record loss history");
    }

    #[test]
    fn test_contrastive_positive_boost() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let config = ContrastiveConfig {
            epochs: 30,
            learning_rate: 0.1,
            ..Default::default()
        };
        let fitter = ContrastiveFitter::new(ew_store.clone(), config);

        // 只有正例
        let samples = vec![
            TrainingSample {
                source_id: "x".to_string(),
                target_id: "y".to_string(),
                relation_type: "likes".to_string(),
                is_positive: true,
            },
        ];

        fitter.fit(&samples);

        let store = ew_store.read();
        let ew = store.get_edge_weight("x", "y");
        assert!(ew.is_some(), "Should create edge for positive sample");
        assert!(ew.unwrap().signal_strength > 1.0, "Positive sample should boost signal");
    }

    #[test]
    fn test_contrastive_generate_samples() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let config = ContrastiveConfig {
            negative_ratio: 2,
            ..Default::default()
        };
        let fitter = ContrastiveFitter::new(ew_store, config);

        let log = vec![
            QueryLogEntry::new("vector", vec!["a".to_string(), "b".to_string()]),
        ];
        let all_ids = vec!["a".to_string(), "b".to_string(), "c".to_string(), "d".to_string()];

        let samples = fitter.generate_samples_from_log(&log, &all_ids);

        // 正例：(a,b) = 1
        let positives = samples.iter().filter(|s| s.is_positive).count();
        assert!(positives >= 1, "Should have at least 1 positive sample");

        // 负例：2 * 2 = 4 (每个结果节点 × negative_ratio)
        let negatives = samples.iter().filter(|s| !s.is_positive).count();
        assert!(negatives > 0, "Should have negative samples");
    }

    // ---- T50.3 ProjectorTrainer ----

    #[test]
    fn test_projector_trainer_pca() {
        let embeddings: Vec<Vec<f32>> = (0..100)
            .map(|i| {
                let t = i as f32 / 100.0;
                vec![t * 10.0, t * 3.0, t * 1.0, 0.1]
            })
            .collect();

        let config = ProjectorTrainConfig::default();
        let projector = ProjectorTrainer::fit_pca(&embeddings, 4, &config);

        assert_eq!(projector.dims(), (4, 3));
        let evr = projector.explained_variance_ratio();
        assert!(evr[0] > 0.5, "PC1 should explain majority: {}", evr[0]);
    }

    #[test]
    fn test_projector_trainer_anchors() {
        let anchors = vec![
            (vec![1.0, 0.0, 0.0, 0.0], SemanticAxes::new(1.0, 0.0, 0.0)),
            (vec![0.0, 1.0, 0.0, 0.0], SemanticAxes::new(0.0, 1.0, 0.0)),
            (vec![0.0, 0.0, 1.0, 0.0], SemanticAxes::new(0.0, 0.0, 1.0)),
        ];

        let config = ProjectorTrainConfig {
            anchor_epochs: 1000,
            anchor_learning_rate: 0.01,
            ..Default::default()
        };
        let projector = ProjectorTrainer::fit_anchors(&anchors, 4, &config);

        assert_eq!(projector.dims(), (4, 3));
        assert!(projector.fit_mse() < 1.0, "MSE should be reasonable: {}", projector.fit_mse());
    }

    // ---- T50.4 QueryLog + SiFuTrainer ----

    #[test]
    fn test_query_log_basic() {
        let log = QueryLog::new(100);

        log.record(QueryLogEntry::new("vector", vec!["a".to_string(), "b".to_string()]));
        log.record(QueryLogEntry::new("text", vec!["c".to_string()]));

        assert_eq!(log.len(), 2);
        assert!(!log.is_empty());

        let entries = log.entries();
        assert_eq!(entries[0].query_type, "vector");
        assert_eq!(entries[1].query_type, "text");
    }

    #[test]
    fn test_query_log_max_entries() {
        let log = QueryLog::new(3);

        for i in 0..10 {
            log.record(QueryLogEntry::new(format!("q{}", i), vec![]));
        }

        assert_eq!(log.len(), 3);
        let entries = log.entries();
        assert_eq!(entries[0].query_type, "q7"); // 最早的保留
    }

    #[test]
    fn test_sifu_trainer_on_query() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let query_log = Arc::new(QueryLog::new(1000));

        let trainer = SiFuTrainer::new(
            ew_store.clone(),
            HebbianConfig::default(),
            ContrastiveConfig::default(),
            ProjectorTrainConfig::default(),
            query_log.clone(),
        );

        // 模拟查询
        trainer.on_query(QueryLogEntry::new("vector", vec![
            "a".to_string(), "b".to_string(), "c".to_string(),
        ]));

        // 赫布学习应该已执行
        let stats = trainer.hebbian().stats();
        assert_eq!(stats.total_co_activations, 3);

        // 日志应该已记录
        assert_eq!(query_log.len(), 1);
    }

    #[test]
    fn test_sifu_trainer_fit_incremental() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let query_log = Arc::new(QueryLog::new(1000));

        let trainer = SiFuTrainer::new(
            ew_store.clone(),
            HebbianConfig::default(),
            ContrastiveConfig { epochs: 5, ..Default::default() },
            ProjectorTrainConfig::default(),
            query_log,
        );

        // 模拟多次查询
        for i in 0..5 {
            trainer.on_query(QueryLogEntry::new("vector", vec![
                format!("node_{}", i),
                format!("node_{}", i + 1),
            ]));
        }

        let all_ids: Vec<String> = (0..10).map(|i| format!("node_{}", i)).collect();
        let result = trainer.fit_incremental(&all_ids);

        // 应该完成训练
        assert!(result.epochs_completed >= 0); // 可以是 0 如果样本不足
    }

    #[test]
    fn test_sifu_trainer_fit_all() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let query_log = Arc::new(QueryLog::new(1000));

        let trainer = SiFuTrainer::new(
            ew_store,
            HebbianConfig::default(),
            ContrastiveConfig { epochs: 5, ..Default::default() },
            ProjectorTrainConfig::default(),
            query_log,
        );

        // 模拟查询
        for i in 0..3 {
            trainer.on_query(QueryLogEntry::new("vector", vec![
                format!("n_{}", i), format!("n_{}", i + 1),
            ]));
        }

        let all_ids: Vec<String> = (0..10).map(|i| format!("n_{}", i)).collect();
        let promotion_config = PromotionConfig::default();
        let result = trainer.fit_all(&all_ids, &promotion_config);

        assert!(result.duration_ms < 10000, "Training should complete quickly");
    }

    // ---- T50.5 PromotionScanner ----

    #[test]
    fn test_promotion_scan_promote() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));

        // 创建高共激活次数的边
        {
            let mut store = ew_store.write();
            let mut ew = EdgeWeight::new("a", "b", "test");
            ew.co_activation_count = 20; // 超过默认阈值 10
            ew.signal_strength = 1.0;
            store.set_edge_weight_by_id("a", "b", ew);
        }

        let config = PromotionConfig {
            promote_to_type_threshold: 10,
            ..Default::default()
        };

        let result = run_promotion_scan(&ew_store, &config);

        assert_eq!(result.promoted, 1, "Should promote 1 edge");
        assert_eq!(result.total_scanned, 1);

        // 验证信号强度增加
        let store = ew_store.read();
        let ew = store.get_edge_weight("a", "b").unwrap();
        assert!(ew.signal_strength > 1.0, "Signal should be boosted: {}", ew.signal_strength);
    }

    #[test]
    fn test_promotion_scan_demote() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));

        // 创建长时间未更新的边
        {
            let mut store = ew_store.write();
            let mut ew = EdgeWeight::new("a", "b", "test");
            ew.co_activation_count = 1;
            ew.signal_strength = 2.0;
            ew.updated_at = 0; // 很久以前
            store.set_edge_weight_by_id("a", "b", ew);
        }

        let config = PromotionConfig {
            demote_inactive_ms: 1000_i64, // 1秒前
            demotion_penalty: 0.5,
            ..Default::default()
        };

        let result = run_promotion_scan(&ew_store, &config);

        assert_eq!(result.demoted, 1, "Should demote 1 edge");

        let store = ew_store.read();
        let ew = store.get_edge_weight("a", "b").unwrap();
        assert!((ew.signal_strength - 1.0).abs() < 0.01,
            "Signal should be halved: {}", ew.signal_strength);
    }

    #[test]
    fn test_promotion_scan_pruning() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));

        // 创建多条边
        {
            let mut store = ew_store.write();
            for i in 0..10 {
                let mut ew = EdgeWeight::new(&format!("n{}", i), &format!("n{}", i + 1), "test");
                ew.co_activation_count = i as u64;
                store.set_edge_weight_by_id(&format!("n{}", i), &format!("n{}", i + 1), ew);
            }
        }

        let config = PromotionConfig {
            max_individual_edges: 5,
            promote_to_type_threshold: 100, // 高阈值，不会触发晋升
            demote_inactive_ms: i64::MAX,   // 不会触发降级
            ..Default::default()
        };

        let result = run_promotion_scan(&ew_store, &config);

        assert_eq!(result.pruned, 5, "Should prune 5 excess edges");
        assert_eq!(ew_store.read().edge_count(), 5, "Should have 5 edges remaining");
    }

    // ---- T50.6 集成测试 ----

    #[test]
    fn test_end_to_end_learning_cycle() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let query_log = Arc::new(QueryLog::new(10000));

        let trainer = SiFuTrainer::new(
            ew_store.clone(),
            HebbianConfig { learning_rate: 0.2, ..Default::default() },
            ContrastiveConfig { epochs: 10, ..Default::default() },
            ProjectorTrainConfig::default(),
            query_log,
        );

        // 阶段1：模拟多轮查询（重复出现 a-b 对）
        for _ in 0..20 {
            trainer.on_query(QueryLogEntry::new("vector", vec![
                "a".to_string(), "b".to_string(),
            ]));
        }

        // 验证赫布学习：a-b 边的权重应该显著增长
        {
            let store = ew_store.read();
            let ew = store.get_edge_weight("a", "b").unwrap();
            assert!(ew.co_activation_count >= 20, "Should have 20+ co-activations");
            assert!(ew.signal_strength > 2.0, "Signal should have grown significantly: {}", ew.signal_strength);
        }

        // 阶段2：对比学习
        let all_ids: Vec<String> = (0..10).map(|i| format!("node_{}", i)).collect();
        let all_ids_with_ab = {
            let mut ids = all_ids.clone();
            ids.push("a".to_string());
            ids.push("b".to_string());
            ids
        };
        let contrastive_result = trainer.fit_incremental(&all_ids_with_ab);
        // 训练应该完成
        assert!(contrastive_result.epochs_completed > 0 || contrastive_result.edges_updated == 0);

        // 阶段3：晋升扫描
        let promotion_config = PromotionConfig {
            promote_to_type_threshold: 10,
            ..Default::default()
        };
        let promotion_result = run_promotion_scan(&ew_store, &promotion_config);

        // a-b 共激活 20 次，应该被晋升
        assert!(promotion_result.promoted >= 1,
            "High co-activation edge should be promoted: {:?}", promotion_result);
    }

    #[test]
    fn test_learning_strengthens_relevant_edges() {
        let ew_store = Arc::new(RwLock::new(EdgeWeightStore::new(3)));
        let query_log = Arc::new(QueryLog::new(10000));

        let trainer = SiFuTrainer::new(
            ew_store.clone(),
            HebbianConfig { learning_rate: 0.1, ..Default::default() },
            ContrastiveConfig { epochs: 10, ..Default::default() },
            ProjectorTrainConfig::default(),
            query_log,
        );

        // 频繁共现的对
        for _ in 0..50 {
            trainer.on_query(QueryLogEntry::new("vector", vec![
                "rust".to_string(), "cargo".to_string(),
            ]));
        }

        // 偶尔共现的对
        for _ in 0..5 {
            trainer.on_query(QueryLogEntry::new("vector", vec![
                "rust".to_string(), "python".to_string(),
            ]));
        }

        let store = ew_store.read();

        let rust_cargo = store.get_edge_weight("rust", "cargo").unwrap();
        let rust_python = store.get_edge_weight("rust", "python").unwrap();

        // rust-cargo 应该比 rust-python 强得多
        assert!(
            rust_cargo.signal_strength > rust_python.signal_strength,
            "Frequent pair ({}) should be stronger than infrequent ({})",
            rust_cargo.signal_strength, rust_python.signal_strength
        );

        assert!(
            rust_cargo.co_activation_count > rust_python.co_activation_count,
            "Frequent pair co-activation ({}) > infrequent ({})",
            rust_cargo.co_activation_count, rust_python.co_activation_count
        );
    }
}
