use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
use tauri::Manager;

/// 包装 Child，在 Drop 时强制终止整个进程树（防止 panic/crash 时泄漏）
struct ProcessGuard {
    child: Option<Child>,
}

impl ProcessGuard {
    fn new(child: Child) -> Self {
        Self { child: Some(child) }
    }

    fn take(&mut self) -> Option<Child> {
        self.child.take()
    }
}

impl Drop for ProcessGuard {
    fn drop(&mut self) {
        if let Some(ref mut child) = self.child.take() {
            kill_process_tree(child);
        }
    }
}

struct StreamlitProcess(Mutex<Option<ProcessGuard>>);

/// 等待指定端口可连接，最多等待 timeout_secs 秒
fn wait_for_port(host: &str, port: u16, timeout_secs: u64) -> bool {
    let start = std::time::Instant::now();
    while start.elapsed() < std::time::Duration::from_secs(timeout_secs) {
        if std::net::TcpStream::connect((host, port)).is_ok() {
            return true;
        }
        std::thread::sleep(std::time::Duration::from_millis(200));
    }
    false
}

/// 终止整个进程树（跨平台）
///
/// - Windows: taskkill /F /T → 终止目标进程及其所有子进程
/// - Unix: SIGTERM → 等待 3s → SIGKILL
fn kill_process_tree(child: &mut Child) {
    #[cfg(windows)]
    {
        let pid = child.id();
        // taskkill /T 递归终止整个进程树，确保 Streamlit worker 等孙进程也被清理
        match Command::new("taskkill")
            .args(["/F", "/T", "/PID", &pid.to_string()])
            .stdout(Stdio::null())
            .stderr(Stdio::null())
            .spawn()
        {
            Ok(mut tk) => {
                let _ = tk.wait();
                log::info!("[清理] taskkill /T /PID {} 完成", pid);
            }
            Err(e) => {
                log::warn!("[清理] taskkill 调用失败: {}，回退到直接 kill", e);
                let _ = child.kill();
            }
        }
        let _ = child.wait();
        log::info!("[清理] Streamlit 进程树已终止 (Windows, PID: {})", pid);
    }

    #[cfg(unix)]
    {
        let pid = child.id();
        unsafe {
            extern "C" {
                fn kill(pid: i32, sig: i32) -> i32;
            }
            kill(pid as i32, 15); // SIGTERM
        }
        std::thread::sleep(std::time::Duration::from_secs(3));

        if let Ok(Some(_)) = child.try_wait() {
            log::info!("[清理] Streamlit 进程已优雅退出 (SIGTERM, PID: {})", pid);
            return;
        }
        log::warn!("[清理] Streamlit 进程未响应 SIGTERM，强制终止...");

        let _ = child.kill();
        let _ = child.wait();
        log::info!("[清理] Streamlit 进程已强制终止 (Unix, PID: {})", pid);
    }

    #[cfg(not(any(windows, unix)))]
    {
        let _ = child.kill();
        let _ = child.wait();
    }
}

/// 从 StreamlitProcess 状态中取出进程并终止
fn cleanup_streamlit(state: &StreamlitProcess) {
    if let Ok(mut guard) = state.0.lock() {
        if let Some(ref mut pg) = *guard {
            if let Some(ref mut child) = pg.take() {
                kill_process_tree(child);
            }
        }
        // 清除空壳，避免 Drop 时二次处理
        *guard = None;
    }
}

/// 在 debug 模式下，尝试多种方式启动 Streamlit
fn spawn_streamlit_debug(project_root: &std::path::Path) -> Result<Child, String> {
    let script_path = project_root.join("geo_tool.py");
    let script = script_path.to_str().unwrap_or("geo_tool.py");

    let args = [
        "run",
        script,
        "--server.port",
        "8501",
        "--server.address",
        "127.0.0.1",
        "--server.headless",
        "true",
        "--browser.gatherUsageStats",
        "false",
        "--logger.level",
        "error",
    ];

    log::info!("[开发模式] 启动 Streamlit: {:?}", script_path);

    let result = Command::new("streamlit")
        .args(&args)
        .current_dir(project_root)
        .stdout(Stdio::null())
        .stderr(Stdio::inherit())
        .spawn();

    if let Ok(child) = result {
        log::info!("[开发模式] 通过 'streamlit' 启动成功 (PID: {})", child.id());
        return Ok(child);
    }
    log::warn!("[开发模式] 'streamlit' 命令未找到，尝试 python -m streamlit ...");

    Command::new("python")
        .arg("-m")
        .arg("streamlit")
        .args(&args)
        .current_dir(project_root)
        .stdout(Stdio::null())
        .stderr(Stdio::inherit())
        .spawn()
        .map(|child| {
            log::info!("[开发模式] 通过 'python -m streamlit' 启动成功 (PID: {})", child.id());
            child
        })
        .map_err(|e| {
            let msg = format!(
                "无法启动 Streamlit。已尝试:\n  \
                 1) streamlit run ...\n  \
                 2) python -m streamlit run ...\n  \
                 请确认已安装 streamlit:  pip install streamlit\n  \
                 最终错误: {}",
                e
            );
            log::error!("[开发模式] {}", msg);
            msg
        })
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    let app = tauri::Builder::default()
        .setup(|app| {
            if cfg!(debug_assertions) {
                app.handle().plugin(
                    tauri_plugin_log::Builder::default()
                        .level(log::LevelFilter::Info)
                        .build(),
                )?;
            }

            let child_result = if cfg!(debug_assertions) {
                let manifest_dir =
                    std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
                let project_root = manifest_dir.parent().unwrap_or(&manifest_dir);
                spawn_streamlit_debug(project_root)
            } else {
                let resource_dir = app
                    .path()
                    .resource_dir()
                    .expect("无法获取资源目录");

                let exe_name = if cfg!(target_os = "windows") {
                    "geo_tool_app.exe"
                } else {
                    "geo_tool_app"
                };

                let exe_path_legacy =
                    resource_dir.join("_up_").join("dist_python").join(exe_name);
                let exe_path_direct = resource_dir.join("dist_python").join(exe_name);

                let exe_path = if exe_path_legacy.exists() {
                    exe_path_legacy
                } else if exe_path_direct.exists() {
                    exe_path_direct
                } else {
                    let msg = format!(
                        "未找到 Streamlit 打包应用:\n  尝试1: {:?}\n  尝试2: {:?}",
                        exe_path_legacy, exe_path_direct
                    );
                    log::error!("[生产模式] {}", msg);
                    eprintln!("错误：{}", msg);
                    return Ok(());
                };

                log::info!("[生产模式] 启动打包应用: {:?}", exe_path);

                let mut cmd = Command::new(&exe_path);
                cmd.current_dir(&resource_dir)
                    .stdin(Stdio::null())
                    .stdout(Stdio::null())
                    .stderr(Stdio::null());

                #[cfg(target_os = "windows")]
                {
                    use std::os::windows::process::CommandExt;
                    const CREATE_NO_WINDOW: u32 = 0x08000000u32;
                    cmd.creation_flags(CREATE_NO_WINDOW);
                }

                cmd.spawn().map_err(|e| {
                    format!("无法启动打包应用 {:?}: {}", exe_path, e)
                })
            };

            match child_result {
                Ok(child) => {
                    log::info!("Streamlit 进程已启动 (PID: {})", child.id());
                    let guard = ProcessGuard::new(child);
                    app.manage(StreamlitProcess(Mutex::new(Some(guard))));
                }
                Err(e) => {
                    log::error!("启动 Streamlit 失败: {}", e);
                    eprintln!("错误：{}", e);
                }
            }

            if !wait_for_port("127.0.0.1", 8501, 15) {
                log::warn!("Streamlit 在 15 秒内未能就绪，请检查是否已安装 streamlit");
            } else {
                log::info!("Streamlit 已就绪 (127.0.0.1:8501)");
            }

            Ok(())
        })
        .build(tauri::generate_context!())
        .expect("error while building tauri application");

    app.run(|app_handle, event| {
        match event {
            tauri::RunEvent::WindowEvent { event, .. } => {
                if let tauri::WindowEvent::CloseRequested { .. } = event {
                    log::info!("窗口关闭请求，正在清理子进程...");
                    if let Some(state) = app_handle.try_state::<StreamlitProcess>() {
                        cleanup_streamlit(&*state);
                    }
                }
            }
            tauri::RunEvent::Exit => {
                log::info!("应用退出，执行最终清理...");
                if let Some(state) = app_handle.try_state::<StreamlitProcess>() {
                    cleanup_streamlit(&*state);
                }
            }
            _ => {}
        }
    });
}
