//! Hosting Router - Route requests to content based on host
//!
//! Handles subdomain-based routing, SPA support, and custom domains.

use crate::registry::NameRegistry;
use crate::domain::DomainVerifier;
use crate::error::{Error, Result};
use synor_storage::ContentId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Route configuration for a hosted site
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RouteConfig {
    /// Fallback routes (pattern -> target)
    /// e.g., "/*" -> "/index.html" for SPAs
    pub routes: HashMap<String, String>,
    /// Custom headers (pattern -> headers)
    pub headers: HashMap<String, HashMap<String, String>>,
    /// Redirects (from -> (to, status))
    pub redirects: Vec<Redirect>,
    /// Error pages (status -> path)
    pub error_pages: HashMap<u16, String>,
}

impl Default for RouteConfig {
    fn default() -> Self {
        let mut routes = HashMap::new();
        // Default SPA routing
        routes.insert("/*".to_string(), "/index.html".to_string());

        Self {
            routes,
            headers: HashMap::new(),
            redirects: Vec::new(),
            error_pages: HashMap::new(),
        }
    }
}

/// Redirect rule
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Redirect {
    /// Source path (can include wildcards)
    pub from: String,
    /// Target path or URL
    pub to: String,
    /// HTTP status code (301, 302, 307, 308)
    pub status: u16,
}

/// Resolved route result
#[derive(Debug, Clone)]
pub struct ResolvedRoute {
    /// CID of the content
    pub cid: ContentId,
    /// Path within the content
    pub path: String,
    /// Additional headers to set
    pub headers: HashMap<String, String>,
    /// Is this a fallback route (SPA)?
    pub is_fallback: bool,
}

/// Hosting router
pub struct HostingRouter {
    /// Synor hosting domain (e.g., "synor.network")
    hosting_domain: String,
    /// Name registry for subdomain resolution
    registry: NameRegistry,
    /// Domain verifier for custom domains
    verifier: DomainVerifier,
    /// Per-name route configurations
    configs: HashMap<String, RouteConfig>,
    /// Current block number (for expiry checks)
    current_block: u64,
}

impl HostingRouter {
    /// Create a new hosting router
    pub fn new(hosting_domain: String) -> Self {
        Self {
            hosting_domain: hosting_domain.clone(),
            registry: NameRegistry::new(),
            verifier: DomainVerifier::new(hosting_domain),
            configs: HashMap::new(),
            current_block: 0,
        }
    }

    /// Set current block number
    pub fn set_block(&mut self, block: u64) {
        self.current_block = block;
    }

    /// Get the name registry
    pub fn registry(&self) -> &NameRegistry {
        &self.registry
    }

    /// Get mutable name registry
    pub fn registry_mut(&mut self) -> &mut NameRegistry {
        &mut self.registry
    }

    /// Get the domain verifier
    pub fn verifier(&self) -> &DomainVerifier {
        &self.verifier
    }

    /// Get mutable domain verifier
    pub fn verifier_mut(&mut self) -> &mut DomainVerifier {
        &mut self.verifier
    }

    /// Set route configuration for a name
    pub fn set_config(&mut self, name: &str, config: RouteConfig) {
        self.configs.insert(name.to_string(), config);
    }

    /// Route a request based on host and path
    pub fn route(&self, host: &str, path: &str) -> Result<ResolvedRoute> {
        // Parse host to determine name
        let name = self.parse_host(host)?;

        // Resolve name to CID
        let cid = self.registry.resolve(&name, self.current_block)
            .ok_or_else(|| Error::NameNotFound(name.clone()))?;

        // Get route config (or default)
        let config = self.configs.get(&name).cloned().unwrap_or_default();

        // Check for redirects
        if let Some(redirect) = self.find_redirect(path, &config.redirects) {
            return Err(Error::Redirect {
                to: redirect.to.clone(),
                status: redirect.status,
            });
        }

        // Resolve path
        let (resolved_path, is_fallback) = self.resolve_path(path, &config);

        // Get headers for this path
        let headers = self.get_headers(&resolved_path, &config);

        Ok(ResolvedRoute {
            cid,
            path: resolved_path,
            headers,
            is_fallback,
        })
    }

    /// Parse host header to extract name
    fn parse_host(&self, host: &str) -> Result<String> {
        // Remove port if present
        let host = host.split(':').next().unwrap_or(host);

        // Check if it's a subdomain of hosting domain
        if let Some(subdomain) = host.strip_suffix(&format!(".{}", self.hosting_domain)) {
            // Validate subdomain is a valid name
            crate::validate_name(subdomain)?;
            return Ok(subdomain.to_string());
        }

        // Check if it's a custom domain
        if let Some(name) = self.registry.resolve_domain(host) {
            // Verify domain is still verified
            if self.verifier.is_verified(host, self.current_block) {
                return Ok(name.to_string());
            } else {
                return Err(Error::DomainNotVerified(host.to_string()));
            }
        }

        Err(Error::UnknownHost(host.to_string()))
    }

    /// Resolve path using route config
    fn resolve_path(&self, path: &str, config: &RouteConfig) -> (String, bool) {
        // Normalize path
        let path = if path.is_empty() || path == "/" {
            "/index.html".to_string()
        } else {
            path.to_string()
        };

        // Check for exact match in routes (would be file existence check in real impl)
        // For now, assume files with extensions exist, others need fallback
        if path.contains('.') {
            return (path, false);
        }

        // Check route patterns
        for (pattern, target) in &config.routes {
            if self.matches_pattern(&path, pattern) {
                return (target.clone(), true);
            }
        }

        // Default: return path as-is
        (path, false)
    }

    /// Check if path matches a route pattern
    fn matches_pattern(&self, path: &str, pattern: &str) -> bool {
        if pattern == "/*" {
            return true;
        }

        if pattern.ends_with("/*") {
            let prefix = pattern.trim_end_matches("/*");
            return path.starts_with(prefix);
        }

        path == pattern
    }

    /// Find matching redirect
    fn find_redirect<'a>(&self, path: &str, redirects: &'a [Redirect]) -> Option<&'a Redirect> {
        for redirect in redirects {
            if self.matches_pattern(path, &redirect.from) {
                return Some(redirect);
            }
        }
        None
    }

    /// Get headers for a path
    fn get_headers(&self, path: &str, config: &RouteConfig) -> HashMap<String, String> {
        let mut headers = HashMap::new();

        // Find matching header patterns
        for (pattern, pattern_headers) in &config.headers {
            if self.matches_pattern(path, pattern) {
                headers.extend(pattern_headers.clone());
            }
        }

        headers
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::registry::RegistrationRequest;
    use synor_storage::ContentId;

    fn make_cid(data: &[u8]) -> ContentId {
        ContentId::from_content(data)
    }

    #[test]
    fn test_route_subdomain() {
        let mut router = HostingRouter::new("synor.network".to_string());
        let owner = [1u8; 32];
        let cid = make_cid(b"my app content");

        // Register name
        let request = RegistrationRequest {
            name: "myapp".to_string(),
            cid: cid.clone(),
            duration: 1000,
            metadata: None,
        };
        router.registry_mut().register(request, owner, 0).unwrap();

        // Route request
        let resolved = router.route("myapp.synor.network", "/").unwrap();
        assert_eq!(resolved.cid.digest, cid.digest);
        assert_eq!(resolved.path, "/index.html");
    }

    #[test]
    fn test_spa_routing() {
        let mut router = HostingRouter::new("synor.network".to_string());
        let owner = [1u8; 32];
        let cid = make_cid(b"spa content");

        let request = RegistrationRequest {
            name: "myapp".to_string(),
            cid: cid.clone(),
            duration: 1000,
            metadata: None,
        };
        router.registry_mut().register(request, owner, 0).unwrap();

        // Deep path should fallback to index.html
        let resolved = router.route("myapp.synor.network", "/dashboard/settings").unwrap();
        assert_eq!(resolved.path, "/index.html");
        assert!(resolved.is_fallback);
    }

    #[test]
    fn test_unknown_host() {
        let router = HostingRouter::new("synor.network".to_string());

        let result = router.route("unknown.synor.network", "/");
        assert!(result.is_err());
    }
}
