//! Reimplementation of tower-http's DefaultMakeSpan that only runs at "INFO" level for our own needs.

use axum::http::{Request, StatusCode};
use kanidm_proto::constants::KOPID;
use sketching::event_dynamic_lvl;
use tower_http::LatencyUnit;
use tracing::{Level, Span};

/// The default way Spans will be created for Trace.
///
#[derive(Debug, Clone)]
pub struct DefaultMakeSpanKanidmd {}

impl DefaultMakeSpanKanidmd {
    /// Create a new `DefaultMakeSpanKanidmd`.
    pub fn new() -> Self {
        Self {}
    }
}

impl Default for DefaultMakeSpanKanidmd {
    fn default() -> Self {
        Self::new()
    }
}

impl<B> tower_http::trace::MakeSpan<B> for DefaultMakeSpanKanidmd {
    fn make_span(&mut self, request: &Request<B>) -> Span {
        // Needs to be at info to ensure that there is always a span for each
        // tracing event to hook into.
        tracing::span!(
            Level::INFO,
            "request",
            method = %request.method(),
            uri = %request.uri(),
            version = ?request.version(),
        )
    }
}

#[derive(Clone, Debug)]
pub(crate) struct DefaultOnResponseKanidmd {
    #[allow(dead_code)]
    level: Level,
    #[allow(dead_code)]
    latency_unit: LatencyUnit,
    #[allow(dead_code)]
    include_headers: bool,
}

impl DefaultOnResponseKanidmd {
    #[allow(dead_code)]
    pub fn new() -> Self {
        Self::default()
    }
}

impl Default for DefaultOnResponseKanidmd {
    fn default() -> Self {
        Self {
            level: Level::INFO,
            latency_unit: LatencyUnit::Millis,
            include_headers: false,
        }
    }
}

impl<B> tower_http::trace::OnResponse<B> for DefaultOnResponseKanidmd {
    fn on_response(
        self,
        response: &axum::response::Response<B>,
        latency: std::time::Duration,
        _span: &Span,
    ) {
        let kopid = match response.headers().get(KOPID) {
            Some(val) => val.to_str().unwrap_or("<invalid kopid>"),
            None => "<unknown>",
        };
        let (level, msg) =
            match response.status().is_success() || response.status().is_informational() {
                true => (Level::DEBUG, "response sent"),
                false => {
                    if response.status().is_redirection() {
                        (Level::INFO, "client redirection sent")
                    } else if response.status().is_client_error() {
                        if response.status() == StatusCode::NOT_FOUND {
                            (Level::INFO, "client error")
                        } else {
                            (Level::WARN, "client error") // it worked, but there was an input error
                        }
                    } else {
                        (Level::ERROR, "error handling request") // oh no the server failed
                    }
                }
            };
        event_dynamic_lvl!(
            level,
            ?latency,
            status_code = response.status().as_u16(),
            kopid = kopid,
            msg
        );
    }
}
