use cow_utils::CowUtils;
use http::HeaderValue;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::OnceLock;

pub static GLOBAL_CONFIG: OnceLock<Config> = OnceLock::new();

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum Mode {
    #[default]
    Development,
    Production,
}

impl std::fmt::Display for Mode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Mode::Development => write!(f, "development"),
            Mode::Production => write!(f, "production"),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    pub host: String,
    pub port: u16,
    pub origin: Option<String>,
    pub enable_logging: bool,
    pub timeout_seconds: u64,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            host: "127.0.0.1".to_string(),
            port: 3000,
            origin: None,
            enable_logging: true,
            timeout_seconds: 30,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorsConfig {
    pub allowed_origins: Vec<String>,
    pub allow_credentials: bool,
    pub max_age: u32,
}

impl Default for CorsConfig {
    fn default() -> Self {
        Self { allowed_origins: vec![], allow_credentials: true, max_age: 86400 }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedirectConfig {
    pub allowed_hosts: Vec<String>,
    pub allow_relative: bool,
    pub allow_subdomains: bool,
}

impl Default for RedirectConfig {
    fn default() -> Self {
        Self { allowed_hosts: vec![], allow_relative: true, allow_subdomains: false }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CspConfig {
    pub script_src: Vec<String>,
    pub style_src: Vec<String>,
    pub img_src: Vec<String>,
    pub font_src: Vec<String>,
    pub connect_src: Vec<String>,
    pub default_src: Vec<String>,
    pub worker_src: Vec<String>,
}

impl Default for CspConfig {
    fn default() -> Self {
        Self {
            default_src: vec!["'self'".to_string()],
            script_src: vec!["'self'".to_string()],
            style_src: vec!["'self'".to_string()],
            img_src: vec!["'self'".to_string(), "data:".to_string(), "https:".to_string()],
            font_src: vec!["'self'".to_string(), "data:".to_string()],
            connect_src: vec!["'self'".to_string(), "ws:".to_string(), "wss:".to_string()],
            worker_src: vec!["'self'".to_string()],
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitConfig {
    pub enabled: bool,
    pub requests_per_second: u32,
    pub burst_size: u32,
    #[serde(default = "default_revalidate_rpm")]
    pub revalidate_requests_per_minute: u32,
}

fn default_revalidate_rpm() -> u32 {
    10
}

impl Default for RateLimitConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            requests_per_second: 100,
            burst_size: 200,
            revalidate_requests_per_minute: 10,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpamBlockerConfig {
    pub enabled: bool,
}

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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ViteConfig {
    pub host: String,
    pub port: u16,
    pub enable_hmr_proxy: bool,
    pub ws_protocol: String,
}

impl Default for ViteConfig {
    fn default() -> Self {
        Self {
            host: "localhost".to_string(),
            port: 5173,
            enable_hmr_proxy: true,
            ws_protocol: "vite-hmr".to_string(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StaticConfig {
    pub dev_public_dir: PathBuf,
    pub prod_public_dir: PathBuf,
    pub enable_directory_listing: bool,
    pub cache_control: String,
}

impl Default for StaticConfig {
    fn default() -> Self {
        Self {
            dev_public_dir: PathBuf::from("public"),
            prod_public_dir: PathBuf::from("dist"),
            enable_directory_listing: false,
            cache_control: "public, max-age=31536000".to_string(),
        }
    }
}

#[derive(Debug, Clone)]
enum RoutePattern {
    Exact(String),
    Prefix(String),
    Regex(regex::Regex),
}

impl RoutePattern {
    fn from_pattern(pattern: &str) -> Self {
        if let Some(prefix) = pattern.strip_suffix("/*") {
            RoutePattern::Prefix(prefix.to_string())
        } else if pattern.contains('*') {
            let escaped = regex::escape(pattern);
            let regex_pattern = escaped.cow_replace(r"\*", ".*");
            match regex::Regex::new(&format!("^{}$", regex_pattern)) {
                Ok(regex) => RoutePattern::Regex(regex),
                Err(_) => RoutePattern::Exact(pattern.to_string()),
            }
        } else {
            RoutePattern::Exact(pattern.to_string())
        }
    }

    fn matches(&self, path: &str) -> bool {
        match self {
            RoutePattern::Exact(pattern) => pattern == path,
            RoutePattern::Prefix(prefix) => {
                path == prefix || path.starts_with(&format!("{}/", prefix))
            }
            RoutePattern::Regex(regex) => regex.is_match(path),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheControlConfig {
    pub routes: FxHashMap<String, String>,
    pub static_files: String,
    pub server_components: String,
}

#[derive(Debug, Clone)]
struct CompiledCacheControlConfig {
    routes: Vec<(RoutePattern, String)>,
}

impl From<&CacheControlConfig> for CompiledCacheControlConfig {
    fn from(config: &CacheControlConfig) -> Self {
        let mut routes: Vec<(RoutePattern, String)> = config
            .routes
            .iter()
            .map(|(pattern, cache_control)| {
                (RoutePattern::from_pattern(pattern), cache_control.clone())
            })
            .collect();

        routes.sort_by(|(a, _), (b, _)| match (a, b) {
            (RoutePattern::Exact(_), RoutePattern::Exact(_)) => std::cmp::Ordering::Equal,
            (RoutePattern::Exact(_), _) => std::cmp::Ordering::Less,
            (_, RoutePattern::Exact(_)) => std::cmp::Ordering::Greater,
            (RoutePattern::Prefix(a_prefix), RoutePattern::Prefix(b_prefix)) => {
                b_prefix.len().cmp(&a_prefix.len())
            }
            (RoutePattern::Prefix(_), RoutePattern::Regex(_)) => std::cmp::Ordering::Less,
            (RoutePattern::Regex(_), RoutePattern::Prefix(_)) => std::cmp::Ordering::Greater,
            (RoutePattern::Regex(_), RoutePattern::Regex(_)) => std::cmp::Ordering::Equal,
        });

        CompiledCacheControlConfig { routes }
    }
}

impl Default for CacheControlConfig {
    fn default() -> Self {
        Self {
            routes: FxHashMap::default(),
            static_files: "public, max-age=31536000, immutable".to_string(),
            server_components: "public, max-age=31536000, stale-while-revalidate=86400".to_string(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RscHtmlConfig {
    pub enabled: bool,
    pub timeout_ms: u64,
    pub cache_template: bool,
    pub pretty_print: bool,
}

impl Default for RscHtmlConfig {
    fn default() -> Self {
        Self { enabled: true, timeout_ms: 5000, cache_template: true, pretty_print: false }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoadingConfig {
    pub enabled: bool,
    pub min_display_time_ms: u64,
    pub cache_loading_components: bool,
}

impl Default for LoadingConfig {
    fn default() -> Self {
        Self { enabled: true, min_display_time_ms: 200, cache_loading_components: true }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamingConfig {
    pub enabled: bool,
    pub buffer_size: usize,
    pub resolution_timeout_ms: u64,
}

impl Default for StreamingConfig {
    fn default() -> Self {
        Self { enabled: true, buffer_size: 64, resolution_timeout_ms: 5000 }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RscConfig {
    pub enable_ssr: bool,
    pub enable_streaming: bool,
    pub component_cache_size: usize,
    pub render_timeout_ms: u64,
    pub script_execution_timeout_ms: u64,
    pub enable_hot_reload: bool,
    pub hmr_reload_enabled: bool,
    pub hmr_max_retry_attempts: usize,
    pub hmr_reload_timeout_ms: u64,
    pub hmr_parallel_reloads: bool,
    pub hmr_debounce_delay_ms: u64,
    pub hmr_max_history_size: usize,
    pub hmr_enable_memory_monitoring: bool,
}

impl Default for RscConfig {
    fn default() -> Self {
        Self {
            enable_ssr: true,
            enable_streaming: true,
            component_cache_size: 2000,
            render_timeout_ms: 8000,
            script_execution_timeout_ms: 3000,
            enable_hot_reload: true,
            hmr_reload_enabled: true,
            hmr_max_retry_attempts: 3,
            hmr_reload_timeout_ms: 5000,
            hmr_parallel_reloads: true,
            hmr_debounce_delay_ms: 150,
            hmr_max_history_size: 100,
            hmr_enable_memory_monitoring: true,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Config {
    pub mode: Mode,
    pub server: ServerConfig,
    pub vite: ViteConfig,
    pub static_files: StaticConfig,
    pub rsc: RscConfig,
    pub rsc_html: RscHtmlConfig,
    pub caching: CacheControlConfig,
    pub loading: LoadingConfig,
    #[serde(default)]
    pub streaming: StreamingConfig,
    #[serde(default)]
    pub cors: CorsConfig,
    #[serde(default)]
    pub redirect: RedirectConfig,
    #[serde(default)]
    pub csp: CspConfig,
    #[serde(default)]
    pub rate_limit: RateLimitConfig,
    #[serde(default)]
    pub spam_blocker: SpamBlockerConfig,
    #[serde(default)]
    pub images: crate::server::image::ImageConfig,
}

impl Config {
    pub fn new(mode: Mode) -> Self {
        let default_config = Self::default();
        Self {
            mode,
            vite: ViteConfig {
                port: default_config.vite.port,
                host: default_config.server.host.clone(),
                ..default_config.vite
            },
            rsc: RscConfig {
                enable_hot_reload: mode != Mode::Production,
                hmr_reload_enabled: mode != Mode::Production,
                ..default_config.rsc
            },
            rsc_html: RscHtmlConfig {
                pretty_print: mode == Mode::Development,
                ..default_config.rsc_html
            },
            caching: CacheControlConfig {
                server_components: if mode == Mode::Production {
                    "public, max-age=31536000, stale-while-revalidate=86400".to_string()
                } else {
                    "no-cache, no-store, must-revalidate".to_string()
                },
                ..default_config.caching
            },
            ..default_config
        }
    }

    pub fn from_env() -> Result<Self, ConfigError> {
        Self::from_env_with_base(None)
    }

    pub fn from_env_with_base(base: Option<&std::path::Path>) -> Result<Self, ConfigError> {
        let mut config = Self::default();

        if let Ok(mode_str) = std::env::var("RARI_MODE") {
            config.mode = match mode_str.cow_to_lowercase().as_ref() {
                "development" | "dev" => Mode::Development,
                "production" | "prod" => Mode::Production,
                _ => return Err(ConfigError::Mode(mode_str)),
            };
        }

        if let Ok(host) = std::env::var("RARI_HOST") {
            config.server.host = host;
        }

        if let Ok(port_str) = std::env::var("RARI_PORT") {
            config.server.port = port_str.parse().map_err(|_| ConfigError::Port(port_str))?;
        }

        if let Ok(origin) = std::env::var("RARI_ORIGIN") {
            config.server.origin = Some(origin);
        }

        if let Ok(vite_host) = std::env::var("RARI_VITE_HOST") {
            config.vite.host = vite_host;
        }

        if let Ok(vite_port_str) = std::env::var("RARI_VITE_PORT") {
            config.vite.port =
                vite_port_str.parse().map_err(|_| ConfigError::VitePort(vite_port_str))?;
        }

        if let Ok(public_dir) = std::env::var("RARI_PUBLIC_DIR") {
            config.static_files.dev_public_dir = PathBuf::from(public_dir);
        }

        if let Ok(dist_dir) = std::env::var("RARI_DIST_DIR") {
            config.static_files.prod_public_dir = PathBuf::from(dist_dir);
        }

        if let Ok(timeout_str) = std::env::var("RARI_SCRIPT_EXECUTION_TIMEOUT_MS") {
            config.rsc.script_execution_timeout_ms =
                timeout_str.parse().map_err(|_| ConfigError::Timeout(timeout_str.clone()))?;
        }

        if let Ok(disable_hmr) = std::env::var("DISABLE_HMR_RELOAD") {
            config.rsc.hmr_reload_enabled = disable_hmr.cow_to_lowercase() != "true"
                && disable_hmr != "1"
                && disable_hmr.cow_to_lowercase() != "yes";
        }

        if let Ok(max_retry_str) = std::env::var("RARI_HMR_MAX_RETRY_ATTEMPTS") {
            config.rsc.hmr_max_retry_attempts = max_retry_str
                .parse()
                .map_err(|_| ConfigError::Config("RARI_HMR_MAX_RETRY_ATTEMPTS".to_string()))?;
        }

        if let Ok(timeout_str) = std::env::var("RARI_HMR_RELOAD_TIMEOUT_MS") {
            config.rsc.hmr_reload_timeout_ms = timeout_str
                .parse()
                .map_err(|_| ConfigError::Config("RARI_HMR_RELOAD_TIMEOUT_MS".to_string()))?;
        }

        if let Ok(parallel_str) = std::env::var("RARI_HMR_PARALLEL_RELOADS") {
            config.rsc.hmr_parallel_reloads = parallel_str.cow_to_lowercase() == "true"
                || parallel_str == "1"
                || parallel_str.cow_to_lowercase() == "yes";
        }

        if let Ok(debounce_str) = std::env::var("RARI_HMR_DEBOUNCE_DELAY_MS") {
            config.rsc.hmr_debounce_delay_ms = debounce_str
                .parse()
                .map_err(|_| ConfigError::Config("RARI_HMR_DEBOUNCE_DELAY_MS".to_string()))?;
        }

        if let Ok(history_size_str) = std::env::var("RARI_HMR_MAX_HISTORY_SIZE") {
            config.rsc.hmr_max_history_size = history_size_str
                .parse()
                .map_err(|_| ConfigError::Config("RARI_HMR_MAX_HISTORY_SIZE".to_string()))?;
        }

        if let Ok(memory_monitoring_str) = std::env::var("RARI_HMR_ENABLE_MEMORY_MONITORING") {
            config.rsc.hmr_enable_memory_monitoring = memory_monitoring_str.cow_to_lowercase()
                == "true"
                || memory_monitoring_str == "1"
                || memory_monitoring_str.cow_to_lowercase() == "yes";
        }

        if let Ok(rsc_html_enabled_str) = std::env::var("RARI_RSC_HTML_ENABLED") {
            config.rsc_html.enabled = rsc_html_enabled_str.cow_to_lowercase() == "true"
                || rsc_html_enabled_str == "1"
                || rsc_html_enabled_str.cow_to_lowercase() == "yes";
        }

        if let Ok(rsc_html_timeout_str) = std::env::var("RARI_RSC_HTML_TIMEOUT_MS") {
            config.rsc_html.timeout_ms = rsc_html_timeout_str
                .parse()
                .map_err(|_| ConfigError::Config("RARI_RSC_HTML_TIMEOUT_MS".to_string()))?;
        }

        if let Ok(rsc_html_cache_template_str) = std::env::var("RARI_RSC_HTML_CACHE_TEMPLATE") {
            config.rsc_html.cache_template = rsc_html_cache_template_str.cow_to_lowercase()
                == "true"
                || rsc_html_cache_template_str == "1"
                || rsc_html_cache_template_str.cow_to_lowercase() == "yes";
        }

        if let Ok(rsc_html_pretty_print_str) = std::env::var("RARI_RSC_HTML_PRETTY_PRINT") {
            config.rsc_html.pretty_print = rsc_html_pretty_print_str.cow_to_lowercase() == "true"
                || rsc_html_pretty_print_str == "1"
                || rsc_html_pretty_print_str.cow_to_lowercase() == "yes";
        }

        if let Ok(loading_enabled_str) = std::env::var("RARI_LOADING_ENABLED") {
            config.loading.enabled = loading_enabled_str.cow_to_lowercase() == "true"
                || loading_enabled_str == "1"
                || loading_enabled_str.cow_to_lowercase() == "yes";
        }

        if let Ok(min_display_time_str) = std::env::var("RARI_LOADING_MIN_DISPLAY_TIME_MS") {
            config.loading.min_display_time_ms = min_display_time_str
                .parse()
                .map_err(|_| ConfigError::Config("RARI_LOADING_MIN_DISPLAY_TIME_MS".to_string()))?;
        }

        if let Ok(cache_loading_str) = std::env::var("RARI_LOADING_CACHE_COMPONENTS") {
            config.loading.cache_loading_components = cache_loading_str.cow_to_lowercase()
                == "true"
                || cache_loading_str == "1"
                || cache_loading_str.cow_to_lowercase() == "yes";
        }

        if let Ok(streaming_enabled_str) = std::env::var("RARI_STREAMING_ENABLED") {
            config.streaming.enabled = streaming_enabled_str.cow_to_lowercase() == "true"
                || streaming_enabled_str == "1"
                || streaming_enabled_str.cow_to_lowercase() == "yes";
        }

        if let Ok(buffer_size_str) = std::env::var("RARI_STREAMING_BUFFER_SIZE") {
            config.streaming.buffer_size = buffer_size_str
                .parse()
                .map_err(|_| ConfigError::Config("RARI_STREAMING_BUFFER_SIZE".to_string()))?;
        }

        if let Ok(resolution_timeout_str) = std::env::var("RARI_STREAMING_RESOLUTION_TIMEOUT_MS") {
            config.streaming.resolution_timeout_ms =
                resolution_timeout_str.parse().map_err(|_| {
                    ConfigError::Config("RARI_STREAMING_RESOLUTION_TIMEOUT_MS".to_string())
                })?;
        }

        let config_path = match base {
            Some(b) => b.join("dist/server/config.json"),
            None => PathBuf::from("dist/server/config.json"),
        };

        if let Ok(server_config_json) = std::fs::read_to_string(&config_path) {
            let config_data = match serde_json::from_str::<serde_json::Value>(&server_config_json) {
                Ok(data) => data,
                Err(e) => {
                    tracing::warn!(
                        "Failed to parse dist/server/config.json: {}. Using defaults.",
                        e
                    );
                    return Ok(config);
                }
            };
            if let Some(csp_data) = config_data.get("csp") {
                if let Some(script_src) = csp_data.get("scriptSrc").and_then(|v| v.as_array()) {
                    config.csp.script_src = script_src
                        .iter()
                        .filter_map(|v| v.as_str().map(|s| s.to_string()))
                        .collect();
                }
                if let Some(style_src) = csp_data.get("styleSrc").and_then(|v| v.as_array()) {
                    config.csp.style_src = style_src
                        .iter()
                        .filter_map(|v| v.as_str().map(|s| s.to_string()))
                        .collect();
                }
                if let Some(img_src) = csp_data.get("imgSrc").and_then(|v| v.as_array()) {
                    config.csp.img_src =
                        img_src.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect();
                }
                if let Some(font_src) = csp_data.get("fontSrc").and_then(|v| v.as_array()) {
                    config.csp.font_src =
                        font_src.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect();
                }
                if let Some(connect_src) = csp_data.get("connectSrc").and_then(|v| v.as_array()) {
                    config.csp.connect_src = connect_src
                        .iter()
                        .filter_map(|v| v.as_str().map(|s| s.to_string()))
                        .collect();
                }
                if let Some(default_src) = csp_data.get("defaultSrc").and_then(|v| v.as_array()) {
                    config.csp.default_src = default_src
                        .iter()
                        .filter_map(|v| v.as_str().map(|s| s.to_string()))
                        .collect();
                }
                if let Some(worker_src) = csp_data.get("workerSrc").and_then(|v| v.as_array()) {
                    config.csp.worker_src = worker_src
                        .iter()
                        .filter_map(|v| v.as_str().map(|s| s.to_string()))
                        .collect();
                }
            }

            if let Some(rate_limit_data) = config_data.get("rateLimit") {
                if let Some(enabled) = rate_limit_data.get("enabled").and_then(|v| v.as_bool()) {
                    config.rate_limit.enabled = enabled;
                }
                if let Some(rps) = rate_limit_data.get("requestsPerSecond").and_then(|v| v.as_u64())
                {
                    config.rate_limit.requests_per_second = rps.try_into().unwrap_or_else(|_| {
                        tracing::warn!(
                            "requestsPerSecond value {} exceeds u32::MAX, using default",
                            rps
                        );
                        100
                    });
                }
                if let Some(burst) = rate_limit_data.get("burstSize").and_then(|v| v.as_u64()) {
                    config.rate_limit.burst_size = burst.try_into().unwrap_or_else(|_| {
                        tracing::warn!("burstSize value {} exceeds u32::MAX, using default", burst);
                        200
                    });
                }
                if let Some(revalidate_rpm) =
                    rate_limit_data.get("revalidateRequestsPerMinute").and_then(|v| v.as_u64())
                {
                    config.rate_limit.revalidate_requests_per_minute = revalidate_rpm.try_into().unwrap_or_else(|_| {
                        let default_value = default_revalidate_rpm();
                        tracing::warn!("revalidateRequestsPerMinute value {} exceeds u32::MAX, using default {}", revalidate_rpm, default_value);
                        default_value
                    });
                }
            }

            if let Some(spam_blocker_data) = config_data.get("spamBlocker")
                && let Some(enabled) = spam_blocker_data.get("enabled").and_then(|v| v.as_bool())
            {
                config.spam_blocker.enabled = enabled;
            }

            if let Some(cache_control_data) = config_data.get("cacheControl")
                && let Some(routes) = cache_control_data.get("routes").and_then(|v| v.as_object())
            {
                for (route, cache_value) in routes {
                    if let Some(cache_str) = cache_value.as_str() {
                        if HeaderValue::from_str(cache_str).is_ok() {
                            config.caching.routes.insert(route.clone(), cache_str.to_string());
                        } else {
                            tracing::warn!(
                                "Invalid cache-control header value for route '{}': '{}' (contains invalid characters)",
                                route,
                                cache_str
                            );
                        }
                    } else {
                        tracing::warn!(
                            "Invalid cache-control value type for route '{}': expected string, got {:?}",
                            route,
                            cache_value
                        );
                    }
                }
            }
        } else {
            tracing::debug!("No dist/server/config.json found, using defaults");
        }

        if config.mode == Mode::Development {
            config.caching.server_components = "no-cache, no-store, must-revalidate".to_string();
        }

        Ok(config)
    }

    pub fn get() -> Option<&'static Config> {
        GLOBAL_CONFIG.get()
    }

    pub fn set_global(config: Config) -> Result<(), Box<Config>> {
        GLOBAL_CONFIG.set(config).map_err(Box::new)
    }

    pub fn server_address(&self) -> String {
        format!("{}:{}", self.server.host, self.server.port)
    }

    pub fn vite_address(&self) -> String {
        format!("{}:{}", self.vite.host, self.vite.port)
    }

    pub fn public_dir(&self) -> &PathBuf {
        match self.mode {
            Mode::Development => &self.static_files.dev_public_dir,
            Mode::Production => &self.static_files.prod_public_dir,
        }
    }

    pub fn is_development(&self) -> bool {
        self.mode == Mode::Development
    }

    pub fn is_production(&self) -> bool {
        self.mode == Mode::Production
    }

    pub fn hmr_reload_enabled(&self) -> bool {
        self.is_development() && self.rsc.hmr_reload_enabled
    }

    pub fn cors_config(&self) -> CorsConfig {
        if !self.cors.allowed_origins.is_empty() {
            return self.cors.clone();
        }

        if self.is_development() {
            CorsConfig {
                allowed_origins: vec![
                    format!("http://{}:{}", self.server.host, self.server.port),
                    format!("http://localhost:{}", self.server.port),
                    format!("http://127.0.0.1:{}", self.server.port),
                    format!("http://{}:{}", self.vite.host, self.vite.port),
                    format!("http://localhost:{}", self.vite.port),
                ],
                allow_credentials: true,
                max_age: 86400,
            }
        } else {
            let allowed_origins = if let Some(origin) = &self.server.origin {
                vec![origin.clone()]
            } else {
                vec![format!("http://{}:{}", self.server.host, self.server.port)]
            };

            CorsConfig { allowed_origins, allow_credentials: true, max_age: 86400 }
        }
    }

    pub fn redirect_config(&self) -> RedirectConfig {
        if !self.redirect.allowed_hosts.is_empty() {
            return self.redirect.clone();
        }

        if self.is_development() {
            let mut allowed_hosts =
                vec!["localhost".to_string(), "127.0.0.1".to_string(), self.server.host.clone()];

            if let Some(origin) = &self.server.origin
                && let Ok(url) = url::Url::parse(origin)
                && let Some(host) = url.host_str()
            {
                allowed_hosts.push(host.to_string());
            }

            RedirectConfig { allowed_hosts, allow_relative: true, allow_subdomains: false }
        } else {
            let allowed_hosts = if let Some(origin) = &self.server.origin {
                if let Ok(url) = url::Url::parse(origin) {
                    if let Some(host) = url.host_str() { vec![host.to_string()] } else { vec![] }
                } else {
                    vec![]
                }
            } else {
                vec![self.server.host.clone()]
            };

            RedirectConfig { allowed_hosts, allow_relative: true, allow_subdomains: false }
        }
    }

    pub fn get_cache_control_for_route(&self, path: &str) -> &str {
        if let Some(cache_control) = self.caching.routes.get(path) {
            return cache_control;
        }

        let compiled = CompiledCacheControlConfig::from(&self.caching);

        for (pattern, cache_control) in &compiled.routes {
            if pattern.matches(path) {
                for orig_cache_control in self.caching.routes.values() {
                    if orig_cache_control == cache_control {
                        return orig_cache_control;
                    }
                }
            }
        }

        &self.caching.server_components
    }

    pub fn csp_config(&self) -> CspConfig {
        let mut config = self.csp.clone();

        if !config.script_src.contains(&"'unsafe-inline'".to_string()) {
            config.script_src.push("'unsafe-inline'".to_string());
        }

        if !config.style_src.contains(&"'unsafe-inline'".to_string()) {
            config.style_src.push("'unsafe-inline'".to_string());
        }

        if self.is_development() && !config.script_src.contains(&"'unsafe-eval'".to_string()) {
            config.script_src.push("'unsafe-eval'".to_string());
        }

        config
    }

    pub fn build_csp_policy(&self) -> String {
        let config = self.csp_config();
        let mut directives = Vec::new();

        if !config.default_src.is_empty() {
            directives.push(format!("default-src {}", config.default_src.join(" ")));
        }

        if !config.script_src.is_empty() {
            directives.push(format!("script-src {}", config.script_src.join(" ")));
        }

        if !config.style_src.is_empty() {
            directives.push(format!("style-src {}", config.style_src.join(" ")));
        }

        if !config.img_src.is_empty() {
            directives.push(format!("img-src {}", config.img_src.join(" ")));
        }

        if !config.font_src.is_empty() {
            directives.push(format!("font-src {}", config.font_src.join(" ")));
        }

        if !config.connect_src.is_empty() {
            directives.push(format!("connect-src {}", config.connect_src.join(" ")));
        }

        if !config.worker_src.is_empty() {
            directives.push(format!("worker-src {}", config.worker_src.join(" ")));
        }

        directives.join("; ")
    }
}

#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
    #[error("Invalid mode: {0}")]
    Mode(String),
    #[error("Invalid port: {0}")]
    Port(String),
    #[error("Invalid Vite port: {0}")]
    VitePort(String),
    #[error("Invalid timeout: {0}")]
    Timeout(String),
    #[error("Invalid config value for {0}")]
    Config(String),
}

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

    #[test]
    fn test_config_default() {
        let config = Config::default();
        assert_eq!(config.mode, Mode::Development);
        assert_eq!(config.server.host, "127.0.0.1");
        assert_eq!(config.server.port, 3000);
        assert_eq!(config.vite.port, 5173);
    }

    #[test]
    fn test_config_new_with_mode() {
        let config = Config::new(Mode::Production);
        assert_eq!(config.mode, Mode::Production);
        assert!(!config.rsc.enable_hot_reload);
    }

    #[test]
    fn test_server_address() {
        let config = Config::default();
        assert_eq!(config.server_address(), "127.0.0.1:3000");
    }

    #[test]
    fn test_vite_address() {
        let config = Config::default();
        assert_eq!(config.vite_address(), "localhost:5173");
    }

    #[test]
    fn test_public_dir() {
        let dev_config = Config::new(Mode::Development);
        assert_eq!(dev_config.public_dir(), &PathBuf::from("public"));

        let prod_config = Config::new(Mode::Production);
        assert_eq!(prod_config.public_dir(), &PathBuf::from("dist"));
    }

    #[test]
    fn test_mode_display() {
        assert_eq!(Mode::Development.to_string(), "development");
        assert_eq!(Mode::Production.to_string(), "production");
    }

    #[test]
    fn test_cache_control_exact_match() {
        let mut config = Config::default();
        config.caching.routes.insert("/api/users".to_string(), "no-cache".to_string());

        let cache_control = config.get_cache_control_for_route("/api/users");
        assert_eq!(cache_control, "no-cache");
    }

    #[test]
    fn test_cache_control_glob_pattern() {
        let mut config = Config::default();
        config.caching.routes.insert("/api/*".to_string(), "no-cache".to_string());

        let cache_control = config.get_cache_control_for_route("/api/users");
        assert_eq!(cache_control, "no-cache");

        let cache_control = config.get_cache_control_for_route("/api/products/123");
        assert_eq!(cache_control, "no-cache");
    }

    #[test]
    fn test_cache_control_default_fallback() {
        let config = Config::default();

        let cache_control = config.get_cache_control_for_route("/some/random/path");
        assert_eq!(cache_control, "public, max-age=31536000, stale-while-revalidate=86400");
    }

    #[test]
    fn test_cache_control_pattern_priority() {
        let mut config = Config::default();
        config.caching.routes.insert("/api/*".to_string(), "no-cache".to_string());
        config.caching.routes.insert("/api/public".to_string(), "public, max-age=3600".to_string());

        let cache_control = config.get_cache_control_for_route("/api/public");
        assert_eq!(cache_control, "public, max-age=3600");

        let cache_control = config.get_cache_control_for_route("/api/private");
        assert_eq!(cache_control, "no-cache");
    }

    #[test]
    fn test_pattern_matching() {
        let pattern = RoutePattern::from_pattern("/api/*");
        assert!(pattern.matches("/api/users"));
        assert!(pattern.matches("/api/products/123"));
        assert!(!pattern.matches("/blog/posts"));
        assert!(!pattern.matches("/apiv2/users"));
        assert!(pattern.matches("/api"));

        let pattern = RoutePattern::from_pattern("/blog/*/comments");
        assert!(pattern.matches("/blog/post-1/comments"));
        assert!(!pattern.matches("/blog/post-1"));

        let pattern = RoutePattern::from_pattern("*");
        assert!(pattern.matches("/any/path"));

        let pattern = RoutePattern::from_pattern("*.js");
        assert!(pattern.matches("/static/app.js"));
        assert!(!pattern.matches("/static/app.css"));

        let pattern = RoutePattern::from_pattern("/v1.0/*");
        assert!(pattern.matches("/v1.0/users"));
        assert!(!pattern.matches("/v1X0/users"));
    }

    #[test]
    fn test_cache_config_serialization() {
        let mut routes = FxHashMap::default();
        routes.insert("/api/*".to_string(), "no-cache".to_string());
        routes.insert("/blog/*".to_string(), "public, max-age=3600".to_string());

        let cache_config = CacheControlConfig {
            routes,
            static_files: "public, max-age=31536000, immutable".to_string(),
            server_components: "no-cache".to_string(),
        };

        let serialized = serde_json::to_string(&cache_config).unwrap();
        let deserialized: CacheControlConfig = serde_json::from_str(&serialized).unwrap();

        assert_eq!(cache_config.static_files, deserialized.static_files);
        assert_eq!(cache_config.server_components, deserialized.server_components);
        assert_eq!(cache_config.routes.len(), deserialized.routes.len());
    }

    #[test]
    fn test_streaming_config_default() {
        let streaming_config = StreamingConfig::default();
        assert!(streaming_config.enabled);
        assert_eq!(streaming_config.buffer_size, 64);
        assert_eq!(streaming_config.resolution_timeout_ms, 5000);
    }

    #[test]
    fn test_config_includes_streaming() {
        let config = Config::default();
        assert!(config.streaming.enabled);
        assert_eq!(config.streaming.buffer_size, 64);
        assert_eq!(config.streaming.resolution_timeout_ms, 5000);
    }

    #[test]
    fn test_streaming_config_serialization() {
        let streaming_config =
            StreamingConfig { enabled: false, buffer_size: 128, resolution_timeout_ms: 10000 };

        let serialized = serde_json::to_string(&streaming_config).unwrap();
        let deserialized: StreamingConfig = serde_json::from_str(&serialized).unwrap();

        assert_eq!(streaming_config.enabled, deserialized.enabled);
        assert_eq!(streaming_config.buffer_size, deserialized.buffer_size);
        assert_eq!(streaming_config.resolution_timeout_ms, deserialized.resolution_timeout_ms);
    }

    #[test]
    fn test_config_from_env_cache_control_validation() {
        use std::io::Write;

        let temp_dir = std::env::temp_dir().join(format!("rari_test_{}", std::process::id()));
        let dist_server_dir = temp_dir.join("dist").join("server");
        std::fs::create_dir_all(&dist_server_dir).unwrap();

        let config_json = serde_json::json!({
            "cacheControl": {
                "routes": {
                    "/valid": "public, max-age=3600",
                    "/invalid-newline": "public\nmax-age=3600",
                    "/invalid-type": 12345
                }
            }
        });

        let config_path = dist_server_dir.join("config.json");
        let mut file = std::fs::File::create(&config_path).unwrap();
        file.write_all(config_json.to_string().as_bytes()).unwrap();

        let result = Config::from_env_with_base(Some(&temp_dir));

        let _ = std::fs::remove_dir_all(&temp_dir);

        let config = result.unwrap();

        assert!(
            config.caching.routes.contains_key("/valid"),
            "Valid cache-control route should be accepted"
        );
        assert_eq!(config.caching.routes.get("/valid").unwrap(), "public, max-age=3600");

        assert!(
            !config.caching.routes.contains_key("/invalid-newline"),
            "Cache-control with newline should be rejected by HeaderValue::from_str"
        );
        assert!(
            !config.caching.routes.contains_key("/invalid-type"),
            "Non-string cache-control value should be rejected"
        );
    }

    #[test]
    fn test_dev_mode_cache_control() {
        let dev_config = Config::new(Mode::Development);
        assert_eq!(
            dev_config.caching.server_components, "no-cache, no-store, must-revalidate",
            "Development mode should use no-cache for server components"
        );

        let prod_config = Config::new(Mode::Production);
        assert_eq!(
            prod_config.caching.server_components,
            "public, max-age=31536000, stale-while-revalidate=86400",
            "Production mode should use long cache for server components"
        );
    }
}
