use std::{fs::File, io::BufReader, path::PathBuf};

use anyhow::Result;
use indexmap::IndexMap;
use serde_yaml::Value;

use crate::mprocs::{
  event::{AppEvent, CopyMove},
  keymap::Keymap,
  proc_log_config::LogConfig,
  yaml_val::{value_to_string, Val},
};
use crate::term::key::{Key, KeyCode, KeyMods};

#[derive(Debug)]
pub struct Settings {
  keymap_procs: IndexMap<Key, AppEvent>,
  keymap_term: IndexMap<Key, AppEvent>,
  keymap_copy: IndexMap<Key, AppEvent>,
  pub hide_keymap_window: bool,
  pub mouse_scroll_speed: usize,
  pub scrollback_len: usize,
  pub proc_list_width: usize,
  pub proc_list_title: String,
  pub on_all_finished: Option<AppEvent>,
  pub proc_log: Option<LogConfig>,
}

impl Default for Settings {
  fn default() -> Self {
    let mut settings = Self {
      keymap_procs: Default::default(),
      keymap_term: Default::default(),
      keymap_copy: Default::default(),
      hide_keymap_window: false,
      mouse_scroll_speed: 5,
      scrollback_len: 1000,
      proc_list_width: 30,
      proc_list_title: "Processes".to_string(),
      on_all_finished: None,
      proc_log: None,
    };
    settings.add_defaults();
    settings
  }
}

impl Settings {
  pub fn merge_from_xdg(&mut self) -> Result<()> {
    if let Some(path) = self.get_xdg_config_path() {
      match File::open(&path) {
        Ok(file) => {
          let reader = BufReader::new(file);
          let settings_value: Value = serde_yaml::from_reader(reader)?;
          let settings_val = Val::new(&settings_value)?;
          self.merge_value(settings_val)?;
        }
        Err(err) => match err.kind() {
          std::io::ErrorKind::NotFound => (),
          _ => return Err(err.into()),
        },
      }
    }

    Ok(())
  }

  fn get_xdg_config_path(&self) -> Option<std::path::PathBuf> {
    let mut buf = if let Ok(path) = std::env::var("XDG_CONFIG_HOME") {
      PathBuf::from(path)
    } else {
      self.get_xdg_config_dir()?
    };
    buf.push("mprocs/mprocs.yaml");

    Some(buf)
  }

  #[cfg(windows)]
  fn get_xdg_config_dir(&self) -> Option<PathBuf> {
    let path = PathBuf::from(std::env::var_os("APPDATA")?);
    Some(path)
  }

  #[cfg(not(windows))]
  fn get_xdg_config_dir(&self) -> Option<PathBuf> {
    use std::ffi::OsString;

    let mut path = PathBuf::from(
      std::env::var_os("HOME").unwrap_or_else(|| OsString::from("/")),
    );
    path.push(".config");
    Some(path)
  }

  pub fn merge_value(&mut self, val: Val) -> Result<()> {
    let obj = val.as_object()?;

    fn add_keys(
      into: &mut IndexMap<Key, AppEvent>,
      val: Option<&Val>,
    ) -> Result<()> {
      if let Some(keymap) = val {
        let mut keymap = keymap.as_object()?;

        if let Some(reset) = keymap.shift_remove(&Value::from("reset")) {
          if reset.as_bool()? {
            into.clear();
          }
        }

        for (key, event) in keymap {
          let key = Key::parse(value_to_string(&key)?.as_str())?;
          if event.raw().is_null() {
            into.shift_remove(&key);
          } else {
            let event: AppEvent = serde_yaml::from_value(event.raw().clone())?;
            into.insert(key, event);
          }
        }
      }
      Ok(())
    }
    add_keys(
      &mut self.keymap_procs,
      obj.get(&Value::from("keymap_procs")),
    )?;
    add_keys(&mut self.keymap_term, obj.get(&Value::from("keymap_term")))?;
    add_keys(&mut self.keymap_copy, obj.get(&Value::from("keymap_copy")))?;

    if let Some(hide_keymap_window) =
      obj.get(&Value::from("hide_keymap_window"))
    {
      self.hide_keymap_window = hide_keymap_window.as_bool()?;
    }

    if let Some(mouse_scroll_speed) =
      obj.get(&Value::from("mouse_scroll_speed"))
    {
      self.mouse_scroll_speed = mouse_scroll_speed.as_usize()?;
    }

    if let Some(scrollback) = obj.get(&Value::from("scrollback")) {
      self.scrollback_len = scrollback.as_usize()?;
    }

    if let Some(proc_list_title) = obj.get(&Value::from("proc_list_title")) {
      self.proc_list_title = proc_list_title.as_str()?.to_string();
    }

    if let Some(proc_list_width) = obj.get(&Value::from("proc_list_width")) {
      self.proc_list_width = proc_list_width.as_usize()?;
    }

    if let Some(on_all_finished) = obj.get(&Value::from("on_all_finished")) {
      self.on_all_finished =
        Some(serde_yaml::from_value(on_all_finished.raw().clone())?);
    }

    if let Some(proc_log) = obj.get(&Value::from("proc_log")) {
      self.proc_log =
        crate::mprocs::proc_log_config::parse_log_config(proc_log, |path| {
          Ok(PathBuf::from(path))
        })?;
    }

    Ok(())
  }

  pub fn add_defaults(&mut self) {
    let s = self;

    s.keymap_add_p(
      Key::new(KeyCode::Char('a'), KeyMods::CONTROL),
      AppEvent::ToggleFocus,
    );
    s.keymap_add_t(
      Key::new(KeyCode::Char('a'), KeyMods::CONTROL),
      AppEvent::ToggleFocus,
    );
    s.keymap_add_c(
      Key::new(KeyCode::Char('a'), KeyMods::CONTROL),
      AppEvent::ToggleFocus,
    );

    s.keymap_add_p(KeyCode::Char('q').into(), AppEvent::Quit);
    s.keymap_add_p(KeyCode::Char('Q').into(), AppEvent::ForceQuit);
    s.keymap_add_p(KeyCode::Char('p').into(), AppEvent::ShowCommandsMenu);
    s.keymap_add_p(Key::new(KeyCode::Down, KeyMods::NONE), AppEvent::NextProc);
    s.keymap_add_p(
      Key::new(KeyCode::Char('j'), KeyMods::NONE),
      AppEvent::NextProc,
    );
    s.keymap_add_p(Key::new(KeyCode::Up, KeyMods::NONE), AppEvent::PrevProc);
    s.keymap_add_p(
      Key::new(KeyCode::Char('k'), KeyMods::NONE),
      AppEvent::PrevProc,
    );
    s.keymap_add_p(
      Key::new(KeyCode::Char('s'), KeyMods::NONE),
      AppEvent::StartProc,
    );
    s.keymap_add_p(
      Key::new(KeyCode::Char('x'), KeyMods::NONE),
      AppEvent::TermProc,
    );
    s.keymap_add_p(
      Key::new(KeyCode::Char('X'), KeyMods::NONE),
      AppEvent::KillProc,
    );
    s.keymap_add_p(
      Key::new(KeyCode::Char('r'), KeyMods::NONE),
      AppEvent::RestartProc,
    );
    s.keymap_add_p(
      Key::new(KeyCode::Char('R'), KeyMods::NONE),
      AppEvent::ForceRestartProc,
    );
    s.keymap_add_p(
      Key::new(KeyCode::Char('e'), KeyMods::NONE),
      AppEvent::ShowRenameProc,
    );
    let ctrlc = Key::new(KeyCode::Char('c'), KeyMods::CONTROL);
    s.keymap_add_p(ctrlc, AppEvent::SendKey { key: ctrlc });
    s.keymap_add_p(
      Key::new(KeyCode::Char('a'), KeyMods::NONE),
      AppEvent::ShowAddProc,
    );
    s.keymap_add_p(
      Key::new(KeyCode::Char('C'), KeyMods::NONE),
      AppEvent::DuplicateProc,
    );
    s.keymap_add_p(
      Key::new(KeyCode::Char('d'), KeyMods::NONE),
      AppEvent::ShowRemoveProc,
    );

    // Scrolling in TERM and COPY modes
    for map in [&mut s.keymap_procs, &mut s.keymap_copy] {
      map.insert(
        Key::new(KeyCode::Char('y'), KeyMods::CONTROL),
        AppEvent::ScrollUpLines { n: 3 },
      );
      map.insert(
        Key::new(KeyCode::Char('e'), KeyMods::CONTROL),
        AppEvent::ScrollDownLines { n: 3 },
      );
      let ctrlu = Key::new(KeyCode::Char('u'), KeyMods::CONTROL);
      map.insert(ctrlu, AppEvent::ScrollUp);
      map.insert(Key::new(KeyCode::PageUp, KeyMods::NONE), AppEvent::ScrollUp);
      let ctrld = Key::new(KeyCode::Char('d'), KeyMods::CONTROL);
      map.insert(ctrld, AppEvent::ScrollDown);
      map.insert(
        Key::new(KeyCode::PageDown, KeyMods::NONE),
        AppEvent::ScrollDown,
      );
    }

    s.keymap_add_p(Key::new(KeyCode::Char('z'), KeyMods::NONE), AppEvent::Zoom);

    s.keymap_add_p(
      Key::new(KeyCode::Char('?'), KeyMods::NONE),
      AppEvent::ToggleKeymapWindow,
    );

    s.keymap_add_p(
      Key::new(KeyCode::Char('v'), KeyMods::NONE),
      AppEvent::CopyModeEnter,
    );

    for i in 0..8 {
      let char = char::from_digit(i + 1, 10).unwrap();
      s.keymap_add_p(
        Key::new(KeyCode::Char(char), KeyMods::ALT),
        AppEvent::SelectProc { index: i as usize },
      );
    }

    s.keymap_add_c(KeyCode::Esc.into(), AppEvent::CopyModeLeave);
    s.keymap_add_c(KeyCode::Char('v').into(), AppEvent::CopyModeEnd);
    s.keymap_add_c(KeyCode::Char('c').into(), AppEvent::CopyModeCopy);
    for code in [KeyCode::Up, KeyCode::Char('k')] {
      s.keymap_add_c(code.into(), AppEvent::CopyModeMove { dir: CopyMove::Up });
    }
    for code in [KeyCode::Right, KeyCode::Char('l')] {
      s.keymap_add_c(
        code.into(),
        AppEvent::CopyModeMove {
          dir: CopyMove::Right,
        },
      );
    }
    for code in [KeyCode::Down, KeyCode::Char('j')] {
      s.keymap_add_c(
        code.into(),
        AppEvent::CopyModeMove {
          dir: CopyMove::Down,
        },
      );
    }
    for code in [KeyCode::Left, KeyCode::Char('h')] {
      s.keymap_add_c(
        code.into(),
        AppEvent::CopyModeMove {
          dir: CopyMove::Left,
        },
      );
    }
  }

  fn keymap_add_p(&mut self, key: Key, event: AppEvent) {
    self.keymap_procs.insert(key, event);
  }

  fn keymap_add_t(&mut self, key: Key, event: AppEvent) {
    self.keymap_term.insert(key, event);
  }

  fn keymap_add_c(&mut self, key: Key, event: AppEvent) {
    self.keymap_copy.insert(key, event);
  }

  pub fn add_to_keymap(&self, keymap: &mut Keymap) -> Result<()> {
    for (key, event) in &self.keymap_procs {
      keymap.bind_p(*key, event.clone());
    }
    for (key, event) in &self.keymap_term {
      keymap.bind_t(*key, event.clone());
    }
    for (key, event) in &self.keymap_copy {
      keymap.bind_c(*key, event.clone());
    }

    Ok(())
  }
}
