use portable_pty::{Child, CommandBuilder, NativePtySystem, PtySize, PtySystem};
use std::io::{Read, Write};
use std::sync::mpsc::{self, Receiver};
use std::time::Duration;
use vt100::Parser;

const DEFAULT_ROWS: u16 = 80;
const DEFAULT_COLS: u16 = 120;

pub struct PtySession {
    parser: Parser,
    writer: Option<Box<dyn Write + Send>>,
    rx: Receiver<Vec<u8>>,
    cols: u16,
    #[allow(dead_code)]
    child: Box<dyn Child + Send + Sync>,
}

impl PtySession {
    pub fn spawn_claude(args: &[&str]) -> Self {
        Self::spawn_claude_with_size(args, DEFAULT_ROWS, DEFAULT_COLS)
    }

    pub fn spawn_claude_with_size(args: &[&str], rows: u16, cols: u16) -> Self {
        let pty_system = NativePtySystem::default();

        let pair = pty_system
            .openpty(PtySize {
                rows,
                cols,
                pixel_width: 0,
                pixel_height: 0,
            })
            .expect("failed to open pty");

        let mut cmd = CommandBuilder::new("claude");
        for arg in args {
            cmd.arg(*arg);
        }
        if let Some(home) = dirs::home_dir() {
            cmd.cwd(home);
        }
        let child = pair
            .slave
            .spawn_command(cmd)
            .expect("failed to spawn claude");

        drop(pair.slave);

        let writer = pair.master.take_writer().expect("failed to get writer");
        let reader = pair
            .master
            .try_clone_reader()
            .expect("failed to get reader");

        let (tx, rx) = mpsc::channel::<Vec<u8>>();
        std::thread::spawn(move || {
            let mut reader = reader;
            let mut chunk = [0u8; 4096];
            loop {
                match reader.read(&mut chunk) {
                    Ok(0) => break,
                    Ok(n) => {
                        if tx.send(chunk[..n].to_vec()).is_err() {
                            break;
                        }
                    }
                    Err(_) => break,
                }
            }
        });

        Self {
            parser: Parser::new(rows, cols, 0),
            writer: Some(writer),
            rx,
            cols,
            child,
        }
    }

    pub fn grab_screen(&mut self) -> String {
        while let Ok(data) = self.rx.try_recv() {
            self.parser.process(&data);
        }
        let deadline = std::time::Instant::now() + Duration::from_millis(200);
        while std::time::Instant::now() < deadline {
            match self.rx.recv_timeout(Duration::from_millis(50)) {
                Ok(data) => self.parser.process(&data),
                Err(_) => {}
            }
        }

        let screen = self.parser.screen();
        let mut output = String::new();
        for line in screen.rows(0, self.cols) {
            output.push_str(&line);
            output.push('\n');
        }
        output
    }

    pub fn wait_for(&mut self, needle: &str, timeout: Duration) -> bool {
        self.wait_for_any(&[needle], timeout)
    }

    pub fn wait_for_any(&mut self, needles: &[&str], timeout: Duration) -> bool {
        let deadline = std::time::Instant::now() + timeout;
        while std::time::Instant::now() < deadline {
            match self.rx.recv_timeout(Duration::from_millis(100)) {
                Ok(data) => self.parser.process(&data),
                Err(_) => {}
            }
            let screen = self.parser.screen();
            let mut content = String::new();
            for line in screen.rows(0, self.cols) {
                content.push_str(&line);
                content.push('\n');
            }
            for needle in needles {
                if content.contains(needle) {
                    return true;
                }
            }
        }
        false
    }

    pub fn is_alive(&mut self) -> bool {
        matches!(self.child.try_wait(), Ok(None))
    }

    pub fn send(&mut self, input: &[u8]) {
        self.writer.as_mut().expect("writer gone").write_all(input).expect("failed to write to pty");
    }

    pub fn try_send(&mut self, input: &[u8]) -> std::io::Result<()> {
        match self.writer.as_mut() {
            Some(w) => w.write_all(input),
            None => Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, "writer gone")),
        }
    }

    pub fn try_send_line(&mut self, text: &str) -> std::io::Result<()> {
        self.try_send(text.as_bytes())?;
        self.try_send(b"\r")
    }

    pub fn send_enter(&mut self) {
        self.send(b"\r");
    }

    pub fn send_line(&mut self, text: &str) {
        self.send(text.as_bytes());
        self.send_enter();
    }
}
