//! 集成测试：验证 4 个 TypeScript 测试工程能否被正确解析
//!
//! 运行方式：cargo test --test integration_feature_tests
//!
//! 测试覆盖：
//! 1. Shim 测试工程 — ts-native.toml 中的 shims 配置解析
//! 2. node_modules 测试工程 — node_modules 中的 npm 包发现
//! 3. FFI Addon 测试工程 — native_addons 配置解析
//! 4. QuickJS 测试工程 — js-engine 配置解析

use std::path::PathBuf;
use tsnative::project::Project;
use tsnative::swc_transform::parse;

/// 获取项目根目录（Cargo.toml 所在目录）
fn project_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}

/// 获取 feature 测试工程目录
fn feature_dir(name: &str) -> PathBuf {
    project_root().join("tests").join(name)
}

// ========== 1. Shim 测试工程解析 ==========

#[test]
fn test_shim_project_from_dir() {
    // 验证 feature_shim_test 工程能被正确加载
    let root = feature_dir("feature_shim_test");
    let project = Project::from_dir(&root).expect("应能成功加载 shim 测试工程");

    // 验证入口文件被正确识别
    assert!(
        !project.entries.is_empty(),
        "shim 测试工程应有入口文件"
    );
    let main_path = root.join("src").join("main.ts");
    assert!(
        project.entries.contains(&main_path),
        "入口文件应为 src/main.ts"
    );
}

#[test]
fn test_shim_config_parsed() {
    // 验证 ts-native.toml 中的 shims 配置被正确解析
    let root = feature_dir("feature_shim_test");
    let project = Project::from_dir(&root).expect("应能成功加载 shim 测试工程");

    // 验证 5 个 shim 包全部被解析
    assert_eq!(
        project.config.shims.len(),
        5,
        "应有 5 个 shim 配置"
    );

    // 验证每个 shim 的映射值
    assert_eq!(
        project.config.shims.get("lodash").unwrap(),
        "ts_native_shim_lodash",
        "lodash shim 映射应正确"
    );
    assert_eq!(
        project.config.shims.get("dayjs").unwrap(),
        "ts_native_shim_dayjs",
        "dayjs shim 映射应正确"
    );
    assert_eq!(
        project.config.shims.get("uuid").unwrap(),
        "ts_native_shim_uuid",
        "uuid shim 映射应正确"
    );
    assert_eq!(
        project.config.shims.get("clsx").unwrap(),
        "ts_native_shim_clsx",
        "clsx shim 映射应正确"
    );
    assert_eq!(
        project.config.shims.get("chalk").unwrap(),
        "ts_native_shim_chalk",
        "chalk shim 映射应正确"
    );
}

#[test]
fn test_shim_resolve_import_returns_shim_path() {
    // 验证 resolve_import 对 shim 包返回 __shim__ 标记路径
    let root = feature_dir("feature_shim_test");
    let project = Project::from_dir(&root).expect("应能成功加载 shim 测试工程");

    let from = root.join("src").join("main.ts");

    // 所有 shim 包应解析为 __shim__/ 包名 路径
    for pkg in &["lodash", "dayjs", "uuid", "clsx", "chalk"] {
        let resolved = project.resolve_import(&from, pkg);
        assert!(
            resolved.is_some(),
            "shim 包 '{}' 应能被 resolve_import 解析",
            pkg
        );
        let resolved_path = resolved.unwrap();
        let path_str = resolved_path.to_str().unwrap();
        assert!(
            path_str.contains("__shim__"),
            "shim 包 '{}' 的解析路径应包含 __shim__，实际为: {}",
            pkg,
            path_str
        );
        assert!(
            path_str.contains(pkg),
            "shim 包 '{}' 的解析路径应包含包名，实际为: {}",
            pkg,
            path_str
        );
    }

    // 未配置的包应返回 None（既不是 shim，也不是 node_modules）
    let unknown = project.resolve_import(&from, "nonexistent_pkg");
    assert!(
        unknown.is_none(),
        "未配置的包不应被解析"
    );
}

#[test]
fn test_shim_main_ts_parse_to_hir() {
    // 验证 shim 测试工程的 main.ts 能被 swc_transform::parse 正确解析为 HIR
    let root = feature_dir("feature_shim_test");
    let main_path = root.join("src").join("main.ts");
    let source = std::fs::read_to_string(&main_path)
        .expect("应能读取 shim 测试工程的 main.ts");

    let hir = parse(&source).expect("shim 测试工程的 main.ts 应能被解析为 HIR");

    // HIR 应包含多个函数定义（testLodash, testDayjs, testUuid, testClsx, testChalk, main）
    assert!(
        hir.len() >= 5,
        "shim 测试工程应解析出至少 5 个顶层函数，实际: {}",
        hir.len()
    );
}

// ========== 2. node_modules 测试工程解析 ==========

#[test]
fn test_node_modules_project_from_dir() {
    // 验证 feature_node_modules_test 工程能被正确加载
    let root = feature_dir("feature_node_modules_test");
    let project = Project::from_dir(&root).expect("应能成功加载 node_modules 测试工程");

    // 验证入口文件被正确识别
    assert!(
        !project.entries.is_empty(),
        "node_modules 测试工程应有入口文件"
    );
    let main_path = root.join("src").join("main.ts");
    assert!(
        project.entries.contains(&main_path),
        "入口文件应为 src/main.ts"
    );

    // 验证 helper.ts 被识别为模块文件
    let helper_path = root.join("src").join("helper.ts");
    assert!(
        project.files.contains_key(&helper_path),
        "helper.ts 应被识别为源文件"
    );
}

#[test]
fn test_node_modules_no_shims() {
    // 验证 node_modules 测试工程没有配置 shim（ts-native.toml 只有注释）
    let root = feature_dir("feature_node_modules_test");
    let project = Project::from_dir(&root).expect("应能成功加载 node_modules 测试工程");

    assert!(
        project.config.shims.is_empty(),
        "node_modules 测试工程不应有 shim 配置"
    );
}

#[test]
fn test_node_modules_resolve_import_finds_packages() {
    // 验证 resolve_import 对 node_modules 中包的解析行为
    //
    // 注意：当前 resolve_node_modules 从 from 文件的父目录向上搜索，
    // 当 from 为 src/main.ts 时，搜索路径为 src/node_modules/ -> root（break），
    // 因此 root/node_modules/ 下的包无法被找到。
    // 这里验证的是：对于 src/ 下的文件，node_modules 包不会被解析为文件路径。
    let root = feature_dir("feature_node_modules_test");
    let project = Project::from_dir(&root).expect("应能成功加载 node_modules 测试工程");

    let from = root.join("src").join("main.ts");

    // 由于 resolve_node_modules 的搜索逻辑限制，src/ 下的文件无法找到 root/node_modules/ 中的包
    // 这些包不是 shim 也不是用户模块，因此 resolve_import 返回 None
    for pkg in &["math-lib", "string-utils", "app-config"] {
        let resolved = project.resolve_import(&from, pkg);
        assert!(
            resolved.is_none(),
            "包 '{}' 在当前搜索逻辑下不应被 resolve_import 解析（node_modules 在项目根目录，from 在 src/ 下）",
            pkg
        );
    }

    // 验证 node_modules 目录确实存在（通过文件系统直接检查）
    let nm_math_lib = root.join("node_modules").join("math-lib");
    let nm_string_utils = root.join("node_modules").join("string-utils");
    let nm_app_config = root.join("node_modules").join("app-config");
    assert!(
        nm_math_lib.join("package.json").exists(),
        "node_modules/math-lib/package.json 应存在"
    );
    assert!(
        nm_string_utils.join("package.json").exists(),
        "node_modules/string-utils/package.json 应存在"
    );
    assert!(
        nm_app_config.join("package.json").exists(),
        "node_modules/app-config/package.json 应存在"
    );
}

#[test]
fn test_node_modules_resolve_relative_import() {
    // 验证相对路径导入（如 ./helper）能被正确解析
    let root = feature_dir("feature_node_modules_test");
    let project = Project::from_dir(&root).expect("应能成功加载 node_modules 测试工程");

    let from = root.join("src").join("main.ts");

    // ./helper 应解析为 src/helper.ts
    let helper = project.resolve_import(&from, "./helper");
    assert!(
        helper.is_some(),
        "./helper 应能被解析"
    );
    let helper_path = helper.unwrap();
    assert_eq!(
        helper_path,
        root.join("src").join("helper.ts"),
        "./helper 应解析为 src/helper.ts"
    );
}

#[test]
fn test_node_modules_main_ts_parse_to_hir() {
    // 验证 node_modules 测试工程的 main.ts 能被 swc_transform::parse 正确解析为 HIR
    let root = feature_dir("feature_node_modules_test");
    let main_path = root.join("src").join("main.ts");
    let source = std::fs::read_to_string(&main_path)
        .expect("应能读取 node_modules 测试工程的 main.ts");

    let hir = parse(&source).expect("node_modules 测试工程的 main.ts 应能被解析为 HIR");

    // HIR 应包含 main 函数
    assert!(
        !hir.is_empty(),
        "node_modules 测试工程应解析出至少 1 个顶层函数"
    );
}

// ========== 3. FFI Addon 测试工程解析 ==========

#[test]
fn test_ffi_addon_project_from_dir() {
    // 验证 feature_ffi_addon_test 工程能被正确加载
    let root = feature_dir("feature_ffi_addon_test");
    let project = Project::from_dir(&root).expect("应能成功加载 FFI addon 测试工程");

    // 验证入口文件被正确识别
    assert!(
        !project.entries.is_empty(),
        "FFI addon 测试工程应有入口文件"
    );
    let main_path = root.join("src").join("main.ts");
    assert!(
        project.entries.contains(&main_path),
        "入口文件应为 src/main.ts"
    );
}

#[test]
fn test_ffi_addon_config_parsed() {
    // 验证 ts-native.toml 中的 native_addons 配置被正确解析
    let root = feature_dir("feature_ffi_addon_test");
    let project = Project::from_dir(&root).expect("应能成功加载 FFI addon 测试工程");

    // 应有 2 个 native addon 配置
    assert_eq!(
        project.config.native_addons.len(),
        2,
        "应有 2 个 native addon 配置"
    );

    // 验证 image_proc addon
    let image_proc = project.config.native_addons.get("image_proc")
        .expect("应有 image_proc addon 配置");
    assert_eq!(
        image_proc.dll, "image_proc.dll",
        "image_proc 的 dll 应为 image_proc.dll"
    );
    assert_eq!(
        image_proc.functions.len(), 5,
        "image_proc 应有 5 个函数映射"
    );
    assert_eq!(
        image_proc.functions.get("resize").unwrap(), "img_resize",
        "resize 应映射到 img_resize"
    );
    assert_eq!(
        image_proc.functions.get("rotate").unwrap(), "img_rotate",
        "rotate 应映射到 img_rotate"
    );
    assert_eq!(
        image_proc.functions.get("crop").unwrap(), "img_crop",
        "crop 应映射到 img_crop"
    );
    assert_eq!(
        image_proc.functions.get("filter").unwrap(), "img_apply_filter",
        "filter 应映射到 img_apply_filter"
    );
    assert_eq!(
        image_proc.functions.get("get_info").unwrap(), "img_get_info",
        "get_info 应映射到 img_get_info"
    );

    // 验证 crypto_accel addon
    let crypto_accel = project.config.native_addons.get("crypto_accel")
        .expect("应有 crypto_accel addon 配置");
    assert_eq!(
        crypto_accel.dll, "crypto_accel.dll",
        "crypto_accel 的 dll 应为 crypto_accel.dll"
    );
    assert_eq!(
        crypto_accel.functions.len(), 3,
        "crypto_accel 应有 3 个函数映射"
    );
    assert_eq!(
        crypto_accel.functions.get("hash").unwrap(), "accel_hash",
        "hash 应映射到 accel_hash"
    );
    assert_eq!(
        crypto_accel.functions.get("encrypt").unwrap(), "accel_encrypt",
        "encrypt 应映射到 accel_encrypt"
    );
    assert_eq!(
        crypto_accel.functions.get("decrypt").unwrap(), "accel_decrypt",
        "decrypt 应映射到 accel_decrypt"
    );
}

#[test]
fn test_ffi_addon_resolve_import_behavior() {
    // 验证 native addon 包不会被 resolve_import 解析为文件路径
    // addon 的处理由 codegen 阶段完成，而非模块解析
    let root = feature_dir("feature_ffi_addon_test");
    let project = Project::from_dir(&root).expect("应能成功加载 FFI addon 测试工程");

    let from = root.join("src").join("main.ts");

    // image_proc 和 crypto_accel 是 native addon，不是文件模块
    // resolve_import 不应将它们解析为普通文件路径
    let image_proc_resolved = project.resolve_import(&from, "image_proc");
    // addon 包不在 node_modules 中，也不在 shim 中，应返回 None
    assert!(
        image_proc_resolved.is_none(),
        "native addon 'image_proc' 不应被 resolve_import 解析为文件路径"
    );

    let crypto_accel_resolved = project.resolve_import(&from, "crypto_accel");
    assert!(
        crypto_accel_resolved.is_none(),
        "native addon 'crypto_accel' 不应被 resolve_import 解析为文件路径"
    );
}

#[test]
fn test_ffi_addon_main_ts_parse_to_hir() {
    // 验证 FFI addon 测试工程的 main.ts 能被 swc_transform::parse 正确解析为 HIR
    let root = feature_dir("feature_ffi_addon_test");
    let main_path = root.join("src").join("main.ts");
    let source = std::fs::read_to_string(&main_path)
        .expect("应能读取 FFI addon 测试工程的 main.ts");

    let hir = parse(&source).expect("FFI addon 测试工程的 main.ts 应能被解析为 HIR");

    // HIR 应包含多个函数定义（testImageProc, testCryptoAccel, main）
    assert!(
        hir.len() >= 3,
        "FFI addon 测试工程应解析出至少 3 个顶层函数，实际: {}",
        hir.len()
    );
}

// ========== 4. QuickJS 测试工程解析 ==========

#[test]
fn test_quickjs_project_from_dir() {
    // 验证 feature_quickjs_test 工程能被正确加载
    let root = feature_dir("feature_quickjs_test");
    let project = Project::from_dir(&root).expect("应能成功加载 QuickJS 测试工程");

    // 验证入口文件被正确识别
    assert!(
        !project.entries.is_empty(),
        "QuickJS 测试工程应有入口文件"
    );
    let main_path = root.join("src").join("main.ts");
    assert!(
        project.entries.contains(&main_path),
        "入口文件应为 src/main.ts"
    );
}

#[test]
fn test_quickjs_engine_config_parsed() {
    // 验证 ts-native.toml 中的 js-engine 配置被正确解析
    let root = feature_dir("feature_quickjs_test");
    let project = Project::from_dir(&root).expect("应能成功加载 QuickJS 测试工程");

    // 验证 QuickJS 引擎已启用
    assert!(
        project.config.js_engine.enabled,
        "QuickJS 引擎应被启用"
    );

    // 验证 fallback 包列表
    let fallback = &project.config.js_engine.fallback;
    assert_eq!(
        fallback.len(),
        5,
        "应有 5 个 fallback 包"
    );

    // 验证 fallback 包的内容和顺序
    assert_eq!(
        fallback[0], "axios",
        "第 1 个 fallback 包应为 axios"
    );
    assert_eq!(
        fallback[1], "express",
        "第 2 个 fallback 包应为 express"
    );
    assert_eq!(
        fallback[2], "lodash-fp",
        "第 3 个 fallback 包应为 lodash-fp"
    );
    assert_eq!(
        fallback[3], "moment",
        "第 4 个 fallback 包应为 moment"
    );
    assert_eq!(
        fallback[4], "handlebars",
        "第 5 个 fallback 包应为 handlebars"
    );
}

#[test]
fn test_quickjs_no_shims_and_no_addons() {
    // 验证 QuickJS 测试工程没有配置 shim 和 native addon
    let root = feature_dir("feature_quickjs_test");
    let project = Project::from_dir(&root).expect("应能成功加载 QuickJS 测试工程");

    assert!(
        project.config.shims.is_empty(),
        "QuickJS 测试工程不应有 shim 配置"
    );
    assert!(
        project.config.native_addons.is_empty(),
        "QuickJS 测试工程不应有 native addon 配置"
    );
}

#[test]
fn test_quickjs_resolve_import_behavior() {
    // 验证 QuickJS fallback 包的 resolve_import 行为
    // fallback 包不在 node_modules 中，也不在 shim 中，resolve_import 应返回 None
    // （QuickJS fallback 的处理由 codegen 阶段根据 js_engine 配置完成）
    let root = feature_dir("feature_quickjs_test");
    let project = Project::from_dir(&root).expect("应能成功加载 QuickJS 测试工程");

    let from = root.join("src").join("main.ts");

    // fallback 包不在 node_modules 中，resolve_import 应返回 None
    for pkg in &["axios", "express", "lodash-fp", "moment", "handlebars"] {
        let resolved = project.resolve_import(&from, pkg);
        assert!(
            resolved.is_none(),
            "QuickJS fallback 包 '{}' 不应被 resolve_import 解析为文件路径",
            pkg
        );
    }
}

#[test]
fn test_quickjs_main_ts_parse_to_hir() {
    // 验证 QuickJS 测试工程的 main.ts 能被 swc_transform::parse 正确解析为 HIR
    let root = feature_dir("feature_quickjs_test");
    let main_path = root.join("src").join("main.ts");
    let source = std::fs::read_to_string(&main_path)
        .expect("应能读取 QuickJS 测试工程的 main.ts");

    let hir = parse(&source).expect("QuickJS 测试工程的 main.ts 应能被解析为 HIR");

    // HIR 应包含多个函数定义（testAxios, testExpress, testLodashFp, testMoment, testHandlebars, main）
    assert!(
        hir.len() >= 5,
        "QuickJS 测试工程应解析出至少 5 个顶层函数，实际: {}",
        hir.len()
    );
}
