use std::time::Duration;

use axum::http::{
    header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE},
    HeaderName, HeaderValue, Method,
};
use tower_http::cors::{Any, CorsLayer};

/// CORS configuration for the control center
pub struct CorsConfig {
    pub allowed_origins: Vec<String>,
    pub allowed_methods: Vec<Method>,
    pub allowed_headers: Vec<String>,
    pub expose_headers: Vec<String>,
    pub max_age: Duration,
    pub allow_credentials: bool,
}

impl Default for CorsConfig {
    fn default() -> Self {
        Self {
            allowed_origins: vec!["http://localhost:3000".to_string()], /* Default frontend dev
                                                                         * server */
            allowed_methods: vec![
                Method::GET,
                Method::POST,
                Method::PUT,
                Method::DELETE,
                Method::PATCH,
                Method::OPTIONS,
            ],
            allowed_headers: vec![
                CONTENT_TYPE.to_string(),
                AUTHORIZATION.to_string(),
                ACCEPT.to_string(),
                "x-requested-with".to_string(),
                "x-session-id".to_string(),
            ],
            expose_headers: vec![
                "x-total-count".to_string(),
                "x-rate-limit-remaining".to_string(),
                "x-rate-limit-limit".to_string(),
                "x-rate-limit-reset".to_string(),
            ],
            max_age: Duration::from_secs(86400), // 24 hours
            allow_credentials: true,
        }
    }
}

impl CorsConfig {
    /// Create a permissive CORS config for development
    pub fn permissive() -> Self {
        Self {
            allowed_origins: vec!["*".to_string()], // Allow all origins in development
            ..Self::default()
        }
    }

    /// Create a production CORS config with specific origins
    pub fn production(allowed_origins: Vec<String>) -> Self {
        Self {
            allowed_origins,
            ..Self::default()
        }
    }

    /// Create CORS layer from configuration
    pub fn into_layer(self) -> CorsLayer {
        let mut cors = CorsLayer::new();

        // Set allowed origins
        if self.allowed_origins.contains(&"*".to_string()) {
            cors = cors.allow_origin(Any);
        } else {
            let origins: Result<Vec<HeaderValue>, _> = self
                .allowed_origins
                .into_iter()
                .map(|origin| origin.parse())
                .collect();

            match origins {
                Ok(origins) => {
                    cors = cors.allow_origin(origins);
                }
                Err(e) => {
                    tracing::warn!("Invalid CORS origin configuration: {}", e);
                    cors = cors.allow_origin(Any);
                }
            }
        }

        // Set allowed methods
        cors = cors.allow_methods(self.allowed_methods);

        // Set allowed headers
        let headers: Result<Vec<HeaderName>, _> = self
            .allowed_headers
            .into_iter()
            .map(|header| header.parse())
            .collect();

        match headers {
            Ok(headers) => {
                cors = cors.allow_headers(headers);
            }
            Err(e) => {
                tracing::warn!("Invalid CORS header configuration: {}", e);
                cors = cors.allow_headers(Any);
            }
        }

        // Set exposed headers
        let expose_headers: Result<Vec<HeaderName>, _> = self
            .expose_headers
            .into_iter()
            .map(|header| header.parse())
            .collect();

        if let Ok(headers) = expose_headers {
            cors = cors.expose_headers(headers);
        }

        // Set max age
        cors = cors.max_age(self.max_age);

        // Set credentials
        if self.allow_credentials {
            cors = cors.allow_credentials(true);
        }

        cors
    }
}

/// Create default CORS layer for development
pub fn create_development_cors() -> CorsLayer {
    CorsConfig::permissive().into_layer()
}

/// Create production CORS layer
pub fn create_production_cors(allowed_origins: Vec<String>) -> CorsLayer {
    CorsConfig::production(allowed_origins).into_layer()
}

/// Create CORS layer from environment configuration
pub fn create_cors_from_env() -> CorsLayer {
    let allowed_origins = std::env::var("CORS_ALLOWED_ORIGINS")
        .map(|origins| origins.split(',').map(|s| s.trim().to_string()).collect())
        .unwrap_or_else(|_| vec!["http://localhost:3000".to_string()]);

    let allow_credentials = std::env::var("CORS_ALLOW_CREDENTIALS")
        .map(|v| v.to_lowercase() == "true")
        .unwrap_or(true);

    let max_age = std::env::var("CORS_MAX_AGE")
        .ok()
        .and_then(|v| v.parse::<u64>().ok())
        .map(Duration::from_secs)
        .unwrap_or(Duration::from_secs(86400));

    CorsConfig {
        allowed_origins,
        allow_credentials,
        max_age,
        ..CorsConfig::default()
    }
    .into_layer()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cors_config_default() {
        let config = CorsConfig::default();
        assert_eq!(config.allowed_origins, vec!["http://localhost:3000"]);
        assert!(config.allow_credentials);
        assert_eq!(config.max_age, Duration::from_secs(86400));
    }

    #[test]
    fn test_cors_config_permissive() {
        let config = CorsConfig::permissive();
        assert_eq!(config.allowed_origins, vec!["*"]);
        assert!(config.allow_credentials);
    }

    #[test]
    fn test_cors_config_production() {
        let origins = vec![
            "https://example.com".to_string(),
            "https://app.example.com".to_string(),
        ];
        let config = CorsConfig::production(origins.clone());
        assert_eq!(config.allowed_origins, origins);
        assert!(config.allow_credentials);
    }

    #[test]
    fn test_cors_layer_creation() {
        let config = CorsConfig::default();
        let _layer = config.into_layer(); // Should not panic
    }

    #[test]
    fn test_development_cors() {
        let _layer = create_development_cors(); // Should not panic
    }

    #[test]
    fn test_production_cors() {
        let origins = vec!["https://example.com".to_string()];
        let _layer = create_production_cors(origins); // Should not panic
    }
}
