use std::sync::Arc;

use axum::{
    extract::{Request, State},
    http::StatusCode,
    middleware::Next,
    response::{IntoResponse, Response},
    Json,
};
use serde_json::json;
use tracing::warn;

use crate::repositories::AuditLogRepository;
use crate::AppState;

/// Axum middleware that validates the `X-API-Key` header against the in-memory cache
/// and logs every request to the audit log.
pub async fn api_key_middleware(
    State(state): State<Arc<AppState>>,
    request: Request,
    next: Next,
) -> Response {
    let api_key_header = request
        .headers()
        .get("X-API-Key")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("")
        .to_string();

    let endpoint = request.uri().path().to_string();

    let cached = state.api_key_cache.get(&api_key_header).await;

    match cached {
        None => {
            let msg = "Invalid API key";
            log_request(&state, "", &api_key_header, &endpoint, msg, 1, false).await;
            warn!(endpoint = %endpoint, "rejected request: invalid API key");
            (
                StatusCode::UNAUTHORIZED,
                Json(json!({ "error": msg })),
            )
                .into_response()
        }
        Some(key_data) if !key_data.active => {
            let msg = "API key is inactive";
            log_request(
                &state,
                &key_data.user_id,
                &api_key_header,
                &endpoint,
                msg,
                1,
                false,
            )
            .await;
            warn!(endpoint = %endpoint, "rejected request: inactive API key");
            (
                StatusCode::UNAUTHORIZED,
                Json(json!({ "error": msg })),
            )
                .into_response()
        }
        Some(key_data) => {
            log_request(
                &state,
                &key_data.user_id,
                &api_key_header,
                &endpoint,
                "success",
                0,
                true,
            )
            .await;
            next.run(request).await
        }
    }
}

async fn log_request(
    state: &AppState,
    user_id: &str,
    api_key: &str,
    endpoint: &str,
    response: &str,
    error_count: i32,
    success: bool,
) {
    if let Err(e) = state
        .storage
        .audit_log_repo
        .log_request(user_id, api_key, endpoint, response, error_count, success)
        .await
    {
        tracing::error!(error = %e, "failed to log API request");
    }
}
