use anyhow::Result;
use regex::Regex;
use std::io::Write;
use std::process::Command;
use tempfile::NamedTempFile;
use tracing::{debug, error};

/// Expand tilde (~) in a path to the user's home directory
fn expand_tilde(path: &str) -> String {
    if path.starts_with("~") {
        if let Some(home) = std::env::var_os("HOME") {
            let home_str = home.to_string_lossy();
            return path.replacen("~", &home_str, 1);
        }
    }
    path.to_string()
}

pub struct CodeExecutor {
    // Future: add configuration for execution limits, sandboxing, etc.
}

#[derive(Debug, Clone)]
pub struct ExecutionResult {
    pub stdout: String,
    pub stderr: String,
    pub exit_code: i32,
    pub success: bool,
}

impl CodeExecutor {
    pub fn new() -> Self {
        Self {}
    }

    /// Extract code blocks from LLM response and execute them
    pub async fn execute_from_response(&self, response: &str) -> Result<String> {
        self.execute_from_response_with_options(response, true)
            .await
    }

    /// Extract code blocks from LLM response and execute them with UI options
    pub async fn execute_from_response_with_options(
        &self,
        response: &str,
        show_code: bool,
    ) -> Result<String> {
        debug!(
            "CodeExecutor received response ({} chars): {}",
            response.len(),
            response
        );
        let code_blocks = self.extract_code_blocks(response)?;

        if code_blocks.is_empty() {
            if show_code {
                return Ok(format!(
                    "⚠️  No executable code blocks found in response.\n\n{}",
                    response
                ));
            } else {
                return Ok("⚠️  No executable code found.".to_string());
            }
        }

        let mut results = Vec::new();

        // Only show the original LLM response if show_code is true
        if show_code {
            results.push(response.to_string());
            results.push("\n🚀 Executing code...\n".to_string());
        }

        for (language, code) in code_blocks {
            debug!("Executing {} code", language);

            if show_code {
                results.push(format!("📋 Running {} code:", language));
            }

            match self.execute_code(&language, &code).await {
                Ok(result) => {
                    if result.success {
                        if show_code {
                            results.push("✅ Success".to_string());
                        }
                        // Always show stdout if there is any, regardless of show_code
                        if !result.stdout.is_empty() {
                            results.push(result.stdout.trim().to_string());
                        }
                    } else {
                        results.push("❌ Failed".to_string());
                        if !result.stderr.is_empty() {
                            results.push(format!("Error: {}", result.stderr.trim()));
                        }
                    }
                }
                Err(e) => {
                    error!("Failed to execute {} code: {}", language, e);
                    results.push(format!("❌ Execution failed: {}", e));
                }
            }
        }

        // If no results were added (e.g., successful execution with no output),
        // return a simple success message when show_code is false
        if results.is_empty() && !show_code {
            Ok("✅ Done".to_string())
        } else {
            Ok(results.join("\n"))
        }
    }

    /// Extract code blocks from markdown-formatted text
    fn extract_code_blocks(&self, text: &str) -> Result<Vec<(String, String)>> {
        let mut blocks = Vec::new();

        debug!("Extracting code blocks from text: {}", text);

        // Pattern 1: Standard markdown format ```language\ncode```
        let markdown_re = Regex::new(r"(?s)```(\w+)?\n(.*?)```")?;
        for cap in markdown_re.captures_iter(text) {
            let language = cap
                .get(1)
                .map(|m| m.as_str().to_lowercase())
                .unwrap_or_else(|| "bash".to_string()); // Default to bash
            let code = cap.get(2).map(|m| m.as_str()).unwrap_or("").trim();

            debug!(
                "Found markdown code block - language: '{}', code: '{}'",
                language, code
            );

            if !code.is_empty() {
                blocks.push((language, code.to_string()));
            }
        }

        // Pattern 2: Bracket format [Language]code[/Language]
        let bracket_re = Regex::new(r"(?s)\[(\w+)\]\s*(.*?)\s*\[/(\w+)\]")?;
        for cap in bracket_re.captures_iter(text) {
            let open_lang = cap.get(1).map(|m| m.as_str()).unwrap_or("");
            let close_lang = cap.get(3).map(|m| m.as_str()).unwrap_or("");

            // Only match if opening and closing tags are the same (case insensitive)
            if open_lang.to_lowercase() == close_lang.to_lowercase() {
                let language = open_lang.to_lowercase();
                let code = cap.get(2).map(|m| m.as_str()).unwrap_or("").trim();

                debug!(
                    "Found bracket code block - language: '{}', code: '{}'",
                    language, code
                );

                if !code.is_empty() {
                    blocks.push((language, code.to_string()));
                }
            }
        }

        debug!("Total code blocks found: {}", blocks.len());
        Ok(blocks)
    }

    /// Execute code in the specified language
    pub async fn execute_code(&self, language: &str, code: &str) -> Result<ExecutionResult> {
        match language.to_lowercase().as_str() {
            "python" | "py" => self.execute_python(code).await,
            "bash" | "shell" | "sh" => self.execute_bash(code).await,
            "javascript" | "js" => self.execute_javascript(code).await,
            _ => {
                // Try to execute as bash by default
                debug!("Unknown language '{}', trying as bash", language);
                self.execute_bash(code).await
            }
        }
    }

    /// Execute Python code
    async fn execute_python(&self, code: &str) -> Result<ExecutionResult> {
        let mut temp_file = NamedTempFile::new()?;
        temp_file.write_all(code.as_bytes())?;
        let temp_path = temp_file.path();

        let output = Command::new("python3").arg(temp_path).output()?;

        Ok(ExecutionResult {
            stdout: String::from_utf8_lossy(&output.stdout).to_string(),
            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
            exit_code: output.status.code().unwrap_or(-1),
            success: output.status.success(),
        })
    }

    /// Execute Bash code
    async fn execute_bash(&self, code: &str) -> Result<ExecutionResult> {
        // Check if this is a detached/daemon command that should run independently
        let is_detached = code.trim_start().starts_with("setsid ")
            || code.trim_start().starts_with("nohup ")
            || code.contains(" disown")
            || (code.contains(" &") && (code.contains("nohup") || code.contains("setsid")));

        if is_detached {
            // For detached commands, just spawn and return immediately
            use std::process::Stdio;
            Command::new("bash")
                .arg("-c")
                .arg(code)
                .stdin(Stdio::null())
                .stdout(Stdio::null())
                .stderr(Stdio::null())
                .spawn()?;

            return Ok(ExecutionResult {
                stdout: "✅ Command launched in background (detached process)".to_string(),
                stderr: String::new(),
                exit_code: 0,
                success: true,
            });
        }

        let output = Command::new("bash").arg("-c").arg(code).output()?;

        Ok(ExecutionResult {
            stdout: String::from_utf8_lossy(&output.stdout).to_string(),
            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
            exit_code: output.status.code().unwrap_or(-1),
            success: output.status.success(),
        })
    }

    /// Execute JavaScript code (requires Node.js)
    async fn execute_javascript(&self, code: &str) -> Result<ExecutionResult> {
        let mut temp_file = NamedTempFile::new()?;
        temp_file.write_all(code.as_bytes())?;
        let temp_path = temp_file.path();

        let output = Command::new("node").arg(temp_path).output()?;

        Ok(ExecutionResult {
            stdout: String::from_utf8_lossy(&output.stdout).to_string(),
            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
            exit_code: output.status.code().unwrap_or(-1),
            success: output.status.success(),
        })
    }
}

impl Default for CodeExecutor {
    fn default() -> Self {
        Self::new()
    }
}

/// Trait for receiving streaming output from command execution
pub trait OutputReceiver: Send + Sync {
    /// Called when a new line of output is available
    fn on_output_line(&self, line: &str);
}

impl CodeExecutor {
    /// Execute bash command with streaming output
    pub async fn execute_bash_streaming<R: OutputReceiver>(
        &self,
        code: &str,
        receiver: &R,
    ) -> Result<ExecutionResult> {
        self.execute_bash_streaming_in_dir(code, receiver, None)
            .await
    }

    /// Execute bash command with streaming output in a specific directory
    pub async fn execute_bash_streaming_in_dir<R: OutputReceiver>(
        &self,
        code: &str,
        receiver: &R,
        working_dir: Option<&str>,
    ) -> Result<ExecutionResult> {
        use std::process::Stdio;
        use tokio::io::{AsyncBufReadExt, BufReader};
        use tokio::process::Command as TokioCommand;

        // CRITICAL DEBUG: Print to stderr so it's always visible
        debug!("========== execute_bash_streaming_in_dir START ==========");
        debug!("Code to execute: {}", code);
        debug!("Working directory parameter: {:?}", working_dir);
        debug!(
            "FULL DIAGNOSTIC: code='{}', working_dir={:?}",
            code, working_dir
        );

        if let Some(dir) = working_dir {
            debug!(
                "Working dir exists check: {}",
                std::path::Path::new(dir).exists()
            );
            debug!(
                "Working dir is_dir check: {}",
                std::path::Path::new(dir).is_dir()
            );
        }
        debug!(
            "Current process working directory: {:?}",
            std::env::current_dir()
        );

        // Check if this is a detached/daemon command that should run independently
        // Look for patterns like: setsid, nohup with &, or explicit backgrounding with disown
        let is_detached = code.trim_start().starts_with("setsid ")
            || code.trim_start().starts_with("nohup ")
            || code.contains(" disown")
            || (code.contains(" &") && (code.contains("nohup") || code.contains("setsid")));

        if is_detached {
            // For detached commands, just spawn and return immediately
            let mut cmd = TokioCommand::new("bash");
            cmd.arg("-c").arg(code);

            // Set working directory if provided
            if let Some(dir) = working_dir {
                let expanded_dir = expand_tilde(dir);
                cmd.current_dir(&expanded_dir);
            }

            cmd.spawn()?;

            // Don't wait for the process - it's meant to run independently
            return Ok(ExecutionResult {
                stdout: "✅ Command launched in background (detached process)".to_string(),
                stderr: String::new(),
                exit_code: 0,
                success: true,
            });
        }

        let mut cmd = TokioCommand::new("bash");
        cmd.arg("-c")
            .arg(code)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());

        // Set working directory if provided
        if let Some(dir) = working_dir {
            debug!("Setting current_dir on command to: {}", dir);
            let expanded_dir = expand_tilde(dir);
            debug!("Expanded working dir: {}", expanded_dir);
            debug!(
                "Expanded dir exists: {}",
                std::path::Path::new(&expanded_dir).exists()
            );
            debug!(
                "Expanded dir is_dir: {}",
                std::path::Path::new(&expanded_dir).is_dir()
            );
            cmd.current_dir(&expanded_dir);
        }

        debug!("About to spawn command...");
        let spawn_result = cmd.spawn();
        debug!("Spawn result: {:?}", spawn_result.is_ok());
        let mut child = match spawn_result {
            Ok(c) => c,
            Err(e) => {
                debug!("SPAWN ERROR: {:?}", e);
                return Err(e.into());
            }
        };
        debug!("Command spawned successfully");

        let stdout = child.stdout.take().unwrap();
        let stderr = child.stderr.take().unwrap();

        let stdout_reader = BufReader::new(stdout);
        let stderr_reader = BufReader::new(stderr);

        let mut stdout_lines = stdout_reader.lines();
        let mut stderr_lines = stderr_reader.lines();

        let mut stdout_output = Vec::new();
        let mut stderr_output = Vec::new();

        // Read output lines as they come
        loop {
            tokio::select! {
                line = stdout_lines.next_line() => {
                    match line {
                        Ok(Some(line)) => {
                            receiver.on_output_line(&line);
                            stdout_output.push(line);
                        }
                        Ok(None) => break, // EOF
                        Err(e) => {
                            error!("Error reading stdout: {}", e);
                            break;
                        }
                    }
                }
                line = stderr_lines.next_line() => {
                    match line {
                        Ok(Some(line)) => {
                            receiver.on_output_line(&line.to_string());
                            stderr_output.push(line);
                        }
                        Ok(None) => {}, // stderr EOF, continue
                        Err(e) => {
                            error!("Error reading stderr: {}", e);
                        }
                    }
                }
                else => break
            }
        }

        let status = child.wait().await?;

        let result = ExecutionResult {
            stdout: stdout_output.join("\n"),
            stderr: stderr_output.join("\n"),
            exit_code: status.code().unwrap_or(-1),
            success: status.success(),
        };

        debug!("========== execute_bash_streaming_in_dir END ==========");
        debug!("Exit code: {}", result.exit_code);
        debug!("Success: {}", result.success);
        debug!("Stdout length: {}", result.stdout.len());
        debug!("Stderr length: {}", result.stderr.len());
        if !result.stderr.is_empty() {
            debug!("Stderr content: {}", result.stderr);
        }

        Ok(result)
    }
}

/// Check if rustup component llvm-tools-preview is installed
pub fn is_llvm_tools_installed() -> Result<bool> {
    let output = Command::new("rustup")
        .args(&["component", "list", "--installed"])
        .output()?;

    let installed = String::from_utf8_lossy(&output.stdout)
        .lines()
        .any(|line| line.trim() == "llvm-tools-preview" || line.starts_with("llvm-tools"));

    Ok(installed)
}

/// Check if cargo-llvm-cov is installed
pub fn is_cargo_llvm_cov_installed() -> Result<bool> {
    let output = Command::new("cargo").args(&["--list"]).output()?;

    let installed = String::from_utf8_lossy(&output.stdout)
        .lines()
        .any(|line| line.trim().starts_with("llvm-cov"));

    Ok(installed)
}

/// Install llvm-tools-preview via rustup
pub fn install_llvm_tools() -> Result<()> {
    debug!("Installing llvm-tools-preview...");
    let output = Command::new("rustup")
        .args(&["component", "add", "llvm-tools-preview"])
        .output()?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("Failed to install llvm-tools-preview: {}", stderr);
    }

    debug!("✅ llvm-tools-preview installed successfully");
    Ok(())
}

/// Install cargo-llvm-cov via cargo install
pub fn install_cargo_llvm_cov() -> Result<()> {
    debug!("Installing cargo-llvm-cov... (this may take a few minutes)");
    let output = Command::new("cargo")
        .args(&["install", "cargo-llvm-cov"])
        .output()?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("Failed to install cargo-llvm-cov: {}", stderr);
    }

    debug!("✅ cargo-llvm-cov installed successfully");
    Ok(())
}

/// Ensure both llvm-tools-preview and cargo-llvm-cov are installed
/// Returns Ok(true) if tools were already installed, Ok(false) if they were installed by this function
pub fn ensure_coverage_tools_installed() -> Result<bool> {
    let mut already_installed = true;

    // Check and install llvm-tools-preview
    if !is_llvm_tools_installed()? {
        debug!("llvm-tools-preview not found, installing...");
        install_llvm_tools()?;
        already_installed = false;
    } else {
        debug!("✅ llvm-tools-preview is already installed");
    }

    // Check and install cargo-llvm-cov
    if !is_cargo_llvm_cov_installed()? {
        debug!("cargo-llvm-cov not found, installing...");
        install_cargo_llvm_cov()?;
        already_installed = false;
    } else {
        debug!("✅ cargo-llvm-cov is already installed");
    }

    Ok(already_installed)
}
