use axum::{
    http::{HeaderValue, Request, Response, StatusCode},
    middleware::Next,
};

const ACCESS_CONTROL_ALLOW_ORIGIN: &str = "Access-Control-Allow-Origin";
const ACCESS_CONTROL_ALLOW_METHODS: &str = "Access-Control-Allow-Methods";
const ACCESS_CONTROL_ALLOW_HEADERS: &str = "Access-Control-Allow-Headers";
const ACCESS_CONTROL_MAX_AGE: &str = "Access-Control-Max-Age";
const X_CONTENT_TYPE_OPTIONS: &str = "X-Content-Type-Options";
const X_FRAME_OPTIONS: &str = "X-Frame-Options";
const X_XSS_PROTECTION: &str = "X-XSS-Protection";
const STRICT_TRANSPORT_SECURITY: &str = "Strict-Transport-Security";
const CONTENT_SECURITY_POLICY: &str = "Content-Security-Policy";
const X_PERMITTED_CROSS_DOMAIN_POLICIES: &str = "X-Permitted-Cross-Domain-Policies";
const CROSS_ORIGIN_EMBEDDER_POLICY: &str = "Cross-Origin-Embedder-Policy";
const CROSS_ORIGIN_OPENER_POLICY: &str = "Cross-Origin-Opener-Policy";
const CROSS_ORIGIN_RESOURCE_POLICY: &str = "Cross-Origin-Resource-Policy";
const REFERRER_POLICY: &str = "Referrer-Policy";
const PERMISSIONS_POLICY: &str = "Permissions-Policy";
const ALLOW_ALL_ORIGINS: &str = "*";
const ALLOWED_METHODS: &str = "GET, POST, PUT, DELETE, OPTIONS";
const ALLOWED_HEADERS: &str = "Content-Type, Authorization, Accept, Origin, X-Requested-With, Cache-Control, Pragma, X-RSC-Streaming";
const MAX_AGE_SECONDS: &str = "86400";
const NOSNIFF: &str = "nosniff";
const FRAME_DENY: &str = "DENY";
const XSS_PROTECTION: &str = "1; mode=block";
const HSTS_HEADER: &str = "max-age=31536000; includeSubDomains";
const CROSS_DOMAIN_POLICIES_NONE: &str = "none";
const COEP_REQUIRE_CORP: &str = "require-corp";
const COOP_SAME_ORIGIN: &str = "same-origin";
const CORP_SAME_ORIGIN: &str = "same-origin";
const REFERRER_STRICT_ORIGIN: &str = "strict-origin-when-cross-origin";
const PERMISSIONS_RESTRICTIVE: &str = "geolocation=(), microphone=(), camera=(), payment=()";

fn apply_error_cache_control(headers: &mut axum::http::HeaderMap, status: StatusCode) {
    if status.is_client_error() || status.is_server_error() {
        headers.insert(
            axum::http::header::CACHE_CONTROL,
            HeaderValue::from_static("no-cache, no-store, must-revalidate"),
        );
    }
}

pub async fn cors_middleware(
    request: Request<axum::body::Body>,
    next: Next,
) -> Response<axum::body::Body> {
    let mut response = next.run(request).await;

    let status = response.status();
    let headers = response.headers_mut();

    add_cors_headers(headers);
    apply_error_cache_control(headers, status);

    response
}

fn add_cors_headers(headers: &mut axum::http::HeaderMap) {
    let cors_headers = [
        (ACCESS_CONTROL_ALLOW_ORIGIN, ALLOW_ALL_ORIGINS),
        (ACCESS_CONTROL_ALLOW_METHODS, ALLOWED_METHODS),
        (ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_HEADERS),
        (ACCESS_CONTROL_MAX_AGE, MAX_AGE_SECONDS),
    ];

    for (header_name, header_value) in cors_headers {
        if let Ok(value) = HeaderValue::from_str(header_value) {
            headers.insert(header_name, value);
        }
    }
}

pub async fn security_headers_middleware(
    request: Request<axum::body::Body>,
    next: Next,
) -> Response<axum::body::Body> {
    let mut response = next.run(request).await;

    let status = response.status();
    let headers = response.headers_mut();

    add_security_headers(headers);
    apply_error_cache_control(headers, status);

    response
}

fn add_security_headers(headers: &mut axum::http::HeaderMap) {
    let csp_policy = if let Some(config) = crate::server::config::Config::get() {
        config.build_csp_policy()
    } else {
        "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:".to_string()
    };

    let security_headers = [
        (X_CONTENT_TYPE_OPTIONS, NOSNIFF),
        (X_FRAME_OPTIONS, FRAME_DENY),
        (X_XSS_PROTECTION, XSS_PROTECTION),
        (STRICT_TRANSPORT_SECURITY, HSTS_HEADER),
        (X_PERMITTED_CROSS_DOMAIN_POLICIES, CROSS_DOMAIN_POLICIES_NONE),
        (CROSS_ORIGIN_EMBEDDER_POLICY, COEP_REQUIRE_CORP),
        (CROSS_ORIGIN_OPENER_POLICY, COOP_SAME_ORIGIN),
        (CROSS_ORIGIN_RESOURCE_POLICY, CORP_SAME_ORIGIN),
        (REFERRER_POLICY, REFERRER_STRICT_ORIGIN),
        (PERMISSIONS_POLICY, PERMISSIONS_RESTRICTIVE),
    ];

    for (header_name, header_value) in security_headers {
        if let Ok(value) = HeaderValue::from_str(header_value) {
            headers.insert(header_name, value);
        }
    }

    if let Ok(value) = HeaderValue::from_str(&csp_policy) {
        headers.insert(CONTENT_SECURITY_POLICY, value);
    }
}
