use anyhow::Result;
#[cfg(not(unix))]
use anyhow::anyhow;
#[cfg(not(unix))]
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
#[cfg(not(unix))]
use std::path::Path;

pub(crate) const ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR: &str =
    "VTCODE_ZSH_EXEC_BRIDGE_WRAPPER_SOCKET";
pub(crate) const ZSH_EXEC_WRAPPER_MODE_ENV_VAR: &str = "VTCODE_ZSH_EXEC_WRAPPER_MODE";
pub(crate) const EXEC_WRAPPER_ENV_VAR: &str = "EXEC_WRAPPER";

#[derive(Debug, Clone, Serialize, Deserialize)]
struct WrapperExecRequest {
    request_id: String,
    file: String,
    argv: Vec<String>,
    cwd: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum WrapperExecAction {
    Allow,
    Deny,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct WrapperExecResponse {
    request_id: String,
    action: WrapperExecAction,
    reason: Option<String>,
}

#[cfg(unix)]
mod unix_impl {
    use super::{
        EXEC_WRAPPER_ENV_VAR, WrapperExecAction, WrapperExecRequest, WrapperExecResponse,
        ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR, ZSH_EXEC_WRAPPER_MODE_ENV_VAR,
    };
    use anyhow::{Context, Result, bail};
    use hashbrown::HashMap;
    use parking_lot::Mutex;
    use std::fs;
    use std::io::{ErrorKind, Read, Write};
    use std::os::unix::fs::PermissionsExt;
    use std::os::unix::net::{UnixListener, UnixStream};
    use std::path::{Path, PathBuf};
    use std::sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    };
    use std::thread::{self, JoinHandle};
    use std::time::Duration;
    use tracing::warn;
    use uuid::Uuid;

    const ACCEPT_POLL_INTERVAL: Duration = Duration::from_millis(20);

    pub(crate) struct ZshExecBridgeSession {
        socket_path: PathBuf,
        stop: Arc<AtomicBool>,
        worker: Mutex<Option<JoinHandle<()>>>,
    }

    impl ZshExecBridgeSession {
        pub(crate) fn spawn(allow_confirmed_dangerous: bool) -> Result<Self> {
            let socket_path = std::env::temp_dir()
                .join(format!("vtcode-zsh-exec-bridge-{}.sock", Uuid::new_v4()));

            if socket_path.exists() {
                fs::remove_file(&socket_path).with_context(|| {
                    format!(
                        "remove pre-existing zsh bridge socket at {}",
                        socket_path.display()
                    )
                })?;
            }

            let listener = UnixListener::bind(&socket_path).with_context(|| {
                format!(
                    "bind zsh exec bridge socket listener at {}",
                    socket_path.display()
                )
            })?;
            // Restrict socket to owner-only — prevents other users on the same
            // machine from communicating with the bridge (defence in depth;
            // the random UUID path already provides unpredictability).
            fs::set_permissions(&socket_path, std::fs::Permissions::from_mode(0o700))
                .with_context(|| {
                    format!(
                        "set permissions on zsh exec bridge socket at {}",
                        socket_path.display()
                    )
                })?;
            listener
                .set_nonblocking(true)
                .context("set zsh exec bridge listener to nonblocking")?;

            let stop = Arc::new(AtomicBool::new(false));
            let stop_clone = Arc::clone(&stop);
            let cleanup_path = socket_path.clone();
            let worker = thread::Builder::new()
                .name("vtcode-zsh-exec-bridge".to_string())
                .spawn(move || {
                    run_bridge_loop(listener, stop_clone, allow_confirmed_dangerous);
                    let _ = fs::remove_file(&cleanup_path);
                })
                .context("spawn zsh exec bridge listener thread")?;

            Ok(Self {
                socket_path,
                stop,
                worker: Mutex::new(Some(worker)),
            })
        }

        pub(crate) fn env_vars(&self, wrapper_executable: &Path) -> HashMap<String, String> {
            HashMap::from([
                (
                    ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR.to_string(),
                    self.socket_path.to_string_lossy().to_string(),
                ),
                (ZSH_EXEC_WRAPPER_MODE_ENV_VAR.to_string(), "1".to_string()),
                (
                    EXEC_WRAPPER_ENV_VAR.to_string(),
                    wrapper_executable.to_string_lossy().to_string(),
                ),
            ])
        }
    }

    impl Drop for ZshExecBridgeSession {
        fn drop(&mut self) {
            self.stop.store(true, Ordering::Relaxed);
            if let Some(worker) = self.worker.lock().take()
                && worker.join().is_err()
            {
                warn!("zsh exec bridge worker thread panicked during cleanup");
            }
            let _ = fs::remove_file(&self.socket_path);
        }
    }

    fn run_bridge_loop(
        listener: UnixListener,
        stop: Arc<AtomicBool>,
        allow_confirmed_dangerous: bool,
    ) {
        while !stop.load(Ordering::Relaxed) {
            match listener.accept() {
                Ok((mut stream, _)) => {
                    if let Err(err) = handle_wrapper_request(&mut stream, allow_confirmed_dangerous)
                    {
                        warn!(error = %err, "zsh exec bridge request failed");
                    }
                }
                Err(err) if err.kind() == ErrorKind::WouldBlock => {
                    thread::sleep(ACCEPT_POLL_INTERVAL);
                }
                Err(err) => {
                    warn!(error = %err, "zsh exec bridge listener failed");
                    break;
                }
            }
        }
    }

    fn handle_wrapper_request(
        stream: &mut UnixStream,
        allow_confirmed_dangerous: bool,
    ) -> Result<()> {
        let mut payload = String::new();
        stream
            .read_to_string(&mut payload)
            .context("read wrapper request payload")?;
        let request: WrapperExecRequest =
            serde_json::from_str(payload.trim()).context("parse wrapper request payload")?;

        let (action, reason) = evaluate_wrapper_exec_request(&request, allow_confirmed_dangerous);
        let response = WrapperExecResponse {
            request_id: request.request_id.clone(),
            action,
            reason,
        };
        let encoded = serde_json::to_string(&response).context("serialize wrapper response")?;
        stream
            .write_all(encoded.as_bytes())
            .context("write wrapper response payload")?;
        stream
            .write_all(b"\n")
            .context("write wrapper response newline")?;
        stream.flush().context("flush wrapper response")?;
        Ok(())
    }

    fn evaluate_wrapper_exec_request(
        request: &WrapperExecRequest,
        allow_confirmed_dangerous: bool,
    ) -> (WrapperExecAction, Option<String>) {
        let command = if request.argv.is_empty() {
            vec![request.file.clone()]
        } else {
            request.argv.clone()
        };

        if command.is_empty() {
            return (
                WrapperExecAction::Deny,
                Some("Rejected empty wrapped command".to_string()),
            );
        }

        if allow_confirmed_dangerous {
            return (WrapperExecAction::Allow, None);
        }

        let display = shell_words::join(command.iter().map(String::as_str));
        if let Err(err) = crate::tools::validation::commands::validate_command_safety(&display) {
            return (
                WrapperExecAction::Deny,
                Some(format!("Rejected by command safety validation: {err}")),
            );
        }
        if crate::command_safety::command_might_be_dangerous(&command) {
            return (
                WrapperExecAction::Deny,
                Some("Rejected dangerous subcommand".to_string()),
            );
        }

        (WrapperExecAction::Allow, None)
    }

    pub(crate) fn maybe_run_zsh_exec_wrapper_mode() -> Result<bool> {
        let wrapper_mode = std::env::var(ZSH_EXEC_WRAPPER_MODE_ENV_VAR).ok();
        if wrapper_mode.as_deref() != Some("1") {
            return Ok(false);
        }

        run_zsh_exec_wrapper_mode()?;
        Ok(true)
    }

    fn run_zsh_exec_wrapper_mode() -> Result<()> {
        let args: Vec<String> = std::env::args().collect();
        if args.len() < 2 {
            bail!("zsh exec wrapper mode requires target executable path");
        }

        let file = args[1].clone();
        let argv = if args.len() > 2 {
            args[2..].to_vec()
        } else {
            vec![file.clone()]
        };
        let cwd = std::env::current_dir()
            .context("resolve wrapper cwd")?
            .to_string_lossy()
            .to_string();
        let socket_path = std::env::var(ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR)
            .context("missing wrapper socket path env var")?;

        let request_id = Uuid::new_v4().to_string();
        let request = WrapperExecRequest {
            request_id: request_id.clone(),
            file: file.clone(),
            argv: argv.clone(),
            cwd,
        };

        let mut stream = UnixStream::connect(&socket_path)
            .with_context(|| format!("connect to wrapper socket at {socket_path}"))?;
        let encoded = serde_json::to_string(&request).context("serialize wrapper request")?;
        stream
            .write_all(encoded.as_bytes())
            .context("write wrapper request payload")?;
        stream
            .write_all(b"\n")
            .context("write wrapper request newline")?;
        stream
            .shutdown(std::net::Shutdown::Write)
            .context("shutdown wrapper request writer")?;

        let mut response_buf = String::new();
        stream
            .read_to_string(&mut response_buf)
            .context("read wrapper response payload")?;
        let response: WrapperExecResponse =
            serde_json::from_str(response_buf.trim()).context("parse wrapper response payload")?;

        if response.request_id != request_id {
            bail!(
                "wrapper response request_id mismatch: expected {request_id}, got {}",
                response.request_id
            );
        }

        if response.action == WrapperExecAction::Deny {
            if let Some(reason) = response.reason {
                warn!("zsh exec bridge denied execution: {reason}");
            } else {
                warn!("zsh exec bridge denied execution");
            }
            std::process::exit(1);
        }

        let mut command = std::process::Command::new(&file);
        if argv.len() > 1 {
            command.args(&argv[1..]);
        }
        command.env_remove(ZSH_EXEC_WRAPPER_MODE_ENV_VAR);
        command.env_remove(ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR);
        command.env_remove(EXEC_WRAPPER_ENV_VAR);
        let status = command.status().context("spawn wrapped executable")?;
        std::process::exit(status.code().unwrap_or(1));
    }

    #[cfg(test)]
    mod tests {
        use super::*;

        fn request(command: &[&str]) -> WrapperExecRequest {
            let file = command.first().unwrap_or(&"/usr/bin/true").to_string();
            WrapperExecRequest {
                request_id: "test-request".to_string(),
                file: file.clone(),
                argv: command.iter().map(|s| s.to_string()).collect(),
                cwd: "/tmp".to_string(),
            }
        }

        #[test]
        fn evaluate_request_denies_dangerous_when_unconfirmed() {
            let request = request(&["rm", "-rf", "/tmp/demo"]);
            let (action, reason) = evaluate_wrapper_exec_request(&request, false);
            assert_eq!(action, WrapperExecAction::Deny);
            assert!(reason.is_some());
        }

        #[test]
        fn evaluate_request_allows_safe_when_unconfirmed() {
            let request = request(&["/usr/bin/true"]);
            let (action, reason) = evaluate_wrapper_exec_request(&request, false);
            assert_eq!(action, WrapperExecAction::Allow);
            assert!(reason.is_none());
        }

        #[test]
        fn evaluate_request_allows_dangerous_when_confirmed() {
            let request = request(&["rm", "-rf", "/tmp/demo"]);
            let (action, reason) = evaluate_wrapper_exec_request(&request, true);
            assert_eq!(action, WrapperExecAction::Allow);
            assert!(reason.is_none());
        }
    }
}

#[cfg(unix)]
pub(crate) use unix_impl::ZshExecBridgeSession;

#[cfg(unix)]
pub fn maybe_run_zsh_exec_wrapper_mode() -> Result<bool> {
    unix_impl::maybe_run_zsh_exec_wrapper_mode()
}

#[cfg(not(unix))]
pub(crate) struct ZshExecBridgeSession;

#[cfg(not(unix))]
impl ZshExecBridgeSession {
    pub(crate) fn spawn(_allow_confirmed_dangerous: bool) -> Result<Self> {
        Err(anyhow!(
            "zsh exec bridge is only supported on Unix platforms"
        ))
    }

    pub(crate) fn env_vars(&self, _wrapper_executable: &Path) -> HashMap<String, String> {
        HashMap::new()
    }
}

#[cfg(not(unix))]
pub fn maybe_run_zsh_exec_wrapper_mode() -> Result<bool> {
    Ok(false)
}
