use std::{cell::RefMut, fmt::Debug, time};

use anyhow::bail;

use crate::{resource::Resource, VoidResult};

pub type TimerId = u64;
pub type TimerCallback = dyn FnMut() -> VoidResult + 'static;

struct TimerData {
  callback: Box<TimerCallback>,
  next_call_time: time::SystemTime,
  millis: u64,
  once: bool,
  id: TimerId,
}

// derive(Debug) didn't work because of `callback: Box<TimerCallback>`
impl Debug for TimerData {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "TimerData {{ {} }}", self.id)
  }
}

#[derive(Debug, Default)]
pub struct ScheduleState {
  id: TimerId,
  timers: Vec<TimerData>,
  to_be_destroyed: Vec<TimerId>,
}

impl ScheduleState {
  pub fn create(&mut self, callback: Box<TimerCallback>, millis: u64, once: bool) -> TimerId {
    let id = {
      self.id += 1;
      self.id
    };

    logger::debug!("creating timer with id: {id}");

    let next_call_time = time::SystemTime::now() + time::Duration::from_millis(millis);

    self.timers.push(TimerData {
      callback,
      next_call_time,
      millis,
      once,
      id,
    });

    id
  }

  pub fn add_to_be_destroyed(&mut self, id: TimerId) {
    self.to_be_destroyed.push(id);
  }
}

#[derive(Debug, Default)]
pub struct TimerManager {
  timers: Vec<TimerData>,
}

impl TimerManager {
  pub fn process_timers(&mut self, mut schedule: RefMut<ScheduleState>) {
    self.timers.append(&mut schedule.timers);

    if !schedule.to_be_destroyed.is_empty() {
      for idx in self.get_to_be_destroyed(&schedule) {
        self.timers.swap_remove(idx);
      }
      schedule.to_be_destroyed.clear();
    }

    drop(schedule); // unborrow ScheduleState

    let mut indexes_to_remove: Vec<usize> = vec![];
    let now = time::SystemTime::now();

    for (idx, timer) in self.timers.iter_mut().enumerate().rev() {
      if now >= timer.next_call_time {
        if let Err(error) = (timer.callback)() {
          logger::error!("timer callback failed with error: {error:?}");
        } else {
          logger::debug!("timer callback called successfully");
        }
        if timer.once {
          indexes_to_remove.push(idx);
          continue;
        }
        timer.next_call_time = time::SystemTime::now() + time::Duration::from_millis(timer.millis);
      }
    }

    for idx in indexes_to_remove {
      self.timers.swap_remove(idx);
    }
  }

  fn get_to_be_destroyed(&self, schedule: &RefMut<ScheduleState>) -> Vec<usize> {
    let mut indexes: Vec<usize> = schedule
      .to_be_destroyed
      .iter()
      .filter_map(|id| {
        let timer = self
          .timers
          .iter()
          .enumerate()
          .find(|(_, t)| t.id == *id)
          .map(|(idx, _)| idx);

        if timer.is_none() {
          logger::warn!("Failed to destroy timer with id: {id} (it was probably already removed)");
        } else {
          logger::debug!("destroying timer with id: {id}");
        }
        timer
      })
      .collect();
    indexes.sort_unstable_by(|a, b| b.cmp(a));
    indexes
  }
}

pub fn create_timer(
  callback: Box<dyn FnMut() -> VoidResult + 'static>,
  millis: u64,
  once: bool,
) -> Timer {
  let id = Resource::with_timer_schedule_mut(|mut t, _| t.create(callback, millis, once));
  Timer::new(id)
}

pub fn remove_timer(id: TimerId) -> VoidResult {
  Resource::with(|v| {
    let schedule = v.timer_schedule.try_borrow_mut();
    let Ok(mut schedule) = schedule else {
      bail!("Failed to mutably borrow timer schedule");
    };
    schedule.add_to_be_destroyed(id);
    Ok(())
  })
}

#[derive(Debug)]
pub struct Timer {
  id: Option<TimerId>,
}

impl Timer {
  pub(crate) fn new(id: TimerId) -> Self {
    Self { id: Some(id) }
  }

  pub fn destroy(&mut self) -> VoidResult {
    let Some(id) = self.id else {
      bail!("Already destroyed")
    };

    let res = remove_timer(id);
    if let Ok(()) = res {
      self.id = None;
    }
    res
  }

  pub fn id(&self) -> Option<TimerId> {
    self.id
  }
}
