use std::{
    collections::{BTreeMap, VecDeque},
    process::Output,
    time::Instant,
};

use crate::cmd::{Cmd, CmdOutput, CmdResult};

/// Stats and other data tracked over the course of a server run.
pub struct Stats {
    start_time: Instant,
    exit_codes: BTreeMap<Option<i32>, u64>,
    nonzero_status_detail: LimitedBTreeMap<36, Cmd, Output>,
    duration_by_command: BTreeMap<String, (u64, u64)>,
    errors: u64,
    error_detail: LimitedBTreeMap<36, Cmd, String>,
    recent_results: RingBuffer<16, CmdOutput>,
    handled: u64,
}
impl Stats {
    pub fn new() -> Self {
        Self {
            start_time: Instant::now(),
            exit_codes: Default::default(),
            nonzero_status_detail: Default::default(),
            duration_by_command: Default::default(),
            errors: 0,
            error_detail: Default::default(),
            recent_results: Default::default(),
            handled: 0,
        }
    }

    pub fn average_durations(&self) -> impl Iterator<Item = (&str, u64)> {
        self.duration_by_command
            .iter()
            .map(|(key, (count, total))| (key.as_str(), total / count))
    }

    pub fn total_cmd_duration(&self) -> u64 {
        self.duration_by_command
            .values()
            .map(|(_, total)| total)
            .sum()
    }

    pub fn errors(&self) -> u64 {
        self.errors
    }

    pub fn handled(&self) -> u64 {
        self.handled
    }

    pub fn exit_codes(&self) -> &BTreeMap<Option<i32>, u64> {
        &self.exit_codes
    }

    pub fn nonzero_status_detail(&self) -> &LimitedBTreeMap<36, Cmd, Output> {
        &self.nonzero_status_detail
    }

    pub fn error_detail(&self) -> &LimitedBTreeMap<36, Cmd, String> {
        &self.error_detail
    }

    pub fn recent_results(&self) -> &RingBuffer<16, CmdOutput> {
        &self.recent_results
    }

    pub fn elapsed(&self) -> u64 {
        (Instant::now() - self.start_time).as_secs()
    }

    pub fn record_cmd_result(&mut self, result: CmdResult) {
        self.handled = self.handled.saturating_add(1);
        match result {
            CmdResult::Ok(result) => {
                self.exit_codes
                    .entry(result.output.status.code())
                    .and_modify(|v| *v = v.saturating_add(1))
                    .or_insert(1);

                self.duration_by_command
                    .entry(result.cmd.program().to_string())
                    .and_modify(|&mut (ref mut count, ref mut total)| {
                        *count += 1;
                        *total += result.duration.as_millis() as u64;
                    })
                    .or_insert((1, result.duration.as_millis() as u64));

                if !result.output.status.success() {
                    self.nonzero_status_detail.push(result.cmd, result.output);
                } else {
                    self.recent_results.push(result);
                }
            }
            CmdResult::Err(err) => {
                self.errors = self.errors.saturating_add(1);
                self.error_detail.push(err.cmd, err.err);
            }
        };
    }
}
impl Default for Stats {
    fn default() -> Self {
        Self::new()
    }
}

/// "ring buffer" but not really, backed by a VecDeque, has a max size
#[derive(Debug)]
pub struct RingBuffer<const N: usize, T> {
    buffer: VecDeque<T>,
}
impl<const N: usize, T> Default for RingBuffer<N, T> {
    fn default() -> Self {
        Self {
            buffer: Default::default(),
        }
    }
}
impl<const N: usize, T> RingBuffer<N, T> {
    fn push(&mut self, v: T) -> Option<T> {
        let mut res = None;
        if self.buffer.len() > N {
            res = self.buffer.pop_back();
        }
        self.buffer.push_front(v);
        res
    }

    pub fn is_empty(&self) -> bool {
        self.buffer.is_empty()
    }

    pub fn max_capacity(&self) -> usize {
        N
    }

    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.buffer.iter()
    }
}

#[derive(Debug)]
pub struct LimitedBTreeMap<const N: usize, K, V> {
    map: BTreeMap<K, V>,
    key_order: RingBuffer<N, K>,
}
impl<const N: usize, K, V> Default for LimitedBTreeMap<N, K, V> {
    fn default() -> Self {
        Self {
            map: Default::default(),
            key_order: Default::default(),
        }
    }
}
impl<const N: usize, K, V> LimitedBTreeMap<N, K, V>
where
    K: Ord + Clone,
{
    pub fn is_empty(&self) -> bool {
        self.map.is_empty()
    }

    fn push(&mut self, k: K, v: V) -> Option<V> {
        self.map.insert(k.clone(), v);
        let to_remove = self.key_order.push(k);
        to_remove.and_then(|to_remove| self.map.remove(&to_remove))
    }

    pub fn max_capacity(&self) -> usize {
        N
    }

    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
        self.map.iter()
    }
}
