//! Provider locality 判定 + path 脱敏(核心设计3)
//!
//! 解决 🔴HIGH-1:远程 provider 注入项目 path 不再裸泄用户名/公司目录/客户名。
//! - [`ProviderLocality`]:Local / Remote 二态
//! - [`locality_of`]:据 [`AiProviderRecord::base_url`] 推断(localhost/127.0.0.1/
//!   0.0.0.0/::1/ollama = Local)
//! - [`sanitize_for`]:Local 全路径 / Remote basename(失败回退占位标记)

use std::path::Path;

use df_storage::models::AiProviderRecord;

/// Provider 部署位置,决定注入 path 的脱敏粒度。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderLocality {
    /// 本地 provider(base_url 指向 localhost/127.0.0.1/0.0.0.0/::1/ollama):保留全路径。
    Local,
    /// 远程 provider(第三方云):仅保留 basename,避免泄漏盘符/用户名/公司目录。
    Remote,
}

/// 据 provider 的 base_url 推断 locality。
///
/// Local 判定(大小写不敏感,任意子串命中即 Local):
/// - `localhost` / `127.0.0.1` / `0.0.0.0` / `::1`(本地回环地址)
/// - `ollama`(Ollama 默认绑定 127.0.0.1:11434,常以 host 名出现)
///
/// 其余(https://api.openai.com / 自建远程网关)视为 Remote。
/// base_url 为空(配置异常)按 Remote 保守处理(脱敏优先,宁可漏放全路径不漏泄)。
pub fn locality_of(provider: &AiProviderRecord) -> ProviderLocality {
    let url = provider.base_url.to_lowercase();
    const LOCAL_MARKERS: &[&str] = &[
        "localhost",
        "127.0.0.1",
        "0.0.0.0",
        "::1",
        "ollama",
    ];
    if LOCAL_MARKERS.iter().any(|m| url.contains(m)) {
        ProviderLocality::Local
    } else {
        ProviderLocality::Remote
    }
}

/// 按 locality 脱敏原始路径。
///
/// - [`ProviderLocality::Local`]:原样返回(本地 provider 直接操作本地 FS,需全路径)。
/// - [`ProviderLocality::Remote`]:取 basename(`Path::file_name`),失败回退
///   `[remote-path-hidden]`(防裸泄 + 防 LLM 拿到乱码路径瞎调文件工具)。
///
/// 注:脱敏后的字符串由调用方包进 [`df_types::augmentation::SanitizedPath`] newtype,
/// 强制走脱敏入口(防裸 String 误用未脱敏值)。
pub fn sanitize_for(raw: &str, loc: ProviderLocality) -> String {
    match loc {
        ProviderLocality::Local => raw.to_string(),
        ProviderLocality::Remote => Path::new(raw)
            .file_name()
            .and_then(|n| n.to_str())
            .filter(|s| !s.is_empty())
            .map(|s| s.to_string())
            .unwrap_or_else(|| "[remote-path-hidden]".to_string()),
    }
}

// ============================================================
// 单测:locality_of / sanitize_for(覆盖 localhost/127.0.0.1/0.0.0.0/::1/ollama/远程)
// ============================================================

#[cfg(test)]
mod tests {
    use super::*;
    use df_storage::models::AiProviderRecord;
    use df_types::now_millis;

    /// 构造测试用 AiProviderRecord(只关心 base_url,其余字段占位)。
    fn provider_with_url(base_url: &str) -> AiProviderRecord {
        AiProviderRecord {
            id: "test".to_string(),
            name: "test".to_string(),
            provider_type: "openai_compat".to_string(),
            api_key: String::new(),
            base_url: base_url.to_string(),
            default_model: "m".to_string(),
            models: None,
            model_configs: Vec::new(),
            is_default: false,
            config: None,
            created_at: now_millis().to_string(),
            updated_at: now_millis().to_string(),
            enabled: true,
            weight: 50,
        }
    }

    // ---------- locality_of ----------

    #[test]
    fn locality_localhost() {
        assert_eq!(locality_of(&provider_with_url("http://localhost:11434")), ProviderLocality::Local);
    }

    #[test]
    fn locality_127() {
        assert_eq!(locality_of(&provider_with_url("http://127.0.0.1:8080")), ProviderLocality::Local);
    }

    #[test]
    fn locality_0000() {
        assert_eq!(locality_of(&provider_with_url("http://0.0.0.0:3000")), ProviderLocality::Local);
    }

    #[test]
    fn locality_ipv6_loopback() {
        assert_eq!(locality_of(&provider_with_url("http://[::1]:8080")), ProviderLocality::Local);
    }

    #[test]
    fn locality_ollama_marker() {
        // ollama 作为 host 名(部分用户配 http://ollama:11434 容器网络场景)
        assert_eq!(locality_of(&provider_with_url("http://ollama:11434")), ProviderLocality::Local);
    }

    #[test]
    fn locality_case_insensitive() {
        // 大小写不敏感:LOCALHOST 也应识别
        assert_eq!(locality_of(&provider_with_url("http://LOCALHOST:11434")), ProviderLocality::Local);
    }

    #[test]
    fn locality_remote_openai() {
        assert_eq!(locality_of(&provider_with_url("https://api.openai.com")), ProviderLocality::Remote);
    }

    #[test]
    fn locality_remote_custom_gateway() {
        assert_eq!(locality_of(&provider_with_url("https://my-llm-gateway.company.com")), ProviderLocality::Remote);
    }

    #[test]
    fn locality_empty_url_defaults_remote() {
        // 空 base_url(配置异常)保守按 Remote 脱敏
        assert_eq!(locality_of(&provider_with_url("")), ProviderLocality::Remote);
    }

    // ---------- sanitize_for ----------

    #[test]
    fn sanitize_local_keeps_full_path() {
        let raw = "E:/wk-lab/devflow";
        assert_eq!(sanitize_for(raw, ProviderLocality::Local), "E:/wk-lab/devflow");
    }

    #[test]
    fn sanitize_remote_basename_unix() {
        // Unix 风格路径:取最后一段 basename
        assert_eq!(sanitize_for("/home/alice/devflow", ProviderLocality::Remote), "devflow");
    }

    #[test]
    fn sanitize_remote_basename_windows() {
        // Windows 路径:取 basename(去盘符/用户目录)
        assert_eq!(sanitize_for("C:\\Users\\alice\\projects\\devflow", ProviderLocality::Remote), "devflow");
    }

    #[test]
    fn sanitize_remote_basename_forward_slash() {
        assert_eq!(sanitize_for("E:/wk-lab/devflow", ProviderLocality::Remote), "devflow");
    }

    #[test]
    fn sanitize_remote_root_only() {
        // 根路径(file_name 返 None)回退占位标记,不裸泄空串也不裸泄根
        assert_eq!(sanitize_for("/", ProviderLocality::Remote), "[remote-path-hidden]");
        assert_eq!(sanitize_for("C:\\", ProviderLocality::Remote), "[remote-path-hidden]");
    }

    #[test]
    fn sanitize_remote_empty() {
        // 空串回退占位标记
        assert_eq!(sanitize_for("", ProviderLocality::Remote), "[remote-path-hidden]");
    }

    #[test]
    fn sanitize_local_empty_stays_empty() {
        // Local 原样返回(空串保持空串,Local 不脱敏)
        assert_eq!(sanitize_for("", ProviderLocality::Local), "");
    }
}
