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

/// Configuration for CORS middleware.
#[derive(Debug, Clone)]
pub struct CorsConfig {
    /// Allowed origins. An empty list means no CORS headers are added.
    /// Use `["*"]` to allow all origins (not recommended for production).
    pub allowed_origins: Vec<String>,

    /// Allowed HTTP methods. Defaults to common auth methods.
    pub allowed_methods: Vec<String>,

    /// Allowed request headers.
    pub allowed_headers: Vec<String>,

    /// Headers exposed to the browser.
    pub exposed_headers: Vec<String>,

    /// Whether credentials (cookies, authorization) are allowed.
    pub allow_credentials: bool,

    /// Max age for preflight cache (seconds).
    pub max_age: u64,

    /// Whether CORS handling is enabled.
    pub enabled: bool,
}

impl Default for CorsConfig {
    fn default() -> Self {
        Self {
            allowed_origins: Vec::new(),
            allowed_methods: vec![
                "GET".into(),
                "POST".into(),
                "PUT".into(),
                "DELETE".into(),
                "PATCH".into(),
                "OPTIONS".into(),
            ],
            allowed_headers: vec![
                "Content-Type".into(),
                "Authorization".into(),
                "X-Requested-With".into(),
                "X-Athena-Sdk".into(),
                "X-Athena-Tenant".into(),
                "X-Athena-Client".into(),
                "X-Athena-Proxy-Request-Id".into(),
                "X-Request-Id".into(),
            ],
            exposed_headers: vec!["X-Athena-Time".into()],
            allow_credentials: true,
            max_age: 86400,
            enabled: true,
        }
    }
}

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

    pub fn allowed_origin(mut self, origin: impl Into<String>) -> Self {
        self.allowed_origins.push(origin.into());
        self
    }

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

    pub fn max_age(mut self, seconds: u64) -> Self {
        self.max_age = seconds;
        self
    }

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

/// CORS middleware.
///
/// Handles preflight OPTIONS requests and adds CORS response headers.
pub struct CorsMiddleware {
    config: CorsConfig,
    auth_config: Arc<AuthConfig>,
}

impl CorsMiddleware {
    pub fn new(config: CorsConfig, auth_config: Arc<AuthConfig>) -> Self {
        Self {
            config,
            auth_config,
        }
    }

    fn has_wildcard_allow_all(&self) -> bool {
        self.config
            .allowed_origins
            .iter()
            .any(|origin| origin.trim() == "*")
    }

    fn is_origin_allowed(&self, origin: &str) -> bool {
        if self.config.allowed_origins.is_empty() {
            return false;
        }
        if self.has_wildcard_allow_all() {
            return true;
        }

        let Some(origin) = extract_origin(origin) else {
            return false;
        };

        self.config.allowed_origins.iter().any(|pattern| {
            normalize_configured_origin_pattern(pattern)
                .is_some_and(|normalized| glob_match::glob_match(&normalized, &origin))
        })
    }

    fn cors_headers(&self, origin: &str) -> Vec<(String, String)> {
        let mut headers = Vec::new();

        // Use the request origin if allowed (not wildcard when credentials are on)
        let allow_origin = if self.config.allow_credentials {
            origin.to_string()
        } else if self.has_wildcard_allow_all() {
            "*".to_string()
        } else {
            origin.to_string()
        };

        headers.push(("Access-Control-Allow-Origin".into(), allow_origin));

        if self.config.allow_credentials {
            headers.push(("Access-Control-Allow-Credentials".into(), "true".into()));
        }

        if !self.config.allowed_methods.is_empty() {
            headers.push((
                "Access-Control-Allow-Methods".into(),
                self.config.allowed_methods.join(", "),
            ));
        }

        if !self.config.allowed_headers.is_empty() {
            headers.push((
                "Access-Control-Allow-Headers".into(),
                self.config.allowed_headers.join(", "),
            ));
        }

        if !self.config.exposed_headers.is_empty() {
            headers.push((
                "Access-Control-Expose-Headers".into(),
                self.config.exposed_headers.join(", "),
            ));
        }

        headers.push((
            "Access-Control-Max-Age".into(),
            self.config.max_age.to_string(),
        ));

        headers
    }
}

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

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

        if req.method == HttpMethod::Options {
            let origin = match req.headers.get("origin") {
                Some(value) => value.clone(),
                None => return Ok(None),
            };

            if !self.is_origin_allowed(&origin) {
                let referer: Option<&str> =
                    req.header_ignore_ascii_case("referer").map(String::as_str);
                let recorded = record_origin_trust_violation(
                    self.auth_config.as_ref(),
                    req,
                    OriginTrustViolationPhase::CorsPreflightRejected,
                    Some(origin.as_str()),
                    referer,
                );
                return Ok(Some(AuthResponse::json(
                    403,
                    &serde_json::json!({
                        "code": "CORS_ORIGIN_NOT_ALLOWED",
                        "message": "Request origin is not allowed by CORS policy.",
                        "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,
                    }),
                )?));
            }

            let mut response = AuthResponse::new(204);
            for (key, value) in self.cors_headers(&origin) {
                response = response.with_header(key, value);
            }
            return Ok(Some(response));
        }

        let origin = match req.headers.get("origin") {
            Some(o) => o.clone(),
            None => return Ok(None), // No Origin header -> not a CORS request
        };

        if !self.is_origin_allowed(&origin) {
            return Ok(None); // Origin not allowed -> skip CORS headers
        }

        Ok(None)
    }

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

        let origin = match req.headers.get("origin") {
            Some(o) => o.clone(),
            None => return Ok(response),
        };

        if !self.is_origin_allowed(&origin) {
            maybe_record_suppressed_cors_attempt(self.auth_config.as_ref(), req);
            return Ok(response);
        }

        for (key, value) in self.cors_headers(&origin) {
            response.headers.insert(key, value);
        }

        Ok(response)
    }
}

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