use crate::{
    exe,
    http::{
        meta::HttpMetadata, protocol::header::HeaderKey, protocol::status::StatusCode,
        types::Executor,
    },
};
use std::sync::Arc;

#[derive(Clone, Debug)]
pub struct CorsConfig {
    allow_origin_all: bool,
    allow_methods: Vec<String>,
    allow_headers: Vec<String>,
    allow_credentials: bool,
    max_age: Option<usize>,
}

impl Default for CorsConfig {
    fn default() -> Self {
        Self {
            allow_origin_all: true,
            allow_methods: vec![
                "GET".to_string(),
                "POST".to_string(),
                "PUT".to_string(),
                "DELETE".to_string(),
                "PATCH".to_string(),
                "OPTIONS".to_string(),
            ],
            allow_headers: vec![
                "Content-Type".to_string(),
                "Authorization".to_string(),
                "X-Requested-With".to_string(),
            ],
            allow_credentials: true,
            max_age: Some(86400),
        }
    }
}

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

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

    pub fn allow_methods(mut self, methods: Vec<&str>) -> Self {
        self.allow_methods = methods.into_iter().map(|s| s.to_uppercase()).collect();
        self
    }

    pub fn allow_headers(mut self, headers: Vec<&str>) -> Self {
        self.allow_headers = headers.into_iter().map(|s| s.to_string()).collect();
        self
    }

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

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

    pub fn build(self) -> Arc<Executor> {
        let config = Arc::new(self);
        exe!(
            move |ctx, config| {
                let mut is_options = false;

                if let Some(meta) = ctx.local.get_mut::<HttpMetadata>() {
                    let origin = meta.headers.get(&HeaderKey::Origin).cloned();

                    if origin.is_some() || config.allow_origin_all {
                        let origin_value = origin.as_deref().unwrap_or("*");
                        meta.headers.insert(
                            HeaderKey::AccessControlAllowOrigin,
                            origin_value.to_string(),
                        );
                    }

                    meta.headers.insert(
                        HeaderKey::AccessControlAllowMethods,
                        config.allow_methods.join(", "),
                    );
                    meta.headers.insert(
                        HeaderKey::AccessControlAllowHeaders,
                        config.allow_headers.join(", "),
                    );

                    if config.allow_credentials {
                        meta.headers
                            .insert(HeaderKey::AccessControlAllowCredentials, "true".to_string());
                    }

                    if let Some(max_age) = config.max_age {
                        meta.headers
                            .insert(HeaderKey::AccessControlMaxAge, max_age.to_string());
                    }

                    if meta.method.to_str() == "OPTIONS" {
                        is_options = true;
                    }
                }

                if is_options {
                    ctx.status(StatusCode::Ok).send("", None);
                    return false;
                }

                true
            },
            |ctx| { config.clone() }
        )
    }
}

#[macro_export]
macro_rules! cors {
    () => {
        $crate::http::middlewares::cors::CorsConfig::new().build()
    };
    ($($t:tt)*) => {
        $crate::http::middlewares::cors::CorsConfig::new()$($t)*.build()
    };
}
