pub mod browser_tool;
pub mod handshake;
pub mod protocol;

pub use browser_tool::{BrowserPipeTool, CommandOutput};
pub use handshake::{perform_handshake, HandshakeResult};
pub use protocol::{
    supported_actions, Action, AgentMessage, BrowserMessage, SecurityFields, Timing,
};

use std::io::{BufRead, BufReader, Read, Write};
use std::sync::{mpsc, Mutex};
use std::time::Duration;

use thiserror::Error;

const MAX_MESSAGE_BYTES: usize = 1024 * 1024;

#[derive(Debug, Error)]
pub enum PipeError {
    #[error("io error: {0}")]
    Io(#[from] std::io::Error),
    #[error("json error: {0}")]
    Json(#[from] serde_json::Error),
    #[error("timeout while waiting for browser message")]
    Timeout,
    #[error("pipe closed")]
    PipeClosed,
    #[error("protocol error: {0}")]
    Protocol(String),
    #[error("unexpected message: {0}")]
    UnexpectedMessage(String),
    #[error("message too large: {0} bytes")]
    MessageTooLarge(usize),
    #[error(transparent)]
    Security(#[from] crate::security::SecurityError),
}

pub trait Transport: Send + Sync {
    fn send(&self, message: &AgentMessage) -> Result<(), PipeError>;
    fn recv_timeout(&self, timeout: Duration) -> Result<BrowserMessage, PipeError>;
}

pub struct StdioTransport {
    rx: Mutex<mpsc::Receiver<Result<BrowserMessage, PipeError>>>,
    writer: Mutex<Box<dyn Write + Send>>,
}

impl StdioTransport {
    pub fn new<R, W>(reader: R, writer: W) -> Self
    where
        R: Read + Send + 'static,
        W: Write + Send + 'static,
    {
        let (tx, rx) = mpsc::channel();

        std::thread::spawn(move || {
            let mut reader = BufReader::new(reader);

            loop {
                let mut line = String::new();
                match reader.read_line(&mut line) {
                    Ok(0) => break,
                    Ok(_) => {
                        let line = line.trim_end_matches(&['\r', '\n'][..]);
                        if line.is_empty() {
                            let _ = tx.send(Err(PipeError::Protocol(
                                "received empty JSON line".to_string(),
                            )));
                            continue;
                        }

                        if line.as_bytes().len() > MAX_MESSAGE_BYTES {
                            let _ = tx.send(Err(PipeError::MessageTooLarge(line.len())));
                            continue;
                        }

                        let parsed =
                            serde_json::from_str::<BrowserMessage>(line).map_err(PipeError::from);
                        let _ = tx.send(parsed);
                    }
                    Err(err) => {
                        let _ = tx.send(Err(PipeError::Io(err)));
                        break;
                    }
                }
            }
        });

        Self {
            rx: Mutex::new(rx),
            writer: Mutex::new(Box::new(writer)),
        }
    }
}

impl Transport for StdioTransport {
    fn send(&self, message: &AgentMessage) -> Result<(), PipeError> {
        let payload = serde_json::to_vec(message)?;
        if payload.len() > MAX_MESSAGE_BYTES {
            return Err(PipeError::MessageTooLarge(payload.len()));
        }

        let mut writer = self
            .writer
            .lock()
            .map_err(|_| PipeError::Protocol("writer lock poisoned".to_string()))?;
        writer.write_all(&payload)?;
        writer.write_all(b"\n")?;
        writer.flush()?;
        Ok(())
    }

    fn recv_timeout(&self, timeout: Duration) -> Result<BrowserMessage, PipeError> {
        let rx = self
            .rx
            .lock()
            .map_err(|_| PipeError::Protocol("receiver lock poisoned".to_string()))?;
        match rx.recv_timeout(timeout) {
            Ok(result) => result,
            Err(mpsc::RecvTimeoutError::Timeout) => Err(PipeError::Timeout),
            Err(mpsc::RecvTimeoutError::Disconnected) => Err(PipeError::PipeClosed),
        }
    }
}
