//! E2E 集成测试：验证完整的编译-执行流程
//!
//! 测试覆盖：
//! 1. 编译测试项目（e2e_test_project）
//! 2. 验证编译产物（.obj / .tsnobj）存在
//! 3. 运行生成的可执行文件并验证输出
//!
//! 运行方式：cargo test --test integration_feature_tests -- e2e

use std::path::PathBuf;
use std::process::Command;
use tsnative::project::Project;
use tsnative::project_compiler::ProjectCompiler;
use tsnative::swc_transform::parse;

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

// ========== E2E 编译-执行测试 ==========

/// 简单的 E2E 测试：编译并运行 main.ts
/// main.ts 计算 (1+2)*3 = 9, 然后条件分支 9>5 → 8, 循环 3 次 8→11, 返回 11
#[test]
fn test_e2e_compile_and_run() {
    let e2e_dir = project_root().join("tests").join("e2e_test_project");
    assert!(e2e_dir.exists(), "E2E 测试项目目录应存在: {:?}", e2e_dir);

    // 1. 验证源文件可被解析为 HIR
    let main_ts = e2e_dir.join("src").join("main.ts");
    let source = std::fs::read_to_string(&main_ts)
        .expect("应能读取 src/main.ts");
    let hir = parse(&source)
        .expect("应能成功解析 main.ts 为 HIR");
    assert!(!hir.is_empty(), "HIR 应包含至少一条语句");

    // 2. 验证项目配置可被加载
    let project = Project::from_dir(&e2e_dir)
        .expect("应能成功加载 e2e 测试项目");
    assert_eq!(project.entries.len(), 1, "应有一个入口文件");
    assert!(
        project.entries.contains(&main_ts),
        "入口文件应为 src/main.ts"
    );

    // 3. 使用独立输出目录编译
    let thread_id = std::thread::current().id();
    let output_dir = e2e_dir.join(format!("target/tsn-release-{:?}", thread_id));
    let _ = std::fs::remove_dir_all(&output_dir);
    std::fs::create_dir_all(&output_dir).expect("应能创建输出目录");
    
    let mut compiler = ProjectCompiler::from_dir_with_output(&e2e_dir, &output_dir)
        .expect("应能创建 ProjectCompiler");
    let report = compiler.compile_project()
        .expect("项目编译应成功");

    // 4. 验证编译报告
    assert_eq!(report.errors, 0, "编译应无错误");
    assert!(report.functions_compiled > 0, "应编译了至少一个函数");
    assert!(report.total_code_size > 0, "代码段应有内容");

    // 5. 验证编译产物文件存在
    let tsnobj_path = output_dir.join("main.tsnobj");
    assert!(tsnobj_path.exists(), "应生成了 main.tsnobj: {:?}", tsnobj_path);

    // 6. 验证可执行文件存在
    let exe_path = output_dir.join("main.exe");
    let result = if exe_path.exists() {
        // 运行可执行文件并验证输出
        let result = run_with_timeout(&exe_path, std::time::Duration::from_secs(10));
        
        // 清理输出目录
        let _ = std::fs::remove_dir_all(&output_dir);
        
        match result {
            Ok((stdout, stderr)) => {
                // main.ts 计算 (1+2)*3=9, >5 → 8, 循环+3 → 11, 最终返回 11
                // loader 会在 stdout 打印 "=== Result: 11 ==="
                assert!(
                    stdout.contains("Result: 11"),
                    "输出应包含计算结果 Result: 11, stdout: {:?}, stderr: {:?}",
                    stdout, stderr
                );
                
                // 验证重定位全部成功（格式: "N/N ok, 0 failed"）
                assert!(
                    stdout.contains("ok, 0 failed"),
                    "所有重定位应成功, stdout: {:?}",
                    stdout
                );
                Ok(())
            }
            Err(e) => Err(e),
        }
    } else {
        Err(format!("可执行文件不存在: {:?}", exe_path).into())
    };
    
    if let Err(e) = result {
        panic!("测试失败: {}", e);
    }
}

/// 辅助函数：编译并运行测试项目，验证返回值为 1（成功）
/// 使用独立输出目录避免 DLL 占用冲突
fn compile_and_run_test(test_dir: &std::path::Path) -> (bool, String, String) {
    // 生成唯一的输出目录（基于线程 ID）
    let thread_id = std::thread::current().id();
    let output_dir = test_dir.join(format!("target/tsn-release-{:?}", thread_id));
    
    // 确保输出目录存在且干净
    let _ = std::fs::remove_dir_all(&output_dir);
    std::fs::create_dir_all(&output_dir).expect("应能创建输出目录");

    // 编译到独立目录
    let mut compiler = ProjectCompiler::from_dir_with_output(test_dir, &output_dir)
        .expect("应能创建 ProjectCompiler");
    let report = compiler.compile_project()
        .expect("项目编译应成功");

    assert_eq!(report.errors, 0, "编译应无错误");

    // exe 名称来自入口文件名（如 main.ts → main.exe）
    let exe_path = output_dir.join("main.exe");

    if exe_path.exists() {
        // 设置超时（10 秒）
        let result = run_with_timeout(&exe_path, std::time::Duration::from_secs(10));
        
        // 清理输出目录
        let _ = std::fs::remove_dir_all(&output_dir);
        
        match result {
            Ok((stdout, stderr)) => {
                let success = stdout.contains("Result: 1") && stdout.contains("ok, 0 failed");
                (success, stdout, stderr)
            }
            Err(e) => (false, String::new(), format!("运行超时或失败: {}", e)),
        }
    } else {
        (false, String::new(), format!("可执行文件不存在: {:?}", exe_path))
    }
}

/// 运行命令并设置超时
fn run_with_timeout(
    exe: &std::path::Path,
    timeout: std::time::Duration,
) -> Result<(String, String), Box<dyn std::error::Error>> {
    use std::process::{Command, Stdio};
    use std::time::Instant;
    
    let mut child = Command::new(exe)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;
    
    let start = Instant::now();
    let pid = child.id();
    
    // 轮询检查是否完成
    loop {
        match child.try_wait()? {
            Some(_status) => {
                // 进程已完成
                let mut stdout = String::new();
                let mut stderr = String::new();
                
                if let Some(mut out) = child.stdout.take() {
                    use std::io::Read;
                    out.read_to_string(&mut stdout)?;
                }
                if let Some(mut err) = child.stderr.take() {
                    use std::io::Read;
                    err.read_to_string(&mut stderr)?;
                }
                
                return Ok((stdout, stderr));
            }
            None => {
                // 进程还在运行
                if start.elapsed() > timeout {
                    // 超时，强制 kill
                    let _ = Command::new("taskkill")
                        .args(&["/F", "/PID", &pid.to_string()])
                        .output();
                    return Err("测试超时".into());
                }
                std::thread::sleep(std::time::Duration::from_millis(50));
            }
        }
    }
}

/// E2E 测试：Buffer 模块
#[test]
fn test_e2e_buffer_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("buffer");
    assert!(test_dir.exists(), "Buffer 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Buffer 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：EventEmitter 模块
/// TODO: 需要修复 EventEmitter 实例方法（on/emit 等）
#[test]
fn test_e2e_events_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("events");
    assert!(test_dir.exists(), "Events 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Events 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：crypto 模块
#[test]
fn test_e2e_crypto_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("crypto");
    assert!(test_dir.exists(), "Crypto 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Crypto 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：fs 模块
#[test]
fn test_e2e_fs_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("fs");
    assert!(test_dir.exists(), "FS 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "FS 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：path 模块
#[test]
fn test_e2e_path_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("path");
    assert!(test_dir.exists(), "Path 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Path 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：os 模块
#[test]
fn test_e2e_os_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("os");
    assert!(test_dir.exists(), "OS 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "OS 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：util 模块
#[test]
fn test_e2e_util_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("util");
    assert!(test_dir.exists(), "Util 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Util 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：url 模块
#[test]
fn test_e2e_url_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("url");
    assert!(test_dir.exists(), "URL 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "URL 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：querystring 模块
/// TODO: 修复 js_fn 未解析问题（function 关键字导致）
#[test]
fn test_e2e_querystring_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("querystring");
    assert!(test_dir.exists(), "Querystring 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Querystring 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：process 模块
#[test]
fn test_e2e_process_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("process");
    assert!(test_dir.exists(), "Process 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Process 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：assert 模块
#[test]
fn test_e2e_assert_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("assert");
    assert!(test_dir.exists(), "Assert 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Assert 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// E2E 测试：console 模块
#[test]
fn test_e2e_console_module() {
    let test_dir = project_root().join("tests").join("e2e_module_tests").join("console");
    assert!(test_dir.exists(), "Console 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Console 模块测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

/// 验证编译输出的 obj 文件可被正确解析
#[test]
fn test_e2e_compiled_obj_valid() {
    let e2e_dir = project_root().join("tests").join("e2e_test_project");

    let mut compiler = ProjectCompiler::from_dir(&e2e_dir)
        .expect("应能创建 ProjectCompiler");
    
    // 只执行模块编译（Phase 1），不生成可执行文件
    let main_ts = e2e_dir.join("src").join("main.ts");
    let obj_bytes = compiler.compile_module(&main_ts)
        .expect("main.ts 编译应成功");

    // 验证 obj 文件是有效的 COFF（至少包含数据）
    assert!(!obj_bytes.is_empty(), "生成的 obj 应有内容");
    assert!(obj_bytes.len() > 20, "obj 文件应包含头部 + 代码");

    // 尝试验证 COFF 签名
    // COFF 文件以 IMAGE_FILE_HEADER 开始（20 字节）
    // Machine 字段应在前面2字节
    let machine = u16::from_le_bytes([obj_bytes[0], obj_bytes[1]]);
    // x86_64 的 Machine ID 是 0x8664
    assert_eq!(machine, 0x8664, "COFF Machine 字段应为 x86_64 (0x8664)");
}

// ========== AOT 特性测试：ESM Export ==========
//
// 测试 ESM 命名导出、默认导出、重导出、星号导出、命名空间导入的完整语义
// 基于 Node.js ECMAScript 标准用例适配，验证 AOT 静态编译下所有导入/导出模式

#[test]
fn test_e2e_aot_esm_export() {
    let test_dir = project_root().join("tests").join("e2e_aot_esm_export");
    assert!(test_dir.exists(), "ESM Export 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "ESM Export 测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

// ========== AOT 特性测试：Rest / Spread ==========
//
// 测试数组展开、对象展开、剩余参数、函数调用展开完整语义
// 覆盖空展开、多层展开、混合展开、固定+剩余参数等边界场景

#[test]
fn test_e2e_aot_rest_spread() {
    let test_dir = project_root().join("tests").join("e2e_aot_rest_spread");
    assert!(test_dir.exists(), "Rest/Spread 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Rest/Spread 测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

// ========== AOT 特性测试：Proxy / Reflect ==========
//
// 测试 Proxy 代理对象的创建、get/set/has/deleteProperty 陷阱
// Reflect.get/set/has/deleteProperty/construct/apply 静态方法
// 基于 Node.js ECMAScript 标准用例适配

#[test]
fn test_e2e_aot_proxy_reflect() {
    let test_dir = project_root().join("tests").join("e2e_aot_proxy_reflect");
    assert!(test_dir.exists(), "Proxy/Reflect 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "Proxy/Reflect 测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}

// ========== AOT 特性测试：JSX ==========
//
// 测试 JSX 编译为结构化 VNode 对象 {tag, props, children}
// 覆盖：基础标签、属性、文本子节点、嵌套、多子节点、表达式
// 验证零胶水零 h 函数、纯 TS 结构化调用

#[test]
fn test_e2e_aot_jsx() {
    let test_dir = project_root().join("tests").join("e2e_aot_jsx");
    assert!(test_dir.exists(), "JSX 测试目录应存在: {:?}", test_dir);

    let (success, stdout, stderr) = compile_and_run_test(&test_dir);
    assert!(
        success,
        "JSX 测试应成功, stdout: {:?}, stderr: {:?}",
        stdout, stderr
    );
}
