//! Language-agnostic detection from HTTP headers and URL analysis
//!
//! This module provides language detection that automatically works with any
//! languages discovered from content/locales configuration, making it fully
//! language agnostic.

use axum::http::HeaderMap;
use rustelo_core_lib::i18n::language_config::get_language_registry;

/// Extract language preferences from Accept-Language header
pub fn parse_accept_language(headers: &HeaderMap) -> Vec<(String, f32)> {
    let header = match headers.get("accept-language") {
        Some(h) => h,
        None => {
            let registry = get_language_registry();
            return vec![(registry.default_language.clone(), 1.0)];
        }
    };

    let header_str = match header.to_str() {
        Ok(s) => s,
        Err(_) => {
            let registry = get_language_registry();
            return vec![(registry.default_language.clone(), 1.0)];
        }
    };

    let mut languages = Vec::new();

    for lang_range in header_str.split(',') {
        let parts: Vec<&str> = lang_range.trim().split(';').collect();
        let lang_code = parts[0].trim().to_lowercase();

        // Extract quality value (q=0.8) or default to 1.0
        let quality = if parts.len() > 1 {
            parts[1]
                .trim()
                .strip_prefix("q=")
                .and_then(|q| q.parse::<f32>().ok())
                .unwrap_or(1.0)
        } else {
            1.0
        };

        // Normalize language codes - extract base language and check if supported
        let base_lang = lang_code.split('-').next().unwrap_or(&lang_code);
        let registry = get_language_registry();
        let normalized_lang = if registry.languages.contains_key(base_lang) {
            base_lang.to_string()
        } else {
            // If not supported, keep the original code for fallback processing
            lang_code
        };

        languages.push((normalized_lang, quality));
    }

    // Sort by quality (highest first)
    languages.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));

    // If empty, default to system default language
    if languages.is_empty() {
        let registry = get_language_registry();
        languages.push((registry.default_language.clone(), 1.0));
    }

    languages
}

/// Get the preferred language from Accept-Language header
pub fn get_preferred_language(headers: &HeaderMap, supported: &[&str]) -> String {
    let languages = parse_accept_language(headers);

    for (lang, _quality) in languages {
        if supported.contains(&lang.as_str()) {
            return lang;
        }
    }

    // Default to system default language
    let registry = get_language_registry();
    registry.default_language.clone()
}

/// Detect language from URL path using dynamic route configuration
pub fn detect_language_from_path(path: &str) -> Option<String> {
    let registry = get_language_registry();

    // Check for explicit language prefix (/en, /es, etc.)
    for lang_code in registry.get_available_languages() {
        let prefix = format!("/{}", lang_code);
        if path.starts_with(&prefix) {
            return Some(lang_code);
        }
    }

    // Check for language-specific routes using configuration
    registry
        .get_available_languages()
        .into_iter()
        .find(|lang_code| is_language_specific_route(path, lang_code))
}

/// Check if a route is specific to a given language using route configuration
pub fn is_language_specific_route(path: &str, language: &str) -> bool {
    // Use the routing utils as they properly handle the routes.toml configuration
    rustelo_core_lib::routing::utils::is_lang_specific_route(path, language)
}

/// Get cookie value for language preference
pub fn get_language_from_cookie(headers: &HeaderMap) -> Option<String> {
    let cookie_header = headers.get("cookie")?;
    let cookie_str = cookie_header.to_str().ok()?;

    for cookie in cookie_str.split(';') {
        let parts: Vec<&str> = cookie.trim().splitn(2, '=').collect();
        if parts.len() == 2 && parts[0] == "preferred_language" {
            let lang = parts[1].trim_matches('"');
            let registry = get_language_registry();
            if registry.languages.contains_key(lang) {
                return Some(lang.to_string());
            }
        }
    }

    None
}

/// Complete language detection strategy for SSR
pub fn detect_ssr_language(path: &str, headers: &HeaderMap) -> String {
    let registry = get_language_registry();
    let available_languages = registry.get_available_languages();
    let supported_languages: Vec<&str> = available_languages.iter().map(|s| s.as_str()).collect();

    // 1. Check URL path first (explicit user choice)
    if let Some(url_lang) = detect_language_from_path(path) {
        return url_lang;
    }

    // 2. Check cookie (previous user preference)
    if let Some(cookie_lang) = get_language_from_cookie(headers) {
        return cookie_lang;
    }

    // 3. Use Accept-Language header (browser preference)
    get_preferred_language(headers, &supported_languages)
}

/// Check if we need to redirect to localized version for any non-default
/// language Returns Some(redirect_url) if redirect is needed, None otherwise
pub fn check_language_redirect(
    path: &str,
    detected_language: &str,
    _headers: &HeaderMap,
) -> Option<String> {
    let registry = get_language_registry();
    let default_language = registry.get_default_language();

    // No redirect needed for default language
    if detected_language == default_language {
        return None;
    }

    // Check if path already has language prefix
    let language_prefix = format!("/{}", detected_language);
    if path.starts_with(&language_prefix) {
        return None;
    }

    // Check if this route is already language-specific
    if is_language_specific_route(path, detected_language) {
        return None;
    }

    // Generate localized URL
    let localized_url = get_localized_url(path, detected_language);
    if localized_url != path {
        Some(localized_url)
    } else {
        None
    }
}

/// Create appropriate redirect URL based on language using route definitions
pub fn get_localized_url(path: &str, target_language: &str) -> String {
    let registry = get_language_registry();
    let default_language = registry.get_default_language();

    // Remove any existing language prefix from path
    let mut clean_path = path;
    for lang_code in registry.get_available_languages() {
        let prefix = format!("/{}", lang_code);
        if let Some(stripped) = path.strip_prefix(&prefix) {
            clean_path = if stripped.is_empty() { "/" } else { stripped };
            break;
        }
    }

    // Resolve via route configuration — handles slug-based localization in both
    // directions (e.g. /proyectos→EN = /projects, /projects→ES = /proyectos).
    // The previous early-return for default_language bypassed get_localized_route,
    // which incorrectly returned the ES slug unchanged when switching to EN.
    let localized =
        rustelo_core_lib::routing::utils::get_localized_route(clean_path, target_language);
    if localized != clean_path {
        return localized;
    }

    // localized == clean_path can mean two different things:
    //   (a) clean_path is a SHARED route valid in both languages (e.g. /blog with
    //       en="/blog", es="/blog") — return it unchanged, no /lang prefix.
    //   (b) clean_path has no route mapping at all — fall back to /lang prefix.
    // Distinguish by checking whether clean_path is an enabled route. The site
    // uses localized slugs + cookie-based language, not /lang prefixes, so shared
    // routes must stay prefix-free (otherwise /es/blog 404s).
    if route_path_is_configured(clean_path) {
        return clean_path.to_string();
    }

    // Fallback when no route mapping exists: default language needs no prefix;
    // non-default language uses a language-prefix URL so the shell can detect it.
    if target_language == default_language {
        clean_path.to_string()
    } else if clean_path == "/" {
        format!("/{}", target_language)
    } else {
        format!("/{}{}", target_language, clean_path)
    }
}

/// Check whether `path` matches any enabled route's static prefix in the
/// config.
///
/// Used to tell shared-URL routes (valid in every language, e.g. `/blog`) apart
/// from truly-unknown paths. Matches both exact static routes and the static
/// prefix of parametric routes (`/blog/{category}` matches `/blog/devops`).
fn route_path_is_configured(path: &str) -> bool {
    let config = rustelo_core_lib::routing::utils::load_routes_config();
    config.routes.iter().any(|route| {
        if !route.enabled {
            return false;
        }
        if route.path == path {
            return true;
        }
        // Parametric route: compare the static prefix before the first `{`.
        let static_prefix = route
            .path
            .split('{')
            .next()
            .unwrap_or("")
            .trim_end_matches('/');
        !static_prefix.is_empty()
            && static_prefix != "/"
            && (path == static_prefix || path.starts_with(&format!("{static_prefix}/")))
    })
}

#[cfg(test)]
mod tests {
    use axum::http::HeaderValue;

    use super::*;

    fn create_headers(accept_lang: &str) -> HeaderMap {
        let mut headers = HeaderMap::new();
        if let Ok(value) = HeaderValue::from_str(accept_lang) {
            headers.insert("accept-language", value);
        }
        headers
    }

    #[test]
    fn test_parse_accept_language() {
        let headers = create_headers("es-ES,es;q=0.9,en-US;q=0.8,en;q=0.7");
        let languages = parse_accept_language(&headers);

        // Test that languages are parsed and sorted correctly by quality
        assert!(languages.len() >= 3);
        assert!(languages[0].1 >= languages[1].1); // Quality should be descending

        // Test that base language codes are extracted
        let lang_codes: Vec<&str> = languages.iter().map(|(code, _)| code.as_str()).collect();
        assert!(lang_codes.contains(&"es") || lang_codes.contains(&"en"));
    }

    #[test]
    fn test_get_preferred_language() {
        // Test with available languages from registry
        let registry = get_language_registry();
        let binding = registry.get_available_languages();
        let available_langs: Vec<&str> = binding.iter().map(|s| s.as_str()).collect();

        let headers = create_headers("es-ES,es;q=0.9,en;q=0.8");
        let preferred = get_preferred_language(&headers, &available_langs);
        assert!(registry.languages.contains_key(&preferred));

        // Test fallback behavior
        let headers = create_headers("fr,de;q=0.8"); // Unsupported languages
        let preferred = get_preferred_language(&headers, &available_langs);
        assert_eq!(preferred, registry.default_language);
    }

    #[test]
    fn test_detect_language_from_path() {
        let registry = get_language_registry();
        let available_languages = registry.get_available_languages();

        // Test explicit language prefixes
        for lang in available_languages {
            let path = format!("/{}/about", lang);
            assert_eq!(detect_language_from_path(&path), Some(lang.clone()));
        }

        // Test language-specific routes (these depend on routing configuration)
        // The exact behavior depends on the routes.toml configuration
        assert_eq!(detect_language_from_path("/about"), None); // Should be None for neutral paths
        assert_eq!(detect_language_from_path("/"), None);
    }

    #[test]
    fn test_detect_ssr_language() {
        let registry = get_language_registry();
        let headers = create_headers("es-ES,en;q=0.8");

        // Test that detected language is always a supported language
        let result = detect_ssr_language("/about", &headers);
        assert!(registry.languages.contains_key(&result));

        // Test URL takes priority over headers
        for lang in registry.get_available_languages() {
            let path = format!("/{}/about", lang);
            let result = detect_ssr_language(&path, &headers);
            assert_eq!(result, lang.clone());
        }
    }

    #[test]
    fn test_get_localized_url() {
        let registry = get_language_registry();

        // Test that localized URLs are generated for available languages
        for target_lang in registry.get_available_languages() {
            let localized = get_localized_url("/about", &target_lang);
            // The exact result depends on routes.toml configuration,
            // but it should be a valid URL string
            assert!(!localized.is_empty());
            assert!(localized.starts_with('/'));
        }

        // Test path cleaning (removing existing language prefixes)
        for source_lang in registry.get_available_languages() {
            let path_with_prefix = format!("/{}/about", source_lang);
            for target_lang in registry.get_available_languages() {
                let result = get_localized_url(&path_with_prefix, &target_lang);
                assert!(!result.is_empty());
            }
        }
    }
}
