use std::sync::LazyLock;

use actix_web::{
    Error, HttpMessage,
    body::MessageBody,
    dev::{ServiceRequest, ServiceResponse},
    http::header::{AUTHORIZATION, HeaderName, HeaderValue},
    middleware::Next,
};
use tracing::Instrument;
use uuid::Uuid;

use crate::util::iden::Identity;

pub const TRACE_ID: HeaderName = HeaderName::from_static("x-trace-id");

static HOSTNAME: LazyLock<String> = LazyLock::new(|| hostname::get().ok().and_then(|s| s.into_string().ok()).unwrap_or_default());

pub async fn trace_mw(mut req: ServiceRequest, next: Next<impl MessageBody>) -> Result<ServiceResponse<impl MessageBody>, Error> {
    // 生成 TraceId
    let trace_id = match req
        .headers()
        .get(&TRACE_ID)
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string())
    {
        Some(v) if !v.is_empty() => v,
        _ => gen_trace_id(&mut req),
    };

    // 解析 Identity
    let token = req
        .headers()
        .get(AUTHORIZATION)
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string());
    let iden = match token {
        None => Identity::default(),
        Some(v) => Identity::from_auth_token(v),
    };
    let iden_str = iden.to_string();

    // 设置 Identity
    req.extensions_mut().insert(iden);

    // 设置 trace span
    let span = tracing::info_span!("trace", hostname = HOSTNAME.as_str(), trace_id, identity = iden_str);
    let mut resp = next.call(req).instrument(span).await?;
    // 设置 Header
    resp.headers_mut()
        .insert(TRACE_ID, HeaderValue::from_str(&trace_id).unwrap_or(HeaderValue::from_static("")));

    Ok(resp)
}

fn gen_trace_id(req: &mut ServiceRequest) -> String {
    let id = Uuid::new_v4().simple().to_string();
    req.headers_mut()
        .insert(TRACE_ID, HeaderValue::from_str(&id).unwrap_or(HeaderValue::from_static("")));
    id
}
