//! Routing utilities using trait-based dependency injection

use crate::*;
use std::collections::HashMap;

/// Generic language detector using configuration
///
/// Instead of hardcoded language detection, this uses configuration
/// to detect languages from paths.
pub struct ConfigurableLanguageDetector {
    /// Supported languages with their prefixes
    pub languages: Vec<LanguageConfig>,
    /// Default language
    pub default_language: String,
}

impl ConfigurableLanguageDetector {
    /// Create new language detector from site configuration
    pub fn from_site_config(site_config: &SiteConfig) -> Self {
        Self {
            languages: site_config.languages.clone(),
            default_language: site_config.default_language.clone(),
        }
    }

    /// Create new language detector with explicit configuration
    pub fn new(languages: Vec<LanguageConfig>, default_language: String) -> Self {
        Self {
            languages,
            default_language,
        }
    }
}

impl LanguageDetector for ConfigurableLanguageDetector {
    fn extract_language(&self, path: &str) -> (String, String) {
        // Remove leading slash for processing
        let clean_path = path.strip_prefix('/').unwrap_or(path);

        // Check if path starts with a language prefix
        for lang_config in &self.languages {
            if !lang_config.url_prefix.is_empty() && clean_path.starts_with(&lang_config.url_prefix) {
                let remaining_path = clean_path
                    .strip_prefix(&lang_config.url_prefix)
                    .unwrap_or("")
                    .strip_prefix('/')
                    .unwrap_or("")
                    .to_string();

                // Ensure path starts with / for consistency
                let normalized_path = if remaining_path.is_empty() {
                    "/".to_string()
                } else if remaining_path.starts_with('/') {
                    remaining_path
                } else {
                    format!("/{}", remaining_path)
                };

                return (lang_config.code.clone(), normalized_path);
            }
        }

        // No language prefix found, use default language
        let normalized_path = if path.starts_with('/') {
            path.to_string()
        } else {
            format!("/{}", path)
        };

        (self.default_language.clone(), normalized_path)
    }

    fn default_language(&self) -> &str {
        &self.default_language
    }

    fn supported_languages(&self) -> &[String] {
        static EMPTY: Vec<String> = Vec::new();
        // This is a limitation of the trait design - we need to return &[String]
        // but our data is Vec<LanguageConfig>. Implementations can override this.
        &EMPTY
    }
}

/// Generic route resolver using trait-based component lookup
///
/// Instead of generated route mappings, this uses the PageProvider trait
/// to resolve components dynamically.
pub struct GenericRouteResolver<P>
where
    P: PageProvider,
{
    /// Page provider for component resolution
    pub page_provider: P,
    /// Language detector for path processing
    pub language_detector: ConfigurableLanguageDetector,
}

impl<P> GenericRouteResolver<P>
where
    P: PageProvider,
{
    /// Create new route resolver with dependency injection
    pub fn new(page_provider: P, language_detector: ConfigurableLanguageDetector) -> Self {
        Self {
            page_provider,
            language_detector,
        }
    }

    /// Extract parameters from a route path
    ///
    /// This is a simple implementation - real implementations can provide
    /// more sophisticated parameter extraction.
    pub fn extract_parameters(&self, path: &str) -> HashMap<String, String> {
        let mut params = HashMap::new();

        // Simple query parameter extraction
        if let Some((_, query)) = path.split_once('?') {
            for param in query.split('&') {
                if let Some((key, value)) = param.split_once('=') {
                    params.insert(
                        key.to_string(),
                        urlencoding::decode(value).unwrap_or_default().to_string(),
                    );
                }
            }
        }

        params
    }

    /// Convert path to component ID
    ///
    /// This is a simple mapping - implementations can provide more
    /// sophisticated path-to-component resolution.
    pub fn path_to_component_id(&self, path: &str) -> String {
        // Remove leading/trailing slashes and convert to snake_case
        let clean_path = path.trim_matches('/');

        if clean_path.is_empty() {
            return "index".to_string();
        }

        // Convert path segments to component ID
        clean_path
            .split('/')
            .collect::<Vec<_>>()
            .join("_")
            .replace('-', "_")
    }
}

impl<P> RouteResolver for GenericRouteResolver<P>
where
    P: PageProvider,
{
    type Component = P::Component;
    type Parameters = HashMap<String, String>;

    fn resolve_route(&self, path: &str) -> RoutingResult<RouteResolution<Self::Component, Self::Parameters>> {
        // Extract language from path
        let (language, clean_path) = self.language_detector.extract_language(path);

        // Extract parameters
        let (path_without_params, _) = path.split_once('?').unwrap_or((path, ""));
        let parameters = self.extract_parameters(path);

        // Convert path to component ID
        let component_id = self.path_to_component_id(&clean_path);

        // Try to get component from provider
        let component = self.page_provider.get_component(&component_id).ok();

        // Determine canonical path
        let canonical_path = if language == self.language_detector.default_language {
            clean_path.clone()
        } else {
            // Add language prefix for non-default languages
            let lang_config = self.language_detector.languages
                .iter()
                .find(|l| l.code == language);

            if let Some(config) = lang_config {
                if config.url_prefix.is_empty() {
                    clean_path.clone()
                } else {
                    format!("/{}{}", config.url_prefix, clean_path)
                }
            } else {
                clean_path.clone()
            }
        };

        Ok(RouteResolution {
            component,
            path: clean_path,
            language,
            canonical_path,
            parameters,
            is_language_specific: language != self.language_detector.default_language,
        })
    }

    fn get_routes_for_language(&self, language: &str) -> RoutingResult<Vec<String>> {
        // Get all available components from the page provider
        let components = self.page_provider.list_components();

        // Convert component IDs back to paths
        let mut routes = Vec::new();
        for component_id in components {
            let path = if component_id == "index" {
                "/".to_string()
            } else {
                format!("/{}", component_id.replace('_', "/"))
            };

            // Add language prefix if not default language
            let route = if language == self.language_detector.default_language {
                path
            } else {
                let lang_config = self.language_detector.languages
                    .iter()
                    .find(|l| l.code == language);

                if let Some(config) = lang_config {
                    if config.url_prefix.is_empty() {
                        path
                    } else {
                        format!("/{}{}", config.url_prefix, path)
                    }
                } else {
                    path
                }
            };

            routes.push(route);
        }

        Ok(routes)
    }
}

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

    struct MockPageProvider;

    impl PageProvider for MockPageProvider {
        type Component = String;
        type View = String;
        type Props = ();

        fn get_component(&self, component_id: &str) -> ComponentResult<Self::Component> {
            match component_id {
                "index" | "about" | "contact" => Ok(component_id.to_string()),
                _ => Err(ComponentError::NotFound(component_id.to_string())),
            }
        }

        fn render_component(
            &self,
            component: &Self::Component,
            _props: Self::Props,
            _language: &str,
        ) -> ComponentResult<Self::View> {
            Ok(format!("Rendered: {}", component))
        }

        fn list_components(&self) -> Vec<String> {
            vec!["index".to_string(), "about".to_string(), "contact".to_string()]
        }
    }

    fn create_test_language_detector() -> ConfigurableLanguageDetector {
        let languages = vec![
            LanguageConfig {
                code: "en".to_string(),
                name: "English".to_string(),
                english_name: "English".to_string(),
                direction: TextDirection::LeftToRight,
                is_default: true,
                is_enabled: true,
                url_prefix: "".to_string(), // Default language has no prefix
            },
            LanguageConfig {
                code: "es".to_string(),
                name: "Español".to_string(),
                english_name: "Spanish".to_string(),
                direction: TextDirection::LeftToRight,
                is_default: false,
                is_enabled: true,
                url_prefix: "es".to_string(),
            },
        ];

        ConfigurableLanguageDetector::new(languages, "en".to_string())
    }

    #[test]
    fn test_language_detection() {
        let detector = create_test_language_detector();

        // Test default language (no prefix)
        let (lang, path) = detector.extract_language("/about");
        assert_eq!(lang, "en");
        assert_eq!(path, "/about");

        // Test Spanish language (with prefix)
        let (lang, path) = detector.extract_language("/es/about");
        assert_eq!(lang, "es");
        assert_eq!(path, "/about");

        // Test root paths
        let (lang, path) = detector.extract_language("/");
        assert_eq!(lang, "en");
        assert_eq!(path, "/");

        let (lang, path) = detector.extract_language("/es/");
        assert_eq!(lang, "es");
        assert_eq!(path, "/");
    }

    #[test]
    fn test_route_resolution() {
        let page_provider = MockPageProvider;
        let language_detector = create_test_language_detector();
        let resolver = GenericRouteResolver::new(page_provider, language_detector);

        // Test successful route resolution
        let result = resolver.resolve_route("/about").unwrap();
        assert!(result.component.is_some());
        assert_eq!(result.language, "en");
        assert_eq!(result.path, "/about");
        assert!(!result.is_language_specific);

        // Test Spanish route resolution
        let result = resolver.resolve_route("/es/about").unwrap();
        assert!(result.component.is_some());
        assert_eq!(result.language, "es");
        assert_eq!(result.path, "/about");
        assert!(result.is_language_specific);

        // Test not found route
        let result = resolver.resolve_route("/nonexistent").unwrap();
        assert!(result.component.is_none());
        assert_eq!(result.language, "en");
        assert_eq!(result.path, "/nonexistent");
    }

    #[test]
    fn test_parameter_extraction() {
        let page_provider = MockPageProvider;
        let language_detector = create_test_language_detector();
        let resolver = GenericRouteResolver::new(page_provider, language_detector);

        let params = resolver.extract_parameters("/about?name=test&id=123");
        assert_eq!(params.get("name"), Some(&"test".to_string()));
        assert_eq!(params.get("id"), Some(&"123".to_string()));
    }

    #[test]
    fn test_path_to_component_id() {
        let page_provider = MockPageProvider;
        let language_detector = create_test_language_detector();
        let resolver = GenericRouteResolver::new(page_provider, language_detector);

        assert_eq!(resolver.path_to_component_id("/"), "index");
        assert_eq!(resolver.path_to_component_id("/about"), "about");
        assert_eq!(resolver.path_to_component_id("/blog/post"), "blog_post");
        assert_eq!(resolver.path_to_component_id("/my-page"), "my_page");
    }
}

// Re-export for convenience
pub use urlencoding;
