use std::path::Path;
use std::path::PathBuf;

use anyhow::Result;
use codex_core::config::Config;
use codex_core::config::Constrained;
use codex_features::Feature;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;

use crate::test_codex::TestCodex;
use crate::test_codex::test_codex;

#[derive(Clone)]
pub struct ZshForkRuntime {
    zsh_path: PathBuf,
    main_execve_wrapper_exe: PathBuf,
}

impl ZshForkRuntime {
    fn apply_to_config(
        &self,
        config: &mut Config,
        approval_policy: AskForApproval,
        permission_profile: PermissionProfile,
    ) {
        config
            .features
            .enable(Feature::ShellTool)
            .expect("test config should allow feature update");
        config
            .features
            .enable(Feature::ShellZshFork)
            .expect("test config should allow feature update");
        config.zsh_path = Some(self.zsh_path.clone());
        config.main_execve_wrapper_exe = Some(self.main_execve_wrapper_exe.clone());
        config.permissions.allow_login_shell = false;
        config.permissions.approval_policy = Constrained::allow_any(approval_policy);
        config
            .permissions
            .set_permission_profile(permission_profile)
            .expect("set permission profile");
    }
}

pub fn restrictive_workspace_write_profile() -> PermissionProfile {
    PermissionProfile::workspace_write_with(
        &[],
        NetworkSandboxPolicy::Restricted,
        /*exclude_tmpdir_env_var*/ true,
        /*exclude_slash_tmp*/ true,
    )
}

pub fn zsh_fork_runtime(test_name: &str) -> Result<Option<ZshForkRuntime>> {
    let Some(zsh_path) = find_test_zsh_path()? else {
        return Ok(None);
    };
    if !supports_exec_wrapper_intercept(&zsh_path) {
        eprintln!(
            "skipping {test_name}: zsh does not support EXEC_WRAPPER intercepts ({})",
            zsh_path.display()
        );
        return Ok(None);
    }
    let Ok(main_execve_wrapper_exe) = codex_utils_cargo_bin::cargo_bin("codex-execve-wrapper")
    else {
        eprintln!("skipping {test_name}: unable to resolve `codex-execve-wrapper` binary");
        return Ok(None);
    };

    Ok(Some(ZshForkRuntime {
        zsh_path,
        main_execve_wrapper_exe,
    }))
}

pub async fn build_zsh_fork_test<F>(
    server: &wiremock::MockServer,
    runtime: ZshForkRuntime,
    approval_policy: AskForApproval,
    permission_profile: PermissionProfile,
    pre_build_hook: F,
) -> Result<TestCodex>
where
    F: FnOnce(&Path) + Send + 'static,
{
    let mut builder = test_codex()
        .with_pre_build_hook(pre_build_hook)
        .with_config(move |config| {
            runtime.apply_to_config(config, approval_policy, permission_profile);
        });
    builder.build(server).await
}

pub async fn build_unified_exec_zsh_fork_test<F>(
    server: &wiremock::MockServer,
    runtime: ZshForkRuntime,
    approval_policy: AskForApproval,
    permission_profile: PermissionProfile,
    pre_build_hook: F,
) -> Result<TestCodex>
where
    F: FnOnce(&Path) + Send + 'static,
{
    let mut builder = test_codex()
        .with_pre_build_hook(pre_build_hook)
        .with_config(move |config| {
            runtime.apply_to_config(config, approval_policy, permission_profile);
            config.use_experimental_unified_exec_tool = true;
            config
                .features
                .enable(Feature::UnifiedExec)
                .expect("test config should allow feature update");
            config
                .features
                .enable(Feature::UnifiedExecZshFork)
                .expect("test config should allow feature update");
        });
    builder.build(server).await
}

fn find_test_zsh_path() -> Result<Option<PathBuf>> {
    let repo_root = codex_utils_cargo_bin::repo_root()?;
    let dotslash_zsh = repo_root.join("codex-rs/app-server/tests/suite/zsh");
    if !dotslash_zsh.is_file() {
        eprintln!(
            "skipping zsh-fork test: shared zsh DotSlash file not found at {}",
            dotslash_zsh.display()
        );
        return Ok(None);
    }

    match crate::fetch_dotslash_file(&dotslash_zsh, /*dotslash_cache*/ None) {
        Ok(path) => Ok(Some(path)),
        Err(error) => {
            eprintln!("skipping zsh-fork test: failed to fetch zsh via dotslash: {error:#}");
            Ok(None)
        }
    }
}

fn supports_exec_wrapper_intercept(zsh_path: &Path) -> bool {
    let status = std::process::Command::new(zsh_path)
        .arg("-fc")
        .arg("/usr/bin/true")
        .env("EXEC_WRAPPER", "/usr/bin/false")
        .status();
    match status {
        Ok(status) => !status.success(),
        Err(_) => false,
    }
}
