//! Tower middleware that creates an [`AuditEventBuilder`], injects it into
//! request extensions, and auto-emits after the response if the handler
//! did not explicitly emit.
//!
//! # Layer ordering
//!
//! `AuditLayer` should sit **outside** the auth layer so the builder
//! exists before auth runs:
//!
//! ```text
//! AuditLayer          (creates builder, auto-emits after response)
//!   └── AuthLayer     (stamps actor info, may emit auth failures)
//!         └── Router  (handler enriches builder)
//! ```
//!
//! # Example
//!
//! ```ignore
//! use doxa_audit::{AuditLayer, spawn_audit_writer};
//!
//! let logger = spawn_audit_writer(db, 4096);
//!
//! let app = Router::new()
//!     .route("/widgets", get(list_widgets))
//!     .layer(AuthLayer::new(auth_state))
//!     .layer(AuditLayer::new(logger));
//! ```

use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

use axum::body::Body;
use axum::http::Request;
use axum::response::Response;
use tower::{Layer, Service};

use crate::builder::AuditEventBuilder;
use crate::logger::AuditLogger;

/// Tower [`Layer`] that wraps services with [`AuditService`].
///
/// Creates an [`AuditEventBuilder`] per request, injects it into
/// request extensions, and calls [`auto_emit`](AuditEventBuilder::auto_emit)
/// after the inner service returns. If the handler (or an inner layer
/// like `AuthLayer`) already emitted explicitly,
/// the auto-emit is a no-op — exactly-once emission is guaranteed.
#[derive(Clone)]
pub struct AuditLayer {
    logger: AuditLogger,
}

impl AuditLayer {
    /// Create a new audit layer backed by the given [`AuditLogger`].
    pub fn new(logger: AuditLogger) -> Self {
        Self { logger }
    }
}

impl<S> Layer<S> for AuditLayer {
    type Service = AuditService<S>;

    fn layer(&self, inner: S) -> AuditService<S> {
        AuditService {
            logger: self.logger.clone(),
            inner,
        }
    }
}

/// Service produced by [`AuditLayer`].
///
/// On each request it:
/// 1. Creates an [`AuditEventBuilder`] and populates request metadata
///    (source IP, user-agent, request ID) from headers.
/// 2. Inserts the builder into request extensions so inner layers and
///    handlers can enrich it.
/// 3. Calls the inner service.
/// 4. After the response, calls [`auto_emit`](AuditEventBuilder::auto_emit)
///    which sends the event with [`Outcome::Allowed`](crate::Outcome::Allowed)
///    if no prior explicit emission occurred.
#[derive(Clone)]
pub struct AuditService<S> {
    logger: AuditLogger,
    inner: S,
}

impl<S> Service<Request<Body>> for AuditService<S>
where
    S: Service<Request<Body>, Response = Response, Error = Infallible> + Clone + Send + 'static,
    S::Future: Send + 'static,
{
    type Response = Response;
    type Error = Infallible;
    type Future = Pin<Box<dyn Future<Output = Result<Response, Infallible>> + Send>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, mut req: Request<Body>) -> Self::Future {
        let builder = AuditEventBuilder::new(self.logger.clone());
        builder.set_request_metadata(req.headers());

        // Capture HTTP metadata before the request is consumed.
        let method = req.method().to_string();
        let path = req.uri().path().to_owned();

        req.extensions_mut().insert(builder.clone());

        // Canonical tower clone-and-swap: poll_ready was called on
        // `self.inner`, so use the readied clone inside the future.
        let inner_clone = self.inner.clone();
        let mut inner = std::mem::replace(&mut self.inner, inner_clone);

        Box::pin(async move {
            let response = inner.call(req).await?;

            // Record HTTP metadata.
            builder.set_http_request(&method, &path);
            builder.set_http_status(response.status().as_u16());

            // Read the outcome from response extensions if the error
            // type implements HasAuditOutcome (generated by #[derive(ApiError)]).
            // Convert the docs-crate enum to the audit-crate Outcome.
            if let Some(raw) = response
                .extensions()
                .get::<doxa_docs::__private::ResponseAuditOutcome>()
            {
                let outcome = match raw {
                    doxa_docs::__private::ResponseAuditOutcome::Allowed => crate::Outcome::Allowed,
                    doxa_docs::__private::ResponseAuditOutcome::Denied => crate::Outcome::Denied,
                    doxa_docs::__private::ResponseAuditOutcome::Error => crate::Outcome::Error,
                };
                builder.set_outcome(outcome);
            }
            builder.auto_emit();

            Ok(response)
        })
    }
}
