use log::{debug, warn};
use prometheus::HistogramVec;
use std::{
    fmt::{Debug, Formatter},
    str,
    sync::{Arc, RwLock},
    time::Duration,
};
use tokio::time::Instant;

pub enum ContextFilterLevel {
    Debug,
    Info,
    Warn,
    Error,
}

pub struct Context {
    started: Instant,
    interface: String,
    trace_id: String,
    phases_duration: RwLock<Vec<(String, Duration, Option<String>)>>,
    histogram: Option<HistogramVec>,
    level: RwLock<ContextFilterLevel>,
}

impl Context {
    fn new(interface: &str) -> Self {
        Self {
            trace_id: uuid::Uuid::new_v4().to_string(),
            phases_duration: RwLock::new(Vec::new()),
            started: Instant::now(),
            interface: interface.to_string(),
            histogram: None,
            level: RwLock::new(ContextFilterLevel::Debug),
        }
    }

    pub fn push_phase(&self, phase: &str, message: Option<String>) {
        self.phases_duration.write().unwrap().push((
            phase.to_string(),
            self.started.elapsed(),
            message,
        ));

        if let Some(histogram) = &self.histogram {
            histogram
                .with_label_values(&[&self.interface, phase])
                .observe(self.started.elapsed().as_secs_f64());
        }
    }

    fn phases(&self) -> Vec<(String, Duration, Option<String>)> {
        self.phases_duration.read().unwrap().clone()
    }

    pub fn id(&self) -> &str {
        &self.trace_id
    }

    pub fn started(&self) -> Instant {
        self.started
    }

    pub fn slow(&self, duration: Duration) -> bool {
        if self.started.elapsed() > duration {
            self.level(ContextFilterLevel::Warn);
            true
        } else {
            false
        }
    }

    fn level(&self, level: ContextFilterLevel) {
        *self.level.write().unwrap() = level;
    }

    fn with_histogram(mut self, histogram: HistogramVec) -> Self {
        self.histogram = Some(histogram);
        self
    }
}

pub fn new() -> Arc<Context> {
    Arc::new(Context::new("router"))
}

pub fn with_interface(interface: &str) -> Arc<Context> {
    Arc::new(Context::new(interface))
}

pub fn with_metrics(histogram: HistogramVec) -> Arc<Context> {
    Arc::new(Context::new("router").with_histogram(histogram))
}

pub fn with_interface_and_metrics(interface: &str, histogram: HistogramVec) -> Arc<Context> {
    Arc::new(Context::new(interface).with_histogram(histogram))
}

impl Debug for Context {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Context")
            .field("id", &self.trace_id)
            .field("phases", &self.phases())
            .field("costed", &self.started.elapsed())
            .finish()
    }
}

impl Drop for Context {
    fn drop(&mut self) {
        match *self.level.read().unwrap() {
            ContextFilterLevel::Debug => debug!("{:?}", self),
            ContextFilterLevel::Info => debug!("{:?}", self),
            ContextFilterLevel::Warn => warn!("{:?}", self),
            ContextFilterLevel::Error => warn!("{:?}", self),
        }
    }
}
