//! 多 Provider 负载均衡池 — F-260614-04
//!
//! 与 `df_ai::router`(F-01 阶段4,纯函数选模型)对齐的纯逻辑核心:给定 enabled provider
//! 列表 + 已选模型 ID + 选择策略,返回**有序候选 provider 列表**(主→备用)。
//! 零 IO / 零状态,所有状态(DB 读 / 健康度 / 计数器)由调用方持有。
//!
//! ## 架构定位(为何放 src-tauri 而非 df-ai)
//! `AiProviderRecord` 定义在 df-storage。df-ai 当前存储无关(router.rs 操作 df-ai-core 的
//! `ModelConfig`,非 storage 类型)。把 ProviderPool 放 df-ai 会强制 df-ai→df-storage 依赖,
//! 破坏 df-ai 的存储无关边界。ProviderPool 与 `prompt.rs::get_active_provider` /
//! `secret.rs::build_provider_for` 同属"消费 AiProviderRecord 的 app 层逻辑",故放 src-tauri。
//!
//! ## 与 router 协同(职责切分,不重叠)
//! - **router.select_model_id**:在**单个 provider 的 model_configs 池**中选最优模型(F-01)。
//! - **ProviderPool::select**:在**多个 provider 间**选主 + 列出备用(本模块)。
//! 调用顺序:先 router 选模型 → 再 ProviderPool 选 provider 实例(模型在多 provider 间共享时)。
//!
//! ## 选择策略(论证,见 select 文档)
//! 1. **模型亲和优先**:router 已选 model_id,优先选**其池中含该模型的 provider**(正确性:不擅自换模型)。
//! 2. **加权**:同亲和级内按 weight 降序(weight=0 provider 不入选)。
//! 3. **is_default 兜底**:模型亲和全 miss 时,默认 provider 优先(零变化启动行为)。
//!
//! ## fallback 顺序(select 返回的 Vec 即为 fallback 序)
//! 调用方(agentic loop)按顺序尝试:主 provider 失败(可重试错误)→ 列表下一个 provider 重试。
//! 不可重试错误(401/400)立即放弃,不浪费备用 provider(对齐 retry.rs Fatal 分类)。
//!
//! ## 向后兼容
//! - enabled provider 仅 1 个 → 返回单元素 Vec,调用方无 fallback 路径(行为同 F-01 前)。
//! - enabled provider 0 个 → 返回空 Vec,调用方兜底 get_active_provider(行为同 F-01 前)。
//! - 模型在多 provider 间共享 → 主=含模型且 weight 最高的 provider,备用=其余含模型的 provider。

use df_storage::models::AiProviderRecord;

/// 多 Provider 负载均衡池(单元结构,无状态)。
///
/// `select` 为关联函数:给定 enabled provider 列表 + 已选模型 ID,返回有序候选列表。
/// 对齐 `ModelRouter`(df_ai::router)的单元结构 + 关联函数风格。
pub struct ProviderPool;

impl ProviderPool {
    /// 在 enabled provider 池中选出**有序候选列表**(主 → 备用),供调用方 fallback。
    ///
    /// ## 选择算法(3 步,稳定排序)
    /// 1. **过滤 enabled**:只选 `enabled == true` 的 provider。
    /// 2. **过滤零权重**:weight == 0 的 provider 不参与(weight=0 = 显式禁用主选;
    ///    若需"仅 fallback"配置,用 enabled=false 替代,语义更清晰)。
    /// 3. **排序**(稳定,保输入顺序作 tiebreak):
    ///    - **主键:模型亲和**。含 `model_id` 的 provider 排前(模型亲和=true → 排前)。
    ///      model_id 为 None(调用方未走 router,如标题/扫描)→ 退化为全亲和,跳过此键。
    ///    - **次键:weight 降序**。同亲和级内 weight 高者排前。
    ///    - **末键:is_default 兜底**。同亲和同 weight 时 is_default 排前(启动行为不变)。
    ///
    /// ## 向后兼容
    /// - enabled provider 1 个 → 单元素 Vec,无 fallback。
    /// - 0 个 → 空 Vec,调用方兜底 get_active_provider。
    /// - model_id None → 全亲和,纯按 weight + is_default 排序(标题/扫描路径用)。
    pub fn select(
        providers: &[AiProviderRecord],
        model_id: Option<&str>,
    ) -> Vec<AiProviderRecord> {
        // 步骤 1+2:enabled 且 weight > 0
        let mut candidates: Vec<AiProviderRecord> = providers
            .iter()
            .filter(|p| p.enabled && p.weight > 0)
            .cloned()
            .collect();

        // 步骤 3:稳定排序(主键模型亲和,次键 weight,末键 is_default)。
        // sort_by 稳定:同 key 保输入顺序(created_at DESC,list_all 返回序)。
        // 反转比较结果:大者排前(亲和 true > false;weight 大 > 小;is_default true > false)。
        candidates.sort_by(|a, b| {
            // 主键:模型亲和。model_id None → 视两方都亲和(退化为全过此键)。
            // 必须过滤 m.enabled:用户禁用所有匹配实例时,该 provider 不应被标为亲和,
            // 否则会选不含可用目标模型的 provider 做主,污染 fallback 链顺序。
            let a_affinity = model_id
                .map_or(true, |mid| a.model_configs.iter().any(|m| m.model_id == mid && m.enabled));
            let b_affinity = model_id
                .map_or(true, |mid| b.model_configs.iter().any(|m| m.model_id == mid && m.enabled));
            let by_affinity = b_affinity.cmp(&a_affinity); // true 排前
            if by_affinity != std::cmp::Ordering::Equal {
                return by_affinity;
            }
            // 次键:weight 降序。
            let by_weight = b.weight.cmp(&a.weight);
            if by_weight != std::cmp::Ordering::Equal {
                return by_weight;
            }
            // 末键:is_default 兜底。
            b.is_default.cmp(&a.is_default)
        });

        candidates
    }
}

// ============================================================
// 单测(纯逻辑,零 IO)
// ============================================================

#[cfg(test)]
mod tests {
    use super::*;
    use df_ai::df_ai_core::model::ModelConfig;

    /// 构造可定制 AiProviderRecord(默认 enabled/weight=50/is_default=false,无 model_configs)。
    fn provider(id: &str) -> AiProviderRecord {
        AiProviderRecord {
            id: id.into(),
            name: id.into(),
            provider_type: "openai_compat".into(),
            api_key: String::new(),
            base_url: "https://x".into(),
            default_model: "glm-4-flash".into(),
            models: None,
            model_configs: Vec::new(),
            is_default: false,
            config: None,
            created_at: "0".into(),
            updated_at: "0".into(),
            enabled: true,
            weight: 50,
        }
    }

    /// ── 向后兼容:单 provider 路径 ──

    #[test]
    fn empty_pool_returns_empty() {
        // enabled provider 0 个 → 空 Vec,调用方兜底 get_active_provider。
        let pool: Vec<AiProviderRecord> = vec![];
        assert!(ProviderPool::select(&pool, Some("glm-4-flash")).is_empty());
    }

    #[test]
    fn single_provider_returns_singleton() {
        // 单 provider → 单元素 Vec,无 fallback(行为同 F-01 前)。
        let pool = vec![provider("p1")];
        let selected = ProviderPool::select(&pool, Some("glm-4-flash"));
        assert_eq!(selected.len(), 1);
        assert_eq!(selected[0].id, "p1");
    }

    /// ── 过滤:enabled / weight=0 ──

    #[test]
    fn disabled_provider_filtered() {
        // enabled=false → 不入选(单 provider 场景兜底由调用方处理)。
        let pool = vec![
            AiProviderRecord { enabled: false, ..provider("disabled") },
            provider("enabled"),
        ];
        let selected = ProviderPool::select(&pool, None);
        assert_eq!(selected.len(), 1);
        assert_eq!(selected[0].id, "enabled");
    }

    #[test]
    fn zero_weight_provider_filtered() {
        // weight=0 → 显式排除(语义:weight=0 = 不参与主选也不作 fallback)。
        let pool = vec![
            AiProviderRecord { weight: 0, ..provider("zero") },
            provider("normal"),
        ];
        let selected = ProviderPool::select(&pool, None);
        assert_eq!(selected.len(), 1);
        assert_eq!(selected[0].id, "normal");
    }

    /// ── 主键:模型亲和 ──

    #[test]
    fn model_affinity_picks_provider_with_model() {
        // 两 provider,仅 p2 含目标模型 → p2 排前(即使 p1 weight 更高)。
        let pool = vec![
            AiProviderRecord {
                weight: 90,
                ..provider("p1")
            },
            AiProviderRecord {
                weight: 30,
                model_configs: vec![ModelConfig::with_defaults("glm-4-flash")],
                ..provider("p2")
            },
        ];
        let selected = ProviderPool::select(&pool, Some("glm-4-flash"));
        assert_eq!(selected[0].id, "p2", "含目标模型的 provider 应排前");
        assert_eq!(selected[1].id, "p1");
    }

    #[test]
    fn model_id_none_all_affinity_equal() {
        // model_id None(标题/扫描路径)→ 全亲和,纯按 weight 排序。
        let pool = vec![
            AiProviderRecord { weight: 30, ..provider("low") },
            AiProviderRecord { weight: 90, ..provider("high") },
        ];
        let selected = ProviderPool::select(&pool, None);
        assert_eq!(selected[0].id, "high");
        assert_eq!(selected[1].id, "low");
    }

    /// ── 次键:weight 降序 ──

    #[test]
    fn higher_weight_wins_same_affinity() {
        // 两 provider 都不含目标模型(同亲和=false),weight 高者排前。
        let pool = vec![
            AiProviderRecord { weight: 50, ..provider("low") },
            AiProviderRecord { weight: 80, ..provider("high") },
        ];
        let selected = ProviderPool::select(&pool, Some("glm-4-flash"));
        assert_eq!(selected[0].id, "high");
    }

    /// ── 末键:is_default 兜底 ──

    #[test]
    fn is_default_breaks_weight_tie() {
        // 同亲和 + 同 weight → is_default 排前(启动行为不变)。
        let pool = vec![
            provider("normal"), // is_default=false
            AiProviderRecord { is_default: true, ..provider("default") },
        ];
        let selected = ProviderPool::select(&pool, None);
        assert_eq!(selected[0].id, "default");
    }

    /// ── 综合:多 provider 多维度 ──

    #[test]
    fn full_scenario_orders_correctly() {
        // 候选:
        //  a: enabled=true, weight=70, 含目标模型 → 亲和+weight70
        //  b: enabled=true, weight=90, 不含目标模型 → 非亲和+weight90(排 a 后)
        //  c: enabled=true, weight=70, 含目标模型, is_default=true → 亲和+weight70+default(同 a 但 default 排前)
        //  d: enabled=false → 排除
        //  e: weight=0 → 排除
        //
        // 预期顺序:c(亲和/70/default) > a(亲和/70) > b(非亲和/90)
        let pool = vec![
            AiProviderRecord {
                weight: 70,
                model_configs: vec![ModelConfig::with_defaults("glm-4-flash")],
                ..provider("a")
            },
            AiProviderRecord {
                weight: 90,
                ..provider("b")
            },
            AiProviderRecord {
                weight: 70,
                is_default: true,
                model_configs: vec![ModelConfig::with_defaults("glm-4-flash")],
                ..provider("c")
            },
            AiProviderRecord {
                enabled: false,
                ..provider("d")
            },
            AiProviderRecord {
                weight: 0,
                ..provider("e")
            },
        ];
        let selected = ProviderPool::select(&pool, Some("glm-4-flash"));
        assert_eq!(
            selected.iter().map(|p| p.id.as_str()).collect::<Vec<_>>(),
            vec!["c", "a", "b"],
            "应按 亲和 > weight > is_default 排序,排除 enabled=false/weight=0"
        );
    }

    /// ── 模型亲和:enabled 过滤 ──

    #[test]
    fn model_affinity_excludes_disabled_model() {
        // 回归点:匹配 model 全 disabled 时,provider 不应被标为亲和。
        // 设计:p1 含目标模型但 enabled=false 且 weight=90(高 weight 诱惑),
        //       p2 含目标模型且 enabled=true 但 weight=30(低 weight)。
        // 修复后(过滤 m.enabled):p1 非亲和 / p2 亲和 → p2 因亲和主键排前(即使 weight 低)。
        // bug 复现(不过滤 enabled):p1 p2 同亲和 → 按 weight → p1(90)排前。
        // 位置差异精确捕获回归:p2 排前=修复正确,p1 排前=bug 复现。
        let pool = vec![
            AiProviderRecord {
                weight: 90,
                model_configs: vec![ModelConfig {
                    enabled: false,
                    ..ModelConfig::with_defaults("glm-4-flash")
                }],
                ..provider("p1_disabled_model")
            },
            AiProviderRecord {
                weight: 30,
                model_configs: vec![ModelConfig::with_defaults("glm-4-flash")],
                ..provider("p2_enabled_model")
            },
        ];
        let selected = ProviderPool::select(&pool, Some("glm-4-flash"));
        assert_eq!(
            selected.iter().map(|p| p.id.as_str()).collect::<Vec<_>>(),
            vec!["p2_enabled_model", "p1_disabled_model"],
            "匹配 model 全 disabled 的 p1 非亲和,含 enabled model 的 p2 因亲和排前(即使 weight 低)"
        );
    }
}
