use crate::{
  bus::{publish_system_event, subscribe_system_event, SystemEvent},
  cli::cli,
  node::node::NodeCommand,
  watcher::WatcherMode
};

use shared::core::print_error_chain;
use crossterm::event::{Event, EventStream, KeyCode, KeyEventKind, MouseButton, MouseEventKind};
use futures::StreamExt;
use memory_stats::memory_stats;
use std::io::{self, Write};
use tokio::{
  select,
  sync::mpsc,
  time::{Duration, interval},
};
use vlogger::*;

use super::{ WatcherBuilder, WatcherCommand };

use crate::bus::subscribe_watcher_event;
use crate::executor::*;
use crate::log;

use cli_renderer::{
  Renderer,
  InputMode,
  RenderCommand
};

#[allow(dead_code)]
pub struct Watcher {
  node_tx: mpsc::Sender<NodeCommand>,
  exec_tx: mpsc::Sender<ExecutorCommand>,
  cmd_buffer: String,
  cmd_history: Vec<String>,
  history_index: usize,
  handles: Vec<tokio::task::JoinHandle<()>>,
  event_stream: crossterm::event::EventStream,
  mode: WatcherMode,
  pub renderer: Renderer,
}

impl Watcher {
  pub fn new(
    node_tx: mpsc::Sender<NodeCommand>,
    exec_tx: mpsc::Sender<ExecutorCommand>,
    cmd_buffer: String,
    cmd_history: Vec<String>,
    history_index: usize,
    handles: Vec<tokio::task::JoinHandle<()>>,
    renderer: Renderer,
  ) -> Self {
    Self {
      node_tx,
      exec_tx,
      cmd_buffer,
      cmd_history,
      history_index,
      handles,
      renderer,
      mode: WatcherMode::Input,
      event_stream: EventStream::new(),
    }
  }

  fn init(&self) -> io::Result<()>{
    crossterm::execute!(
      std::io::stdout(),
      crossterm::event::EnableBracketedPaste,
      crossterm::event::EnableFocusChange,
      crossterm::event::EnableMouseCapture,
    )
  }

  async fn shutdown(&mut self) -> io::Result<()> {
    ratatui::restore();
    let handles = std::mem::take(&mut self.handles);
    for handle in handles {
      handle.await.unwrap()
    }
    crossterm::execute!(
      std::io::stdout(),
      crossterm::event::DisableBracketedPaste,
      crossterm::event::DisableFocusChange,
      crossterm::event::DisableMouseCapture
    )
  }

  pub fn handle_cmd(&mut self, cmd: WatcherCommand) {
    match cmd {
      WatcherCommand::Render(rend_cmd) => {
        self.renderer.apply(rend_cmd);
      }
      WatcherCommand::SetMode(mode) => {
        match &mode {
          WatcherMode::Input => {}
          WatcherMode::Select{content, title, ..} => {
            let rd_cmd = RenderCommand::SetMode(InputMode::PopUp(content.clone(), title.clone(), 0));
            self.renderer.apply(rd_cmd);
          }
        }
        self.mode = mode;
      }
    }
  }

  pub async fn run(&mut self) -> std::io::Result<()> {
    let mut ui_rx = subscribe_watcher_event();
    let mut render_interval = interval(Duration::from_millis(32));
    let mut terminal = ratatui::init();
    let mut system_rx = subscribe_system_event();

    self.init()?;

    loop {
      select! {
        poll_res = self.poll() => {
          match poll_res {
            Ok(event) => {
              self.renderer.set_area(terminal.get_frame().area());
              match self.handle_event(event).await {
                Ok(ret) => if !ret { self.exit(); break }
                Err(e) => log(msg!(ERROR, "{}", e)),
              }
            }
            Err(()) => { log(msg!(ERROR, "Failed to read from Stream")) }
          }
        }
        ui_event = ui_rx.recv() => {
          match ui_event {
            Ok(cmd) => {
              self.renderer.set_area(terminal.get_frame().area());
              self.handle_cmd(cmd);
            },
            Err(e) => {
              log(msg!(ERROR, "{}", e))
            }
          }
        }
        event_res = system_rx.recv() => {
          if let Ok(event) = event_res {
            match event {
              SystemEvent::Shutdown => {
                break ;
              }
              _ => {}
            }
          }
        }
        _ = render_interval.tick() => {
          self.renderer.set_area(terminal.get_frame().area());
          terminal.draw(|frame| self.renderer.draw(frame))?;
        }
      }
    }
    self.shutdown().await
  }

  pub fn build() -> WatcherBuilder {
    WatcherBuilder::new()
  }

  pub fn exec_tx(&self) -> mpsc::Sender<ExecutorCommand> {
    self.exec_tx.clone()
  }

  pub fn exit(&self) {}

  async fn handle_enter(&mut self) {
    match &self.mode {
      WatcherMode::Input => {
        if !self.cmd_buffer.is_empty() {
          let exec_event = cli(&self.cmd_buffer);
          let _ = self.exec_tx.send(exec_event).await;
          self.cmd_buffer.clear();
          self.renderer.handle_enter()
        }
      }
      WatcherMode::Select { content, callback, index, .. } => {
        match &&**callback {
          &ExecutorCommand::Node(nd_cmd) => {
            match nd_cmd {
              NodeCommand::DisplayBlockByKey(_) => {
                let key = (*content)[*index].clone().to_string();
                let resp = ExecutorCommand::Node(NodeCommand::DisplayBlockByKey(key));
                let _ = self.exec_tx.send(resp).await;
              }
                _ => {log(msg!(DEBUG, "TODO: Implement callback for {:?}", nd_cmd))}
            }
          }
          _ => {log(msg!(DEBUG, "TODO: Implement callback for {:?}", *callback))}
        }
        self.mode = WatcherMode::Input;
        let rd_cmd = RenderCommand::SetMode(InputMode::Input);
        self.renderer.apply(rd_cmd);
      }
    }
  }

  fn handle_arrow_key(&mut self, key: KeyCode) {
    match key {
      KeyCode::Up => {
        match &mut self.mode {
          &mut WatcherMode::Select { ref mut index, .. } => {
            *index = index.saturating_sub(1);
          }
          _ => {}
        }
      }
      KeyCode::Down => {
        match &mut self.mode {
          &mut WatcherMode::Select { ref mut index, ref content, ..} => {
            if *index < content.len().saturating_sub(1) {
              *index = index.saturating_add(1);
            }
          }
          _ => {}
        }
      }
      _ => {}
    }
  }

  pub async fn log_memory() {
    tokio::spawn(async move {
      let id = format!("{}_{}", current_timestamp(), std::process::id());
      let mut path = std::path::PathBuf::new();
      path.push("./proc/");
      path.push(id);
      let mut mem_map = std::fs::OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open(path)
        .unwrap();
      loop {
        let _ = tokio::time::sleep(Duration::from_secs(10)).await;
        if let Some(usage) = memory_stats() {
          let current = current_timestamp();
          let _ = mem_map.write_all(
            msg!(
              INFO,
              "{}: Physical memory usage: {} MB",
              current,
              usage.physical_mem / 1024 / 1024
            )
            .as_bytes(),
          );
          let _ = mem_map.write_all(
            msg!(
              INFO,
              "{}: Virtual memory usage: {} MB",
              current,
              usage.virtual_mem / 1024 / 1024
            )
            .as_bytes(),
          );
        }
      }
    });
  }

  pub async fn handle_event(&mut self, event: Event) -> io::Result<bool> {
    match event {
      Event::Mouse(event) => match event.kind {
        MouseEventKind::ScrollUp => {
          self.renderer.handle_scroll_up();
        }
        MouseEventKind::ScrollDown => {
          self.renderer.handle_scroll_down();
        }
        MouseEventKind::Down(b) => match b {
          MouseButton::Left => {
            let rects = self.renderer.rects();
            self
              .renderer
              .handle_mouse_click_left(event.column, event.row, rects);
          }
          _ => {}
        },
        _ => {}
      },
      Event::Key(k) if k.kind == KeyEventKind::Press => match k.code {
        KeyCode::Esc => publish_system_event(SystemEvent::Shutdown),
        KeyCode::Char(c) => {
          self.cmd_buffer.push(c);
          self.renderer.handle_char_input(c)
        }
        KeyCode::Backspace => {
          self.cmd_buffer.pop();
          self.renderer.handle_backspace()
        }
        KeyCode::Enter => {
          self.handle_enter().await;
        }
        KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right => {
          self.handle_arrow_key(k.code);
          self.renderer.handle_arrow_key(k.code)
        }
        _ => {}
      },
      Event::Paste(text) => {
        log(msg!(DEBUG, "Received pasted text: {text}"));
        self.renderer.render_string_to_focused(text);
      }
      _ => {}
    }
    Ok(true)
  }

  pub async fn poll(&mut self) -> Result<Event, ()> {
    match self.event_stream.next().await {
      Some(Ok(event)) => Ok(event),
      Some(Err(e)) => Err(print_error_chain(&e.into())),
      None => Err(()),
    }
  }
}
