use super::Middleware;
use crate::config::{AuthConfig, extract_origin};
use crate::error::AuthResult;
use crate::origin_debug::{OriginTrustViolationPhase, record_origin_trust_violation};
use crate::types::{AuthRequest, AuthResponse, HttpMethod};
use async_trait::async_trait;
use std::sync::Arc;

/// Configuration for CSRF protection middleware.
#[derive(Debug, Clone)]
pub struct CsrfConfig {
    /// Whether CSRF protection is enabled. Defaults to `true`.
    pub enabled: bool,
}

impl Default for CsrfConfig {
    fn default() -> Self {
        Self { enabled: true }
    }
}

impl CsrfConfig {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn enabled(mut self, enabled: bool) -> Self {
        self.enabled = enabled;
        self
    }
}

/// CSRF protection middleware.
///
/// Validates `Origin` / `Referer` headers on state-changing requests
/// (POST, PUT, DELETE, PATCH) against the configured trusted origins
/// and the service's own base URL.
///
/// Origin checking is delegated to [`AuthConfig::is_origin_trusted`] so
/// that all origin-validation logic lives in a single place.
pub struct CsrfMiddleware {
    config: CsrfConfig,
    /// Shared auth configuration used for origin trust checks.
    auth_config: Arc<AuthConfig>,
}

impl CsrfMiddleware {
    /// Create a new CSRF middleware.
    ///
    /// Origin trust decisions are delegated to `auth_config`.
    pub fn new(config: CsrfConfig, auth_config: Arc<AuthConfig>) -> Self {
        Self {
            config,
            auth_config,
        }
    }

    fn is_state_changing(method: &HttpMethod) -> bool {
        matches!(
            method,
            HttpMethod::Post | HttpMethod::Put | HttpMethod::Delete | HttpMethod::Patch
        )
    }
}

#[async_trait]
impl Middleware for CsrfMiddleware {
    fn name(&self) -> &'static str {
        "csrf"
    }

    async fn before_request(&self, req: &AuthRequest) -> AuthResult<Option<AuthResponse>> {
        if !self.config.enabled {
            return Ok(None);
        }

        // Only check state-changing methods
        if !Self::is_state_changing(&req.method) {
            return Ok(None);
        }

        // Check Origin header first, then Referer
        let request_origin = req
            .headers
            .get("origin")
            .cloned()
            .or_else(|| req.headers.get("referer").and_then(|r| extract_origin(r)));

        match request_origin {
            Some(origin) if self.auth_config.is_origin_trusted(&origin) => Ok(None),
            Some(origin) => {
                let referer: Option<&str> =
                    req.header_ignore_ascii_case("referer").map(String::as_str);
                let origin_header = req.header_ignore_ascii_case("origin").map(String::as_str);
                let recorded = record_origin_trust_violation(
                    self.auth_config.as_ref(),
                    req,
                    OriginTrustViolationPhase::CsrfRejected,
                    origin_header.or(Some(origin.as_str())),
                    referer,
                );
                tracing::warn!(
                    origin = %origin,
                    method = ?req.method,
                    path = %req.path,
                    "csrf rejected: origin not trusted"
                );
                Ok(Some(AuthResponse::json(
                    403,
                    &serde_json::json!({
                        "code": "CSRF_ERROR",
                        "message": "Cross-site request blocked.",
                        "phase": recorded.phase,
                        "requestPath": recorded.request_path,
                        "deliveredOrigin": recorded.delivered_origin,
                        "normalizedDeliveredOrigin": recorded.normalized_delivered_origin,
                        "deliveredReferer": recorded.delivered_referer,
                        "expectedBaseOrigin": recorded.expected_base_origin,
                        "expectedTrustedOrigins": recorded.expected_trusted_origins,
                        "expectedTrustedOriginsRaw": recorded.expected_trusted_origins_raw,
                        "suggestedTrustedOrigin": recorded.suggested_trusted_origin,
                        "suggestedTrustedOriginsRaw": recorded.suggested_trusted_origins_raw,
                        "suggestedTrustedOriginsEnvLine": recorded.suggested_trusted_origins_env_line,
                        "suggestionReason": recorded.suggestion_reason,
                    }),
                )?))
            }
            // If no Origin/Referer header is present, allow the request.
            // This handles same-origin requests from older browsers and
            // non-browser clients (curl, SDKs, etc.).
            None => Ok(None),
        }
    }
}

#[cfg(test)]
#[path = "__tests__/csrf.rs"]
mod tests;
