/*
 * Copyright (c) 2025 Raphael Amorim
 *
 * This file is part of flash, which is licensed
 * under GNU General Public License v3.0.
 */

use std::collections::HashMap;
use std::io;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::Arc;

use crate::parser::Node;
use glob::glob;

/// Remote execution result
#[derive(Debug, Clone)]
pub struct RemoteOutput {
    /// Exit code
    pub exit_code: i32,
    /// Standard output
    pub stdout: String,
    /// Standard error
    pub stderr: String,
    /// Glob expanded file list (if any)
    pub glob_matches: Option<Vec<String>>,
}

/// Remote executor trait for distributed shell execution
pub trait RemoteExecutor: Send + Sync {
    /// Execute external command
    ///
    /// # Arguments
    /// * `command` - command name
    /// * `args` - arguments (may contain glob patterns)
    /// * `cwd` - current working directory
    /// * `env` - environment variables
    ///
    /// # Returns
    /// Command execution result
    fn execute(
        &self,
        command: &str,
        args: &[String],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput>;

    /// Execute an AST subtree remotely.
    fn execute_ast(
        &self,
        node: &Node,
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput>;

    /// Only expand glob patterns (don't execute command)
    ///
    /// # Arguments
    /// * `patterns` - glob patterns
    /// * `cwd` - current working directory
    ///
    /// # Returns
    /// Matched file list
    fn glob(&self, patterns: &[String], cwd: &str) -> io::Result<Vec<String>>;

    /// Read remote file (for source command)
    ///
    /// # Arguments
    /// * `path` - file path
    ///
    /// # Returns
    /// File content
    fn read_file(&self, path: &str) -> io::Result<String>;

    /// Process Substitution: `<(cmd)` or `>(cmd)`
    ///
    /// # Arguments
    /// * `direction` - "input" (<) or "output" (>)
    /// * `command` - command to execute
    /// * `cwd` - current working directory
    ///
    /// # Returns
    /// Temporary file/path
    fn process_subst(
        &self,
        direction: &str,
        command: &str,
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<String>;

    fn execute_pipeline(
        &self,
        commands: &[(&str, &[String])],
        operators: &[&str],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        let mut nodes = Vec::with_capacity(commands.len());
        for (cmd, args) in commands {
            nodes.push(Node::Command {
                name: (*cmd).to_string(),
                args: args.to_vec(),
                redirects: vec![],
            });
        }
        let pipeline = Node::Pipeline { commands: nodes };
        let _ = operators;
        self.execute_ast(&pipeline, cwd, env)
    }
}

/// Check if string contains glob patterns

// Helper to clean script command output
fn clean_script_output(output: &[u8]) -> String {
    let s = String::from_utf8_lossy(output).to_string();
    let lines: Vec<&str> = s
        .lines()
        .filter(|l| {
            !l.contains("Script started")
                && !l.contains("Script done")
                && !l.contains("Script process")
        })
        .collect();
    lines.join("\n")
}

fn contains_glob(s: &str) -> bool {
    s.contains('*') || s.contains('?') || s.contains('[')
}
pub struct LocalExecutor;

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

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

// Stub for LocalExecutor
impl RemoteExecutor for LocalExecutor {
    fn execute(
        &self,
        command: &str,
        args: &[String],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        // Auto-create PTY using script command
        use std::process::{Command, Stdio};

        // Build command string
        let cmd_str = if args.is_empty() {
            command.to_string()
        } else {
            format!("{} {}", command, args.join(" "))
        };

        // Run with script command to get PTY
        let mut full_env = env.clone();
        full_env.insert("TERM".to_string(), "xterm-256color".to_string());

        let output = Command::new("script")
            .args(["-q", "-c", &cmd_str, "/dev/null"])
            .current_dir(cwd)
            .envs(&full_env)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()?;

        // Clean up script output (remove header/footer)
        let stdout = clean_script_output(&output.stdout);

        Ok(RemoteOutput {
            exit_code: output.status.code().unwrap_or(0),
            stdout,
            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
            glob_matches: None,
        })
    }

    fn execute_ast(
        &self,
        node: &Node,
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        match node {
            Node::Command { name, args, .. } => self.execute(name, args, cwd, env),
            Node::Pipeline { commands } => {
                let mut seq: Vec<(String, Vec<String>)> = Vec::new();
                for n in commands {
                    if let Node::Command { name, args, .. } = n {
                        seq.push((name.clone(), args.clone()));
                    } else {
                        return Err(io::Error::other(
                            "LocalExecutor only supports command pipeline AST",
                        ));
                    }
                }
                let refs: Vec<(&str, &[String])> = seq
                    .iter()
                    .map(|(c, a)| (c.as_str(), a.as_slice()))
                    .collect();
                self.execute_pipeline(&refs, &vec!["|"; refs.len().saturating_sub(1)], cwd, env)
            }
            _ => Err(io::Error::other(
                "LocalExecutor only supports command/pipeline AST",
            )),
        }
    }
    fn glob(&self, patterns: &[String], cwd: &str) -> io::Result<Vec<String>> {
        Ok(vec![])
    }
    fn read_file(&self, path: &str) -> io::Result<String> {
        std::fs::read_to_string(path)
    }
    fn process_subst(
        &self,
        direction: &str,
        command: &str,
        cwd: &str,
        _env: &HashMap<String, String>,
    ) -> io::Result<String> {
        Ok(format!("/tmp/{}", std::process::id()))
    }
    fn execute_pipeline(
        &self,
        commands: &[(&str, &[String])],
        operators: &[&str],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        let mut s = String::new();
        for (i, (c, a)) in commands.iter().enumerate() {
            if i > 0 {
                s.push_str(operators.get(i - 1).unwrap_or(&"|"));
            }
            s.push_str(c);
            for x in *a {
                s.push(' ');
                s.push_str(x);
            }
        }
        let o = std::process::Command::new("sh")
            .args(["-c", &s])
            .current_dir(cwd)
            .envs(env)
            .output()?;
        Ok(RemoteOutput {
            exit_code: o.status.code().unwrap_or(0),
            stdout: String::from_utf8_lossy(&o.stdout).to_string(),
            stderr: String::from_utf8_lossy(&o.stderr).to_string(),
            glob_matches: None,
        })
    }
}

pub struct HttpExecutor {
    host: String,
    port: u16,
}

// Stub for LocalExecutor
impl HttpExecutor {
    fn post(&self, path: &str, body: &serde_json::Value) -> io::Result<serde_json::Value> {
        use std::io::{Read, Write};
        use std::net::TcpStream;

        let mut stream = TcpStream::connect(format!("{}:{}", self.host, self.port))?;

        let body_str =
            serde_json::to_string(body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

        let request = format!(
            "POST {} HTTP/1.1\r\n\
             Host: {}:{}\r\n\
             Content-Type: application/json\r\n\
             Content-Length: {}\r\n\
             Connection: close\r\n\
             \r\n\
             {}",
            path,
            self.host,
            self.port,
            body_str.len(),
            body_str
        );

        stream.write_all(request.as_bytes())?;

        let mut response = String::new();
        stream.read_to_string(&mut response)?;

        // Parse HTTP response
        let body_start = response
            .find("\r\n\r\n")
            .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid HTTP response"))?;

        let response_body = &response[body_start + 4..];

        // Check HTTP status
        if let Some(status_line) = response.lines().next() {
            if !status_line.contains("200") && !status_line.contains("OK") {
                return Err(io::Error::new(
                    io::ErrorKind::Other,
                    format!("HTTP error: {}", status_line),
                ));
            }
        }

        serde_json::from_str(response_body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
    }

    pub fn new(host: &str, port: u16) -> Self {
        Self {
            host: host.to_string(),
            port,
        }
    }
    pub fn from_url(url: &str) -> io::Result<Self> {
        let u = url::Url::parse(url).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
        let host = u
            .host_str()
            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "no host"))?
            .to_string();
        let port = u.port().unwrap_or(80);
        Ok(Self { host, port })
    }
}

impl RemoteExecutor for HttpExecutor {
    fn execute(
        &self,
        command: &str,
        args: &[String],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        let mut cmd = command.to_string();
        for a in args {
            cmd.push(' ');
            cmd.push_str(a);
        }
        eprintln!("DEBUG: Sending pipe request");
        let request = serde_json::json!({
            "command": cmd,
            "cwd": cwd,
            "env": env,
        });
        let resp = self.post("/execute", &request)?;
        Ok(RemoteOutput {
            exit_code: resp["exit_code"].as_i64().unwrap_or(0) as i32,
            stdout: resp["stdout"].as_str().unwrap_or("").to_string(),
            stderr: resp["stderr"].as_str().unwrap_or("").to_string(),
            glob_matches: None,
        })
    }
    fn execute_ast(
        &self,
        node: &Node,
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        match node {
            Node::Command { name, args, .. } => self.execute(name, args, cwd, env),
            Node::Pipeline { commands } => {
                let mut seq: Vec<(String, Vec<String>)> = Vec::new();
                for n in commands {
                    if let Node::Command { name, args, .. } = n {
                        seq.push((name.clone(), args.clone()));
                    } else {
                        return Err(io::Error::other(
                            "HttpExecutor only supports command pipeline AST",
                        ));
                    }
                }
                let refs: Vec<(&str, &[String])> = seq
                    .iter()
                    .map(|(c, a)| (c.as_str(), a.as_slice()))
                    .collect();
                self.execute_pipeline(&refs, &vec!["|"; refs.len().saturating_sub(1)], cwd, env)
            }
            _ => Err(io::Error::other(
                "HttpExecutor only supports command/pipeline AST",
            )),
        }
    }
    fn glob(&self, patterns: &[String], cwd: &str) -> io::Result<Vec<String>> {
        eprintln!("DEBUG: Sending pipe request");
        let request = serde_json::json!({ "patterns": patterns, "cwd": cwd });
        let resp = self.post("/glob", &request)?;
        Ok(resp["matches"]
            .as_array()
            .unwrap()
            .iter()
            .filter_map(|v| v.as_str().map(String::from))
            .collect())
    }
    fn read_file(&self, path: &str) -> io::Result<String> {
        eprintln!("DEBUG: Sending pipe request");
        let request = serde_json::json!({ "path": path });
        let resp = self.post("/read_file", &request)?;
        Ok(resp["content"].as_str().unwrap_or("").to_string())
    }
    fn process_subst(
        &self,
        direction: &str,
        command: &str,
        cwd: &str,
        _env: &HashMap<String, String>,
    ) -> io::Result<String> {
        eprintln!("DEBUG: Sending pipe request");
        let request = serde_json::json!({ "direction": direction, "command": command, "cwd": cwd });
        let resp = self.post("/process_subst", &request)?;
        Ok(resp["path"].as_str().unwrap_or("/tmp/x").to_string())
    }
    fn execute_pipeline(
        &self,
        commands: &[(&str, &[String])],
        operators: &[&str],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        let mut cmd = String::new();
        for (i, (c, a)) in commands.iter().enumerate() {
            if i > 0 {
                cmd.push_str(operators.get(i - 1).unwrap_or(&"|"));
            }
            cmd.push_str(c);
            for x in *a {
                cmd.push(' ');
                cmd.push_str(x);
            }
        }
        self.execute(&cmd, &[], cwd, env)
    }
}
/// WebSocket remote executor with persistent connection
/// Supports pipelines and streaming
pub struct WsExecutor {
    host: String,
    port: u16,
    /// Keep-alive connection for multiple commands
    stream: Option<std::net::TcpStream>,
    session_id: String,
}

impl WsExecutor {
    pub fn new(host: &str, port: u16) -> Self {
        Self {
            host: host.to_string(),
            port,
            stream: None,
            session_id: format!("sess_{}", std::process::id()),
        }
    }

    /// Ensure connected, reconnect if needed
    fn ensure_connected(&mut self) -> io::Result<&mut std::net::TcpStream> {
        if self.stream.is_none() {
            self.stream = Some(self.connect()?);
        }
        // Set timeout for non-blocking read
        if let Some(ref mut s) = self.stream {
            s.set_read_timeout(Some(std::time::Duration::from_millis(1000)))?;
        }
        Ok(self.stream.as_mut().unwrap())
    }

    /// Connect to WebSocket server and perform handshake
    fn connect(&self) -> io::Result<std::net::TcpStream> {
        use std::io::{Read, Write};

        let mut stream = std::net::TcpStream::connect(format!("{}:{}", self.host, self.port))?;

        // WebSocket handshake
        let key = base64_encode(&rand_bytes());
        let request = format!(
            "GET /ws HTTP/1.1\r\n\
             Host: {}:{}\r\n\
             Upgrade: websocket\r\n\
             Connection: Upgrade\r\n\
             Sec-WebSocket-Key: {}\r\n\
             Sec-WebSocket-Version: 13\r\n\
             \r\n",
            self.host, self.port, key
        );

        stream.write_all(request.as_bytes())?;

        // Read response
        let mut buf = [0u8; 1024];
        let n = stream.read(&mut buf)?;
        let response = String::from_utf8_lossy(&buf[..n]);

        if !response.contains("101 Switching") {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                "WebSocket handshake failed",
            ));
        }

        Ok(stream)
    }

    /// Send WebSocket text frame
    fn send_frame(&self, stream: &mut std::net::TcpStream, msg: &str) -> io::Result<()> {
        use std::io::Write;

        let data = msg.as_bytes();
        let len = data.len();

        // Text frame (opcode 0x81)
        if len <= 125 {
            let header = [0x81, len as u8];
            stream.write_all(&header)?;
            stream.write_all(data)?;
            stream.flush()?;
        } else if len <= 65535 {
            let header = [0x81, 126, (len >> 8) as u8, (len & 0xff) as u8];
            stream.write_all(&header)?;
            stream.write_all(data)?;
            stream.flush()?;
        } else {
            // Extended 8-byte length (payload > 65535 bytes)
            let header = [
                0x81,
                127,
                (len >> 56) as u8,
                (len >> 48) as u8,
                (len >> 40) as u8,
                (len >> 32) as u8,
                (len >> 24) as u8,
                (len >> 16) as u8,
                (len >> 8) as u8,
                (len) as u8,
            ];
            stream.write_all(&header)?;
            stream.write_all(data)?;
            stream.flush()?;
        }

        stream.write_all(data)?;
        Ok(())
    }

    /// Receive WebSocket frame (simplified - text frames only)
    fn recv_frame(&self, stream: &mut std::net::TcpStream) -> io::Result<Option<String>> {
        use std::io::Read;

        let mut header = [0u8; 2];
        eprintln!("DEBUG recv_frame2: header={:?}", header);
        stream.read_exact(&mut header).map_err(|e| {
            if e.kind() == std::io::ErrorKind::WouldBlock {
                std::io::Error::new(std::io::ErrorKind::WouldBlock, "would block")
            } else {
                e
            }
        })?;

        let fin = (header[0] >> 7) & 1;
        let opcode = header[0] & 0xf;

        if opcode == 0x8 {
            // Close frame
            return Ok(None);
        }

        // Server doesn't use masking, skip mask check
        // let masked = (header[1] >> 7) & 1;
        let masked = 0; // Server frames are not masked
        let mut length = (header[1] & 0x7f) as usize;

        // Read extended length
        if length == 126 {
            let mut ext = [0u8; 2];
            stream.read_exact(&mut ext)?;
            length = ((ext[0] as usize) << 8) | (ext[1] as usize);
        } else if length == 127 {
            // Read 8 bytes for extended length (big-endian)
            let mut ext = [0u8; 8];
            stream.read_exact(&mut ext)?;
            length = ((ext[0] as usize) << 56)
                | ((ext[1] as usize) << 48)
                | ((ext[2] as usize) << 40)
                | ((ext[3] as usize) << 32)
                | ((ext[4] as usize) << 24)
                | ((ext[5] as usize) << 16)
                | ((ext[6] as usize) << 8)
                | (ext[7] as usize);
        }

        // Read mask key if present
        let mut mask_key = [0u8; 4];
        if masked != 0 {
            stream.read_exact(&mut mask_key)?;
        }

        // Read payload
        let mut payload = vec![0u8; length];
        stream.read_exact(&mut payload)?;

        // Unmask if needed
        if masked != 0 {
            for (i, b) in payload.iter_mut().enumerate() {
                *b ^= mask_key[i % 4];
            }
        }

        Ok(Some(String::from_utf8_lossy(&payload).to_string()))
    }
}

impl RemoteExecutor for WsExecutor {
    fn execute(
        &self,
        command: &str,
        args: &[String],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        // This is the sync version - for pipelines, use execute_with_connection
        // For now, we create a temporary connection
        let mut stream = self.connect()?;
        stream.set_read_timeout(Some(std::time::Duration::from_millis(1000)))?;

        // Send request
        eprintln!("DEBUG: Sending pipe request");
        let request = serde_json::json!({
            "type": "execute",
            "command": command,
            "args": args,
            "cwd": cwd,
            "env": env,
            "session_id": self.session_id,
        });

        self.send_frame(&mut stream, &request.to_string())?;

        // Receive responses
        let mut stdout = String::new();
        let mut stderr = String::new();
        let mut exit_code = 0;

        loop {
            eprintln!("DEBUG loop start");
            eprintln!("DEBUG: waiting for response...");
            eprintln!("DEBUG: calling recv_frame");
            match self.recv_frame(&mut stream) {
                Ok(Some(msg)) => {
                    // Parse JSON response
                    if let Ok(resp) = serde_json::from_str::<serde_json::Value>(&msg) {
                        if let Some(s) = resp.get("stdout").and_then(|v| v.as_str()) {
                            print!("{}", s);
                            stdout.push_str(s);
                        }
                        if let Some(s) = resp.get("stderr").and_then(|v| v.as_str()) {
                            eprint!("{}", s);
                            stderr.push_str(s);
                        }
                        if let Some(code) = resp.get("exit_code").and_then(|v| v.as_i64()) {
                            exit_code = code as i32;
                            break;
                        }
                    }
                }
                Ok(None) => break,
                Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
                Err(_) => break,
            }
        }

        Ok(RemoteOutput {
            exit_code,
            stdout,
            stderr,
            glob_matches: None,
        })
    }
    fn execute_ast(
        &self,
        node: &Node,
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        match node {
            Node::Command { name, args, .. } => self.execute(name, args, cwd, env),
            Node::Pipeline { commands } => {
                let mut seq: Vec<(String, Vec<String>)> = Vec::new();
                for n in commands {
                    if let Node::Command { name, args, .. } = n {
                        seq.push((name.clone(), args.clone()));
                    } else {
                        return Err(io::Error::other(
                            "WsExecutor only supports command pipeline AST",
                        ));
                    }
                }
                let refs: Vec<(&str, &[String])> = seq
                    .iter()
                    .map(|(c, a)| (c.as_str(), a.as_slice()))
                    .collect();
                self.execute_pipeline(&refs, &vec!["|"; refs.len().saturating_sub(1)], cwd, env)
            }
            _ => Err(io::Error::other(
                "WsExecutor only supports command/pipeline AST",
            )),
        }
    }

    fn glob(&self, patterns: &[String], cwd: &str) -> io::Result<Vec<String>> {
        use std::io::{Read, Write};

        let mut stream = self.connect()?;

        eprintln!("DEBUG: Sending pipe request");
        let request = serde_json::json!({
            "type": "glob",
            "patterns": patterns,
            "cwd": cwd,
        });

        self.send_frame(&mut stream, &request.to_string())?;

        // Receive response
        if let Ok(Some(msg)) = self.recv_frame(&mut stream) {
            if let Ok(resp) = serde_json::from_str::<serde_json::Value>(&msg) {
                let matches: Vec<String> = resp["matches"]
                    .as_array()
                    .map(|arr| {
                        arr.iter()
                            .filter_map(|v| v.as_str().map(String::from))
                            .collect()
                    })
                    .unwrap_or_default();
                return Ok(matches);
            }
        }

        Ok(vec![])
    }

    fn read_file(&self, path: &str) -> io::Result<String> {
        use std::io::{Read, Write};

        let mut stream = self.connect()?;

        eprintln!("DEBUG: Sending pipe request");
        let request = serde_json::json!({
            "type": "read_file",
            "path": path,
        });

        self.send_frame(&mut stream, &request.to_string())?;

        if let Ok(Some(msg)) = self.recv_frame(&mut stream) {
            if let Ok(resp) = serde_json::from_str::<serde_json::Value>(&msg) {
                return resp["content"]
                    .as_str()
                    .map(String::from)
                    .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "no content"));
            }
        }

        Err(io::Error::new(io::ErrorKind::Other, "read failed"))
    }

    fn process_subst(
        &self,
        direction: &str,
        command: &str,
        cwd: &str,
        _env: &HashMap<String, String>,
    ) -> io::Result<String> {
        // Simplified - return temp path
        let temp_path = format!("/tmp/flash-ps-{}", std::process::id());
        Ok(temp_path)
    }

    fn execute_pipeline(
        &self,
        commands: &[(&str, &[String])],
        operators: &[&str],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        let mut cmd = String::new();
        for (i, (c, a)) in commands.iter().enumerate() {
            if i > 0 {
                cmd.push_str(operators.get(i - 1).unwrap_or(&"|"));
            }
            cmd.push_str(c);
            for x in *a {
                cmd.push(' ');
                cmd.push_str(x);
            }
        }
        self.execute(&cmd, &[], cwd, env)
    }
}

impl WsExecutor {
    /// Execute pipeline via single request
    pub fn execute_pipe(
        &self,
        commands: &[(&str, &[String])],
        cwd: &str,
    ) -> io::Result<RemoteOutput> {
        use std::io::{Read, Write};
        let mut stream = self.connect()?;
        stream.set_read_timeout(Some(std::time::Duration::from_millis(1000)))?;
        let cmds: Vec<serde_json::Value> = commands
            .iter()
            .map(|(cmd, args)| serde_json::json!({ "command": cmd, "args": args }))
            .collect();
        eprintln!("DEBUG: Sending pipe request");
        let request = serde_json::json!({ "type": "pipe", "commands": cmds, "cwd": cwd });
        self.send_frame(&mut stream, &request.to_string())?;
        let mut stdout = String::new();
        let mut exit_code = 0;
        loop {
            eprintln!("DEBUG loop start");
            eprintln!("DEBUG: waiting for response...");
            eprintln!("DEBUG: calling recv_frame");
            match self.recv_frame(&mut stream) {
                Ok(Some(msg)) => {
                    if let Ok(resp) = serde_json::from_str::<serde_json::Value>(&msg) {
                        if let Some(s) = resp.get("stdout").and_then(|v| v.as_str()) {
                            print!("{}", s);
                            stdout.push_str(s);
                        }
                        if let Some(code) = resp.get("exit_code").and_then(|v| v.as_i64()) {
                            exit_code = code as i32;
                            break;
                        }
                    }
                }
                _ => {
                    eprintln!("DEBUG recv_frame error or None");
                    break;
                }
            }
        }
        Ok(RemoteOutput {
            exit_code,
            stdout,
            stderr: String::new(),
            glob_matches: None,
        })
    }
}

// Simple base64 encoder (no external dependency)
fn base64_encode(data: &[u8]) -> String {
    const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    let mut result = String::new();
    for chunk in data.chunks(3) {
        let b0 = chunk[0] as usize;
        let b1 = chunk.get(1).copied().unwrap_or(0) as usize;
        let b2 = chunk.get(2).copied().unwrap_or(0) as usize;
        result.push(ALPHABET[b0 >> 2] as char);
        result.push(ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)] as char);
        if chunk.len() > 1 {
            result.push(ALPHABET[((b1 & 0x0f) << 2) | (b2 >> 6)] as char);
        } else {
            result.push('=');
        }
        if chunk.len() > 2 {
            result.push(ALPHABET[b2 & 0x3f] as char);
        } else {
            result.push('=');
        }
    }
    result
}

// Random bytes for WebSocket key
fn rand_bytes() -> [u8; 16] {
    use std::time::{SystemTime, UNIX_EPOCH};
    let seed = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos() as u64;
    let mut bytes = [0u8; 16];
    for (i, b) in bytes.iter_mut().enumerate() {
        *b = ((seed >> (i % 8)) ^ (seed >> (16 + i))) as u8 | 1;
    }
    bytes
}

/// Pipeline executor - supports multiple commands with pipes
/// Uses persistent WebSocket connection
pub struct WsPipelineExecutor {
    host: String,
    port: u16,
    stream: Option<std::net::TcpStream>,
    session_id: String,
}

impl WsPipelineExecutor {
    pub fn new(host: &str, port: u16) -> Self {
        Self {
            host: host.to_string(),
            port,
            stream: None,
            session_id: format!("pipe_{}", std::process::id()),
        }
    }

    fn connect(&mut self) -> io::Result<()> {
        use std::io::{Read, Write};

        let mut stream = std::net::TcpStream::connect(format!("{}:{}", self.host, self.port))?;

        let key = base64_encode(&rand_bytes());
        let request = format!(
            "GET /ws HTTP/1.1\r\nHost: {}:{}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {}\r\nSec-WebSocket-Version: 13\r\n\r\n",
            self.host, self.port, key
        );

        stream.write_all(request.as_bytes())?;
        let mut buf = [0u8; 1024];
        let n = stream.read(&mut buf)?;
        let response = String::from_utf8_lossy(&buf[..n]);

        if !response.contains("101 Switching") {
            return Err(io::Error::new(io::ErrorKind::Other, "WS handshake failed"));
        }

        stream.set_read_timeout(Some(std::time::Duration::from_millis(1000)))?;
        self.stream = Some(stream);
        Ok(())
    }

    fn send_frame(&mut self, msg: &str) -> io::Result<()> {
        use std::io::Write;
        let data = msg.as_bytes();
        let len = data.len();
        if len <= 125 {
            if let Some(ref mut s) = self.stream {
                s.write_all(&[0x81, len as u8])?;
                s.write_all(data)?;
            }
        }
        Ok(())
    }

    fn recv_frame(&mut self) -> io::Result<Option<String>> {
        use std::io::Read;
        let Some(ref mut stream) = self.stream else {
            return Ok(None);
        };
        let mut header = [0u8; 2];
        eprintln!("DEBUG recv_frame2: header={:?}", header);
        eprintln!("DEBUG recv_frame: reading header");
        stream.read_exact(&mut header)?;
        eprintln!("DEBUG recv_frame: header = {:?}", header);

        let length = (header[1] & 0x7f) as usize;
        if length == 127 {
            return Ok(None);
        }
        let mut payload = vec![0u8; length];
        stream.read_exact(&mut payload)?;
        Ok(Some(String::from_utf8_lossy(&payload).to_string()))
    }

    /// Execute pipeline: cmd1 | cmd2 | cmd3
    pub fn execute_pipeline(
        &mut self,
        commands: &[(&str, &[String])],
        cwd: &str,
    ) -> io::Result<RemoteOutput> {
        if commands.is_empty() {
            return Ok(RemoteOutput {
                exit_code: 0,
                stdout: String::new(),
                stderr: String::new(),
                glob_matches: None,
            });
        }

        self.connect()?;

        let mut final_stdout = String::new();
        let mut final_stderr = String::new();

        for (i, (cmd, args)) in commands.iter().enumerate() {
            let is_last = i == commands.len() - 1;
            let pipe_input = if i > 0 {
                Some(final_stdout.clone())
            } else {
                None
            };

            eprintln!("DEBUG: Sending pipe request");
            let request = serde_json::json!({
                "type": "execute",
                "command": cmd,
                "args": args,
                "cwd": cwd,
                "session_id": self.session_id,
                "pipe_from": pipe_input,
            });

            self.send_frame(&request.to_string())?;

            final_stdout.clear();
            final_stderr.clear();

            loop {
                eprintln!("DEBUG loop start");
                eprintln!("DEBUG: waiting for response...");
                eprintln!("DEBUG: calling recv_frame");
                match self.recv_frame() {
                    Ok(Some(msg)) => {
                        if let Ok(resp) = serde_json::from_str::<serde_json::Value>(&msg) {
                            if let Some(s) = resp.get("stdout").and_then(|v| v.as_str()) {
                                if is_last {
                                    print!("{}", s);
                                }
                                final_stdout.push_str(s);
                            }
                            if let Some(s) = resp.get("stderr").and_then(|v| v.as_str()) {
                                if is_last {
                                    eprint!("{}", s);
                                }
                                final_stderr.push_str(s);
                            }
                        }
                    }
                    _ => {
                        eprintln!("DEBUG recv_frame error or None");
                        break;
                    }
                }
            }
        }

        Ok(RemoteOutput {
            exit_code: 0,
            stdout: final_stdout,
            stderr: final_stderr,
            glob_matches: None,
        })
    }
}
