use std::io::{self, Stderr};
use std::time::Duration;

use color_eyre::eyre::Result;
use crossterm::{
    event::{
        DisableMouseCapture, EnableMouseCapture, Event as CrosstermEvent, EventStream, KeyEventKind,
    },
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
    ExecutableCommand,
};
use futures::StreamExt;
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;

use crate::event::Event;

/// Terminal wrapper that handles setup, teardown, and async event streaming
pub struct Tui {
    pub terminal: Terminal<CrosstermBackend<Stderr>>,
    event_rx: mpsc::UnboundedReceiver<Event>,
    event_tx: mpsc::UnboundedSender<Event>,
    task: Option<JoinHandle<()>>,
    tick_rate: Duration,
}

impl Tui {
    /// Create a new Tui instance
    pub fn new() -> Result<Self> {
        let backend = CrosstermBackend::new(io::stderr());
        let terminal = Terminal::new(backend)?;
        let (event_tx, event_rx) = mpsc::unbounded_channel();

        Ok(Self {
            terminal,
            event_rx,
            event_tx,
            task: None,
            tick_rate: Duration::from_millis(250),
        })
    }

    /// Set tick rate in milliseconds
    pub fn tick_rate(mut self, ms: u64) -> Self {
        self.tick_rate = Duration::from_millis(ms);
        self
    }

    /// Enter the TUI: enable raw mode, enter alternate screen, start event loop
    pub fn enter(&mut self) -> Result<()> {
        enable_raw_mode()?;
        io::stderr().execute(EnterAlternateScreen)?;
        io::stderr().execute(EnableMouseCapture)?;
        self.terminal.hide_cursor()?;
        self.terminal.clear()?;

        // Start the async event reader
        let tx = self.event_tx.clone();
        let tick_rate = self.tick_rate;

        self.task = Some(tokio::spawn(async move {
            let mut reader = EventStream::new();
            let mut tick_interval = tokio::time::interval(tick_rate);

            loop {
                let tick = tick_interval.tick();
                let crossterm_event = reader.next();

                tokio::select! {
                    _ = tick => {
                        let _ = tx.send(Event::Tick);
                    }
                    maybe_event = crossterm_event => {
                        match maybe_event {
                            Some(Ok(CrosstermEvent::Key(key))) => {
                                if key.kind == KeyEventKind::Press {
                                    let _ = tx.send(Event::Key(key));
                                }
                            }
                            Some(Ok(CrosstermEvent::Mouse(mouse))) => {
                                let _ = tx.send(Event::Mouse(mouse));
                            }
                            Some(Ok(CrosstermEvent::Resize(x, y))) => {
                                let _ = tx.send(Event::Resize(x, y));
                            }
                            Some(Err(_)) => {
                                // Event stream error — terminal fd broken, stop reading
                                break;
                            }
                            None => {
                                // Stream ended
                                break;
                            }
                            _ => {}
                        }
                    }
                }
            }
        }));

        Ok(())
    }

    /// Exit the TUI: restore terminal state
    pub fn exit(&mut self) -> Result<()> {
        if let Some(task) = self.task.take() {
            task.abort();
        }
        io::stderr().execute(LeaveAlternateScreen)?;
        io::stderr().execute(DisableMouseCapture)?;
        disable_raw_mode()?;
        self.terminal.show_cursor()?;
        Ok(())
    }

    /// Wait for the next event
    pub async fn next(&mut self) -> Option<Event> {
        self.event_rx.recv().await
    }

    /// Draw a frame
    pub fn draw<F>(&mut self, f: F) -> Result<()>
    where
        F: FnOnce(&mut ratatui::Frame),
    {
        self.terminal.draw(f)?;
        Ok(())
    }
}

impl Drop for Tui {
    fn drop(&mut self) {
        let _ = self.exit();
    }
}
