use anyhow::{Context, Result};
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use serde::Serialize;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpStream;
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use std::sync::{Arc, Mutex};
use std::time::Duration;

use super::common::{error_content, text_content, McpServer, McpTool};

/// Configuration for the browser server
pub struct BrowserServerConfig {
    /// Port for Chrome DevTools Protocol
    pub port: u16,
    /// User data directory for Chrome profile
    pub user_data_dir: PathBuf,
    /// Path to Chrome executable
    pub chrome_path: String,
    /// Page load timeout in seconds
    pub timeout: Duration,
    /// Directory for saving screenshots (stdio mode)
    pub screenshot_dir: PathBuf,
    /// Whether running in HTTP mode (affects screenshot output)
    pub http_mode: bool,
    /// Enable verbose logging
    pub verbose: bool,
}

impl BrowserServerConfig {
    pub fn new(port: u16, chrome_path: Option<String>, timeout: u64, verbose: bool) -> Self {
        // Create unique user data directory in /tmp
        let user_data_dir = PathBuf::from(format!("/tmp/mcpz/browser-{}", port));
        let screenshot_dir = PathBuf::from(format!("/tmp/mcpz/screenshots-{}", port));

        // Find Chrome path - use provided path or search common locations
        let chrome_path = chrome_path.unwrap_or_else(Self::find_chrome);

        Self {
            port,
            user_data_dir,
            chrome_path,
            timeout: Duration::from_secs(timeout),
            screenshot_dir,
            http_mode: false,
            verbose,
        }
    }

    /// Find Chrome executable by searching common installation paths
    fn find_chrome() -> String {
        let candidates: Vec<&str> = if cfg!(target_os = "macos") {
            vec![
                "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
                "/Applications/Chromium.app/Contents/MacOS/Chromium",
                "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
                "/usr/local/bin/google-chrome",
                "/usr/local/bin/chromium",
            ]
        } else if cfg!(target_os = "windows") {
            vec![
                r"C:\Program Files\Google\Chrome\Application\chrome.exe",
                r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
                r"C:\Users\%USERNAME%\AppData\Local\Google\Chrome\Application\chrome.exe",
                r"C:\Program Files\Chromium\Application\chrome.exe",
            ]
        } else {
            // Linux - check multiple common paths
            vec![
                "/usr/bin/google-chrome",
                "/usr/bin/google-chrome-stable",
                "/usr/bin/chromium",
                "/usr/bin/chromium-browser",
                "/snap/bin/chromium",
                "/usr/local/bin/google-chrome",
                "/usr/local/bin/chromium",
                "/opt/google/chrome/google-chrome",
                "/opt/google/chrome/chrome",
                "/opt/chromium/chromium",
            ]
        };

        // Return first existing path, or fallback to a reasonable default
        for candidate in &candidates {
            if std::path::Path::new(candidate).exists() {
                return candidate.to_string();
            }
        }

        // Fallback: try PATH lookup via `which` equivalent
        if cfg!(target_os = "macos") {
            "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome".to_string()
        } else if cfg!(target_os = "windows") {
            r"C:\Program Files\Google\Chrome\Application\chrome.exe".to_string()
        } else {
            // Linux: try common names that might be in PATH
            "google-chrome".to_string()
        }
    }

    /// Set HTTP mode (screenshots return base64 instead of file path)
    pub fn with_http_mode(mut self, http_mode: bool) -> Self {
        self.http_mode = http_mode;
        self
    }
}

/// Browser automation result
#[derive(Serialize)]
struct BrowserResult {
    success: bool,
    message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    data: Option<serde_json::Value>,
}

/// Screenshot result - different types for stdio vs HTTP mode
enum ScreenshotResult {
    /// For stdio mode: file path to saved screenshot
    FilePath { path: PathBuf, full_page: bool },
    /// For HTTP mode: base64 encoded image data
    Base64 { data: String, full_page: bool },
}

/// Chrome DevTools Protocol client
struct ChromeClient {
    stream: TcpStream,
    message_id: u32,
}

impl ChromeClient {
    fn connect(port: u16, timeout: Duration) -> Result<Self> {
        Self::connect_to_tab(port, timeout, None)
    }

    fn connect_to_tab(port: u16, timeout: Duration, target_url: Option<&str>) -> Result<Self> {
        // First, get the WebSocket URL from Chrome's HTTP endpoint
        let http_url = format!("http://localhost:{}/json", port);

        let client = reqwest::blocking::Client::builder()
            .timeout(timeout)
            .build()?;

        let response = client
            .get(&http_url)
            .send()
            .context("Failed to connect to Chrome DevTools")?;

        let tabs: Vec<serde_json::Value> = response.json().context("Failed to parse Chrome tabs")?;

        // If we have a target URL, try to find that tab first
        let tab = if let Some(target) = target_url {
            tabs.iter().find(|t| {
                t.get("url").and_then(|v| v.as_str()).map(|u| u.contains(target)).unwrap_or(false)
            })
        } else {
            None
        };

        // If no target URL match, find a "page" type tab (not extension background pages or offscreen documents)
        // Prefer tabs with actual URLs over chrome:// or extension pages
        let tab = tab.or_else(|| {
            tabs.iter().find(|t| {
                let tab_type = t.get("type").and_then(|v| v.as_str()).unwrap_or("");
                let url = t.get("url").and_then(|v| v.as_str()).unwrap_or("");
                tab_type == "page"
                    && !url.starts_with("chrome://")
                    && !url.starts_with("chrome-extension://")
                    && !url.contains("offscreen")
            })
        }).or_else(|| {
            // Fall back to any "page" type tab
            tabs.iter().find(|t| {
                t.get("type").and_then(|v| v.as_str()) == Some("page")
            })
        }).or_else(|| tabs.first()); // Last resort: first tab

        let ws_url = tab
            .and_then(|tab| tab.get("webSocketDebuggerUrl"))
            .and_then(|url| url.as_str())
            .ok_or_else(|| anyhow::anyhow!("No WebSocket URL found"))?;

        // Parse WebSocket URL to get host and path
        let ws_url = ws_url.trim_start_matches("ws://");
        let parts: Vec<&str> = ws_url.splitn(2, '/').collect();
        let host_port = parts[0];
        let path = format!("/{}", parts.get(1).unwrap_or(&""));

        // Connect via TCP
        let stream =
            TcpStream::connect(host_port).context("Failed to connect to Chrome WebSocket")?;
        stream.set_read_timeout(Some(timeout))?;
        stream.set_write_timeout(Some(timeout))?;

        let mut client = Self {
            stream,
            message_id: 0,
        };

        // Perform WebSocket handshake
        client.websocket_handshake(&host_port.to_string(), &path)?;

        Ok(client)
    }

    fn websocket_handshake(&mut self, host: &str, path: &str) -> Result<()> {
        let key = BASE64.encode(rand::random::<[u8; 16]>());

        let request = format!(
            "GET {} 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",
            path, host, key
        );

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

        // Read response
        let mut reader = BufReader::new(&self.stream);
        let mut response = String::new();

        loop {
            let mut line = String::new();
            reader.read_line(&mut line)?;
            if line == "\r\n" {
                break;
            }
            response.push_str(&line);
        }

        if !response.contains("101") {
            return Err(anyhow::anyhow!(
                "WebSocket handshake failed: {}",
                response
            ));
        }

        Ok(())
    }

    fn send_command(&mut self, method: &str, params: serde_json::Value) -> Result<serde_json::Value> {
        self.message_id += 1;
        let message = serde_json::json!({
            "id": self.message_id,
            "method": method,
            "params": params
        });

        let json = serde_json::to_string(&message)?;
        self.send_ws_message(&json)?;

        // Read response
        loop {
            let response = self.read_ws_message()?;
            let parsed: serde_json::Value = serde_json::from_str(&response)?;

            // Check if this is our response
            if parsed.get("id") == Some(&serde_json::json!(self.message_id)) {
                if let Some(error) = parsed.get("error") {
                    return Err(anyhow::anyhow!("CDP error: {}", error));
                }
                return Ok(parsed.get("result").cloned().unwrap_or(serde_json::json!({})));
            }
        }
    }

    fn send_ws_message(&mut self, message: &str) -> Result<()> {
        let payload = message.as_bytes();
        let len = payload.len();

        // Build WebSocket frame (text frame, masked)
        let mut frame = Vec::new();

        // Opcode 0x81 = final frame + text
        frame.push(0x81);

        // Length with mask bit set
        if len < 126 {
            frame.push((len as u8) | 0x80);
        } else if len < 65536 {
            frame.push(126 | 0x80);
            frame.push((len >> 8) as u8);
            frame.push((len & 0xff) as u8);
        } else {
            frame.push(127 | 0x80);
            for i in (0..8).rev() {
                frame.push((len >> (i * 8)) as u8);
            }
        }

        // Masking key
        let mask: [u8; 4] = rand::random();
        frame.extend_from_slice(&mask);

        // Masked payload
        for (i, byte) in payload.iter().enumerate() {
            frame.push(byte ^ mask[i % 4]);
        }

        self.stream.write_all(&frame)?;
        self.stream.flush()?;

        Ok(())
    }

    fn read_ws_message(&mut self) -> Result<String> {
        let mut header = [0u8; 2];
        std::io::Read::read_exact(&mut self.stream, &mut header)?;

        let _opcode = header[0] & 0x0f;
        let masked = (header[1] & 0x80) != 0;
        let mut len = (header[1] & 0x7f) as usize;

        if len == 126 {
            let mut extended = [0u8; 2];
            std::io::Read::read_exact(&mut self.stream, &mut extended)?;
            len = ((extended[0] as usize) << 8) | (extended[1] as usize);
        } else if len == 127 {
            let mut extended = [0u8; 8];
            std::io::Read::read_exact(&mut self.stream, &mut extended)?;
            len = 0;
            for byte in extended.iter() {
                len = (len << 8) | (*byte as usize);
            }
        }

        let mask = if masked {
            let mut m = [0u8; 4];
            std::io::Read::read_exact(&mut self.stream, &mut m)?;
            Some(m)
        } else {
            None
        };

        let mut payload = vec![0u8; len];
        std::io::Read::read_exact(&mut self.stream, &mut payload)?;

        if let Some(m) = mask {
            for (i, byte) in payload.iter_mut().enumerate() {
                *byte ^= m[i % 4];
            }
        }

        String::from_utf8(payload).context("Invalid UTF-8 in WebSocket message")
    }
}

/// Browser MCP server
pub struct BrowserServer {
    config: BrowserServerConfig,
    chrome_process: Arc<Mutex<Option<Child>>>,
    current_url: Arc<Mutex<Option<String>>>,
}

impl BrowserServer {
    pub fn new(config: BrowserServerConfig) -> Self {
        Self {
            config,
            chrome_process: Arc::new(Mutex::new(None)),
            current_url: Arc::new(Mutex::new(None)),
        }
    }

    /// Get a client connected to the current tab
    fn get_client(&self) -> Result<ChromeClient> {
        let url = self.current_url.lock().unwrap().clone();
        ChromeClient::connect_to_tab(self.config.port, self.config.timeout, url.as_deref())
            .context("Browser not running. Call start_browser first.")
    }

    fn start_browser(&self, headless: bool) -> Result<BrowserResult> {
        let mut process_guard = self.chrome_process.lock().unwrap();

        // Check if already running
        if let Some(ref mut child) = *process_guard {
            match child.try_wait() {
                Ok(None) => {
                    return Ok(BrowserResult {
                        success: true,
                        message: format!("Browser already running on port {}", self.config.port),
                        data: Some(serde_json::json!({
                            "port": self.config.port,
                            "user_data_dir": self.config.user_data_dir.display().to_string()
                        })),
                    });
                }
                _ => {}
            }
        }

        // Create user data directory
        std::fs::create_dir_all(&self.config.user_data_dir)?;

        self.log(&format!(
            "Starting Chrome on port {} with profile at {} (headless: {})",
            self.config.port,
            self.config.user_data_dir.display(),
            headless
        ));

        let mut cmd = Command::new(&self.config.chrome_path);
        cmd.arg(format!("--remote-debugging-port={}", self.config.port))
            .arg(format!(
                "--user-data-dir={}",
                self.config.user_data_dir.display()
            ))
            .arg(format!(
                "--remote-allow-origins=http://localhost:{}",
                self.config.port
            ))
            .arg("--no-first-run")
            .arg("--no-default-browser-check")
            .arg("--window-size=1920,1080")
            .arg("--window-position=0,0");

        if headless {
            // Use new headless mode (Chrome 112+)
            cmd.arg("--headless=new");
            cmd.arg("--disable-gpu");
        }

        let child = cmd
            .stdout(Stdio::null())
            .stderr(Stdio::null())
            .spawn()
            .context("Failed to start Chrome")?;

        *process_guard = Some(child);

        // Wait for Chrome to be ready
        std::thread::sleep(Duration::from_secs(2));

        // Verify connection and set viewport
        for attempt in 0..10 {
            match ChromeClient::connect(self.config.port, Duration::from_secs(5)) {
                Ok(mut client) => {
                    // Set viewport size via Emulation domain
                    let _ = client.send_command("Emulation.setDeviceMetricsOverride", serde_json::json!({
                        "width": 1920,
                        "height": 1080,
                        "deviceScaleFactor": 1,
                        "mobile": false
                    }));

                    return Ok(BrowserResult {
                        success: true,
                        message: format!(
                            "Browser started successfully on port {}",
                            self.config.port
                        ),
                        data: Some(serde_json::json!({
                            "port": self.config.port,
                            "user_data_dir": self.config.user_data_dir.display().to_string()
                        })),
                    });
                }
                Err(_) if attempt < 9 => {
                    std::thread::sleep(Duration::from_millis(500));
                }
                Err(e) => {
                    return Err(anyhow::anyhow!(
                        "Chrome started but DevTools not accessible: {}",
                        e
                    ));
                }
            }
        }

        Ok(BrowserResult {
            success: true,
            message: format!("Browser started on port {}", self.config.port),
            data: Some(serde_json::json!({
                "port": self.config.port,
                "user_data_dir": self.config.user_data_dir.display().to_string()
            })),
        })
    }

    fn open_url(&self, url: &str, wait_time: u64) -> Result<BrowserResult> {
        let mut client = self.get_client()?;

        self.log(&format!("Navigating to: {}", url));

        // Enable Page domain
        client.send_command("Page.enable", serde_json::json!({}))?;

        // Navigate to URL
        client.send_command("Page.navigate", serde_json::json!({ "url": url }))?;

        // Store the URL for future connections
        *self.current_url.lock().unwrap() = Some(url.to_string());

        // Wait for page to load
        std::thread::sleep(Duration::from_secs(wait_time));

        Ok(BrowserResult {
            success: true,
            message: format!("Navigated to {}", url),
            data: Some(serde_json::json!({ "url": url })),
        })
    }

    fn get_page(&self) -> Result<BrowserResult> {
        let mut client = self.get_client()?;

        // Enable DOM
        client.send_command("DOM.enable", serde_json::json!({}))?;

        // Get document
        let doc = client.send_command("DOM.getDocument", serde_json::json!({ "depth": -1 }))?;

        let node_id = doc
            .get("root")
            .and_then(|r| r.get("nodeId"))
            .ok_or_else(|| anyhow::anyhow!("Could not get document node"))?;

        // Get outer HTML
        let html = client.send_command("DOM.getOuterHTML", serde_json::json!({ "nodeId": node_id }))?;

        let outer_html = html
            .get("outerHTML")
            .and_then(|h| h.as_str())
            .unwrap_or("")
            .to_string();

        Ok(BrowserResult {
            success: true,
            message: "Page content retrieved".to_string(),
            data: Some(serde_json::json!({ "html": outer_html })),
        })
    }

    fn get_page_as_markdown(&self) -> Result<BrowserResult> {
        let page_result = self.get_page()?;

        let html = page_result
            .data
            .as_ref()
            .and_then(|d| d.get("html"))
            .and_then(|h| h.as_str())
            .unwrap_or("");

        // Convert HTML to markdown using a simple conversion
        let markdown = html_to_markdown(html);

        Ok(BrowserResult {
            success: true,
            message: "Page content converted to markdown".to_string(),
            data: Some(serde_json::json!({ "markdown": markdown })),
        })
    }

    fn get_screenshot(&self, full_page: bool) -> Result<ScreenshotResult> {
        let mut client = self.get_client()?;

        // Enable Page domain
        client.send_command("Page.enable", serde_json::json!({}))?;

        let mut params = serde_json::json!({
            "format": "png",
            "quality": 100
        });

        if full_page {
            // Get layout metrics for full page screenshot
            let metrics = client.send_command("Page.getLayoutMetrics", serde_json::json!({}))?;

            if let Some(content_size) = metrics.get("contentSize") {
                params["clip"] = serde_json::json!({
                    "x": 0,
                    "y": 0,
                    "width": content_size.get("width").and_then(|w| w.as_f64()).unwrap_or(1920.0),
                    "height": content_size.get("height").and_then(|h| h.as_f64()).unwrap_or(1080.0),
                    "scale": 1
                });
                params["captureBeyondViewport"] = serde_json::json!(true);
            }
        }

        let screenshot = client.send_command("Page.captureScreenshot", params)?;

        let base64_data = screenshot
            .get("data")
            .and_then(|d| d.as_str())
            .unwrap_or("")
            .to_string();

        if self.config.http_mode {
            // HTTP mode: return base64 data for MCP image content
            Ok(ScreenshotResult::Base64 {
                data: base64_data,
                full_page,
            })
        } else {
            // Stdio mode: save to file and return path
            std::fs::create_dir_all(&self.config.screenshot_dir)?;

            let timestamp = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_millis();

            let filename = format!(
                "screenshot_{}{}.png",
                timestamp,
                if full_page { "_full" } else { "" }
            );
            let filepath = self.config.screenshot_dir.join(&filename);

            // Decode base64 and write to file
            let png_data = BASE64.decode(&base64_data)
                .context("Failed to decode screenshot data")?;
            std::fs::write(&filepath, &png_data)
                .context("Failed to write screenshot file")?;

            self.log(&format!("Screenshot saved to: {}", filepath.display()));

            Ok(ScreenshotResult::FilePath {
                path: filepath,
                full_page,
            })
        }
    }

    fn execute_script(&self, script: &str) -> Result<BrowserResult> {
        let mut client = self.get_client()?;

        // Enable Runtime
        client.send_command("Runtime.enable", serde_json::json!({}))?;

        let result = client.send_command(
            "Runtime.evaluate",
            serde_json::json!({
                "expression": script,
                "returnByValue": true,
                "awaitPromise": true
            }),
        )?;

        // Check for exceptions
        if let Some(exception) = result.get("exceptionDetails") {
            let error_text = exception
                .get("exception")
                .and_then(|e| e.get("description"))
                .and_then(|d| d.as_str())
                .unwrap_or("Unknown error");

            return Ok(BrowserResult {
                success: false,
                message: format!("Script execution failed: {}", error_text),
                data: Some(serde_json::json!({ "error": error_text })),
            });
        }

        let value = result
            .get("result")
            .and_then(|r| r.get("value"))
            .cloned()
            .unwrap_or(serde_json::json!(null));

        Ok(BrowserResult {
            success: true,
            message: "Script executed successfully".to_string(),
            data: Some(serde_json::json!({ "result": value })),
        })
    }

    fn stop_browser(&self) -> Result<BrowserResult> {
        let mut process_guard = self.chrome_process.lock().unwrap();

        if let Some(ref mut child) = *process_guard {
            child.kill().ok();
            child.wait().ok();
            *process_guard = None;

            Ok(BrowserResult {
                success: true,
                message: "Browser stopped".to_string(),
                data: None,
            })
        } else {
            Ok(BrowserResult {
                success: true,
                message: "Browser was not running".to_string(),
                data: None,
            })
        }
    }
}

impl Drop for BrowserServer {
    fn drop(&mut self) {
        // Stop Chrome when server is dropped
        let mut process_guard = self.chrome_process.lock().unwrap();
        if let Some(ref mut child) = *process_guard {
            child.kill().ok();
            child.wait().ok();
        }
    }
}

impl McpServer for BrowserServer {
    fn name(&self) -> &str {
        "mcpz-browser"
    }

    fn version(&self) -> &str {
        env!("CARGO_PKG_VERSION")
    }

    fn verbose(&self) -> bool {
        self.config.verbose
    }

    fn tools(&self) -> Vec<McpTool> {
        vec![
            McpTool {
                name: "start_browser".to_string(),
                description: "Start a Chrome browser instance with remote debugging enabled"
                    .to_string(),
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {
                        "headless": {
                            "type": "boolean",
                            "description": "Run Chrome in headless mode (no visible window). Default: false",
                            "default": false
                        }
                    },
                    "required": []
                }),
            },
            McpTool {
                name: "stop_browser".to_string(),
                description: "Stop the running Chrome browser instance".to_string(),
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {},
                    "required": []
                }),
            },
            McpTool {
                name: "open_url".to_string(),
                description: "Navigate to a URL in the browser".to_string(),
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {
                        "url": {
                            "type": "string",
                            "description": "The URL to navigate to"
                        },
                        "wait_time": {
                            "type": "integer",
                            "description": "Seconds to wait after page load for dynamic content (default: 3)",
                            "default": 3
                        }
                    },
                    "required": ["url"]
                }),
            },
            McpTool {
                name: "get_page".to_string(),
                description: "Get the full HTML content of the current page".to_string(),
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {},
                    "required": []
                }),
            },
            McpTool {
                name: "get_page_as_markdown".to_string(),
                description: "Get the current page content converted to markdown".to_string(),
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {},
                    "required": []
                }),
            },
            McpTool {
                name: "get_screenshot".to_string(),
                description: "Capture a screenshot of the current page".to_string(),
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {
                        "full_page": {
                            "type": "boolean",
                            "description": "Capture the full page or just the viewport (default: false)",
                            "default": false
                        }
                    },
                    "required": []
                }),
            },
            McpTool {
                name: "execute_script".to_string(),
                description: "Execute JavaScript code in the browser context".to_string(),
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {
                        "script": {
                            "type": "string",
                            "description": "JavaScript code to execute"
                        }
                    },
                    "required": ["script"]
                }),
            },
        ]
    }

    fn call_tool(&self, name: &str, arguments: &serde_json::Value) -> Result<serde_json::Value> {
        let result = match name {
            "start_browser" => {
                let headless = arguments
                    .get("headless")
                    .and_then(|h| h.as_bool())
                    .unwrap_or(false);
                self.start_browser(headless)
            }
            "stop_browser" => self.stop_browser(),
            "open_url" => {
                let url = arguments
                    .get("url")
                    .and_then(|u| u.as_str())
                    .ok_or_else(|| anyhow::anyhow!("Missing url argument"))?;

                let wait_time = arguments
                    .get("wait_time")
                    .and_then(|w| w.as_u64())
                    .unwrap_or(3);

                self.open_url(url, wait_time)
            }
            "get_page" => self.get_page(),
            "get_page_as_markdown" => self.get_page_as_markdown(),
            "get_screenshot" => {
                let full_page = arguments
                    .get("full_page")
                    .and_then(|f| f.as_bool())
                    .unwrap_or(false);

                // Screenshot has special handling - return early
                return match self.get_screenshot(full_page) {
                    Ok(ScreenshotResult::FilePath { path, full_page }) => {
                        Ok(text_content(&serde_json::to_string_pretty(&serde_json::json!({
                            "success": true,
                            "message": "Screenshot saved to file",
                            "path": path.display().to_string(),
                            "full_page": full_page
                        }))?))
                    }
                    Ok(ScreenshotResult::Base64 { data, full_page }) => {
                        // Return MCP image content with text metadata
                        Ok(serde_json::json!({
                            "content": [
                                {
                                    "type": "text",
                                    "text": serde_json::json!({
                                        "success": true,
                                        "message": "Screenshot captured",
                                        "full_page": full_page
                                    }).to_string()
                                },
                                {
                                    "type": "image",
                                    "data": data,
                                    "mimeType": "image/png"
                                }
                            ]
                        }))
                    }
                    Err(e) => Ok(error_content(&e.to_string())),
                };
            }
            "execute_script" => {
                let script = arguments
                    .get("script")
                    .and_then(|s| s.as_str())
                    .ok_or_else(|| anyhow::anyhow!("Missing script argument"))?;

                self.execute_script(script)
            }
            _ => {
                return Ok(error_content(&format!("Unknown tool: {}", name)));
            }
        };

        match result {
            Ok(browser_result) => {
                let result_json = serde_json::to_string_pretty(&browser_result)?;
                if browser_result.success {
                    Ok(text_content(&result_json))
                } else {
                    Ok(error_content(&result_json))
                }
            }
            Err(e) => Ok(error_content(&e.to_string())),
        }
    }
}

/// Simple HTML to Markdown converter
fn html_to_markdown(html: &str) -> String {
    // Remove script and style tags
    let mut result = html.to_string();

    // Remove script tags
    while let Some(start) = result.find("<script") {
        if let Some(end) = result[start..].find("</script>") {
            result = format!("{}{}", &result[..start], &result[start + end + 9..]);
        } else {
            break;
        }
    }

    // Remove style tags
    while let Some(start) = result.find("<style") {
        if let Some(end) = result[start..].find("</style>") {
            result = format!("{}{}", &result[..start], &result[start + end + 8..]);
        } else {
            break;
        }
    }

    // Simple tag replacements
    let replacements = [
        ("<h1>", "# "),
        ("</h1>", "\n\n"),
        ("<h1 ", "# "),
        ("<h2>", "## "),
        ("</h2>", "\n\n"),
        ("<h2 ", "## "),
        ("<h3>", "### "),
        ("</h3>", "\n\n"),
        ("<h3 ", "### "),
        ("<h4>", "#### "),
        ("</h4>", "\n\n"),
        ("<h4 ", "#### "),
        ("<h5>", "##### "),
        ("</h5>", "\n\n"),
        ("<h5 ", "##### "),
        ("<h6>", "###### "),
        ("</h6>", "\n\n"),
        ("<h6 ", "###### "),
        ("<p>", "\n"),
        ("</p>", "\n\n"),
        ("<p ", "\n"),
        ("<br>", "\n"),
        ("<br/>", "\n"),
        ("<br />", "\n"),
        ("<li>", "- "),
        ("</li>", "\n"),
        ("<li ", "- "),
        ("<strong>", "**"),
        ("</strong>", "**"),
        ("<b>", "**"),
        ("</b>", "**"),
        ("<em>", "*"),
        ("</em>", "*"),
        ("<i>", "*"),
        ("</i>", "*"),
        ("<code>", "`"),
        ("</code>", "`"),
        ("<hr>", "\n---\n"),
        ("<hr/>", "\n---\n"),
        ("<hr />", "\n---\n"),
    ];

    for (from, to) in replacements {
        result = result.replace(from, to);
    }

    // Remove all remaining HTML tags
    let mut cleaned = String::new();
    let mut in_tag = false;
    let mut in_attr_value = false;

    for c in result.chars() {
        match c {
            '<' if !in_attr_value => in_tag = true,
            '>' if in_tag => {
                in_tag = false;
                in_attr_value = false;
            }
            '"' if in_tag => in_attr_value = !in_attr_value,
            _ if !in_tag => cleaned.push(c),
            _ => {}
        }
    }

    // Decode HTML entities
    let entities = [
        ("&amp;", "&"),
        ("&lt;", "<"),
        ("&gt;", ">"),
        ("&quot;", "\""),
        ("&apos;", "'"),
        ("&nbsp;", " "),
        ("&#39;", "'"),
        ("&#x27;", "'"),
    ];

    for (entity, char) in entities {
        cleaned = cleaned.replace(entity, char);
    }

    // Clean up whitespace
    let lines: Vec<&str> = cleaned.lines().map(|l| l.trim()).collect();
    let mut final_result = String::new();
    let mut prev_empty = false;

    for line in lines {
        if line.is_empty() {
            if !prev_empty {
                final_result.push('\n');
                prev_empty = true;
            }
        } else {
            final_result.push_str(line);
            final_result.push('\n');
            prev_empty = false;
        }
    }

    final_result.trim().to_string()
}

/// Run the browser MCP server
pub fn run_browser_server(config: BrowserServerConfig) -> Result<()> {
    if config.verbose {
        eprintln!("[mcpz] Browser server configuration:");
        eprintln!("[mcpz]   Chrome path: {}", config.chrome_path);
        eprintln!("[mcpz]   DevTools port: {}", config.port);
        eprintln!("[mcpz]   User data dir: {}", config.user_data_dir.display());
        eprintln!("[mcpz]   Screenshot dir: {}", config.screenshot_dir.display());
        eprintln!("[mcpz]   HTTP mode: {}", config.http_mode);
        eprintln!("[mcpz]   Timeout: {:?}", config.timeout);
    }

    let server = BrowserServer::new(config);
    server.run()
}

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

    #[test]
    fn test_browser_config_new() {
        let config = BrowserServerConfig::new(9222, None, 30, false);
        assert_eq!(config.port, 9222);
        assert_eq!(config.timeout, Duration::from_secs(30));
        assert!(!config.verbose);
        assert!(!config.http_mode);
        assert!(config.user_data_dir.to_string_lossy().contains("browser-9222"));
        assert!(config.screenshot_dir.to_string_lossy().contains("screenshots-9222"));
    }

    #[test]
    fn test_browser_config_custom_chrome() {
        let config = BrowserServerConfig::new(
            9222,
            Some("/usr/bin/chromium".to_string()),
            60,
            true,
        );
        assert_eq!(config.chrome_path, "/usr/bin/chromium");
        assert_eq!(config.timeout, Duration::from_secs(60));
        assert!(config.verbose);
    }

    #[test]
    fn test_browser_config_http_mode() {
        let config = BrowserServerConfig::new(9222, None, 30, false)
            .with_http_mode(true);
        assert!(config.http_mode);

        let config2 = BrowserServerConfig::new(9222, None, 30, false)
            .with_http_mode(false);
        assert!(!config2.http_mode);
    }

    #[test]
    fn test_browser_server_tools() {
        let config = BrowserServerConfig::new(9222, None, 30, false);
        let server = BrowserServer::new(config);
        let tools = server.tools();

        let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();

        assert!(tool_names.contains(&"start_browser"));
        assert!(tool_names.contains(&"stop_browser"));
        assert!(tool_names.contains(&"open_url"));
        assert!(tool_names.contains(&"get_page"));
        assert!(tool_names.contains(&"get_page_as_markdown"));
        assert!(tool_names.contains(&"get_screenshot"));
        assert!(tool_names.contains(&"execute_script"));
    }

    #[test]
    fn test_browser_server_initialize() {
        let config = BrowserServerConfig::new(9222, None, 30, false);
        let server = BrowserServer::new(config);
        let result = server.handle_initialize();

        assert_eq!(result["protocolVersion"], "2024-11-05");
        assert_eq!(result["serverInfo"]["name"], "mcpz-browser");
    }

    #[test]
    fn test_html_to_markdown_headings() {
        assert!(html_to_markdown("<h1>Title</h1>").contains("# Title"));
        assert!(html_to_markdown("<h2>Section</h2>").contains("## Section"));
        assert!(html_to_markdown("<h3>Subsection</h3>").contains("### Subsection"));
    }

    #[test]
    fn test_html_to_markdown_formatting() {
        assert!(html_to_markdown("<strong>bold</strong>").contains("**bold**"));
        assert!(html_to_markdown("<em>italic</em>").contains("*italic*"));
        assert!(html_to_markdown("<code>code</code>").contains("`code`"));
    }

    #[test]
    fn test_html_to_markdown_removes_script() {
        let html = "<p>Before</p><script>alert('test')</script><p>After</p>";
        let md = html_to_markdown(html);
        assert!(!md.contains("alert"));
        assert!(md.contains("Before"));
        assert!(md.contains("After"));
    }

    #[test]
    fn test_html_to_markdown_entities() {
        assert!(html_to_markdown("&amp;").contains("&"));
        assert!(html_to_markdown("&lt;").contains("<"));
        assert!(html_to_markdown("&gt;").contains(">"));
    }

    #[test]
    fn test_browser_result_serialization() {
        let result = BrowserResult {
            success: true,
            message: "Test".to_string(),
            data: Some(serde_json::json!({"key": "value"})),
        };
        let json = serde_json::to_string(&result).unwrap();
        assert!(json.contains("\"success\":true"));
        assert!(json.contains("\"message\":\"Test\""));
        assert!(json.contains("\"key\":\"value\""));
    }

    #[test]
    fn test_browser_result_without_data() {
        let result = BrowserResult {
            success: true,
            message: "Test".to_string(),
            data: None,
        };
        let json = serde_json::to_string(&result).unwrap();
        assert!(!json.contains("data"));
    }
}
