use color_eyre::{eyre::eyre, Result};
use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem};
use std::io::{Read, Write};

use std::sync::{Arc, Mutex};
use std::thread;
use vt100::Parser;

/// Represents a PTY session with a running command
pub struct PtySession {
    parser: Arc<Mutex<Parser>>,
    writer: Box<dyn Write + Send>,
    _reader_thread: Option<thread::JoinHandle<()>>,
    pub is_finished: Arc<Mutex<bool>>,
    pub exit_code: Arc<Mutex<Option<i32>>>,
}

impl PtySession {
    /// Spawn a new PTY session with the given command
    pub fn spawn(command: &str, args: &[&str], cols: u16, rows: u16) -> Result<Self> {
        let pty_system = NativePtySystem::default();

        // Use the current terminal size if cols/rows are 0
        let (actual_cols, actual_rows) = if cols == 0 || rows == 0 {
            crossterm::terminal::size().unwrap_or((80, 24))
        } else {
            (cols, rows)
        };

        // Create a new PTY with the specified size
        let pty_pair = pty_system
            .openpty(PtySize {
                rows: actual_rows,
                cols: actual_cols,
                pixel_width: 0,
                pixel_height: 0,
            })
            .map_err(|e| eyre!("{}", e))?;

        // Build the command
        let mut cmd = CommandBuilder::new(command);
        for arg in args {
            cmd.arg(arg);
        }

        // Spawn the child process
        let mut child = pty_pair
            .slave
            .spawn_command(cmd)
            .map_err(|e| eyre!("{}", e))?;

        // Get reader and writer
        let mut reader = pty_pair
            .master
            .try_clone_reader()
            .map_err(|e| eyre!("{}", e))?;
        let writer = pty_pair.master.take_writer().map_err(|e| eyre!("{}", e))?;

        // Create VT100 parser for terminal emulation
        let parser = Arc::new(Mutex::new(Parser::new(actual_rows, actual_cols, 0)));
        let parser_clone = Arc::clone(&parser);

        let is_finished = Arc::new(Mutex::new(false));
        let is_finished_clone = Arc::clone(&is_finished);

        let exit_code = Arc::new(Mutex::new(None));
        let exit_code_clone = Arc::clone(&exit_code);

        // Spawn a thread to read from PTY and feed to parser
        let reader_thread = thread::spawn(move || {
            let mut buf = [0u8; 8192];
            loop {
                match reader.read(&mut buf) {
                    Ok(0) => {
                        // EOF - process has exited
                        break;
                    }
                    Ok(n) => {
                        // Feed data to VT100 parser
                        // Keep the lock scope as small as possible
                        {
                            if let Ok(mut parser) = parser_clone.lock() {
                                parser.process(&buf[..n]);
                            }
                        }
                    }
                    Err(e) => {
                        eprintln!("Error reading from PTY: {}", e);
                        break;
                    }
                }
            }

            // Mark as finished
            if let Ok(mut finished) = is_finished_clone.lock() {
                *finished = true;
            }

            // Try to get exit status
            if let Ok(status) = child.wait() {
                if let Ok(mut code) = exit_code_clone.lock() {
                    *code = Some(status.exit_code() as i32);
                }
            }
        });

        Ok(Self {
            parser,
            writer,
            _reader_thread: Some(reader_thread),
            is_finished,
            exit_code,
        })
    }

    /// Send input to the PTY
    pub fn send_input(&mut self, data: &[u8]) -> Result<()> {
        self.writer.write_all(data)?;
        self.writer.flush()?;
        Ok(())
    }

    /// Get the current screen content
    pub fn get_screen(&self) -> Vec<String> {
        if let Ok(parser) = self.parser.lock() {
            let screen = parser.screen();
            // Get the screen content from the beginning of the scrollback history
            screen.rows(0, screen.size().1).collect()
        } else {
            Vec::new()
        }
    }

    /// Check if the process has finished
    pub fn is_finished(&self) -> bool {
        self.is_finished.lock().map(|f| *f).unwrap_or(false)
    }

    /// Get the exit code if the process has finished
    pub fn get_exit_code(&self) -> Option<i32> {
        self.exit_code.lock().ok().and_then(|c| *c)
    }
}
