//! Content File System Watcher
//!
//! Provides hot reloading functionality for content files by monitoring
//! the content directory and automatically rebuilding filter indexes.

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Duration, SystemTime};

#[cfg(feature = "content-watcher")]
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use tokio::sync::{mpsc, RwLock};
use tokio::time::Instant;
use tracing::{debug, error, info, warn};

use super::ContentService;

/// Content change event with category support
#[derive(Debug, Clone)]
pub struct ContentChangeEvent {
    pub path: PathBuf,
    pub content_type: ContentType,
    pub language: String,
    pub category: Option<String>,
    pub post_id: Option<String>,
    pub event_type: ContentEventType,
    pub timestamp: SystemTime,
}

/// Types of content change events
#[derive(Debug, Clone, PartialEq)]
pub enum ContentEventType {
    PostChanged,
    PostAdded,
    PostRemoved,
    PostMoved {
        from_category: String,
        to_category: String,
    },
    CategoryMetaChanged,
    GlobalMetaChanged,
    TranslationAdded,
    TranslationRemoved,
}

pub use rustelo_core_lib::fluent::ContentType;

/// Helper functions for content type operations
pub struct ContentTypeHelper;

impl ContentTypeHelper {
    /// Dynamically determine content type from file path using directory
    /// structure This replaces hardcoded content type detection with
    /// dynamic discovery
    pub fn from_path(path: &Path) -> ContentType {
        let path_str = path.to_string_lossy();

        // Extract content type from path structure like: content/TYPE/language/file.md
        let content_root =
            std::env::var("SITE_CONTENT_PATH").unwrap_or_else(|_| "site/content".to_string());

        // Try to parse path as content_root/content_type/language/file.md
        if let Some(relative_path) = path_str.strip_prefix(&format!("{}/", content_root)) {
            if let Some(first_slash) = relative_path.find('/') {
                let content_type = &relative_path[..first_slash];

                // Skip special directories that aren't content types
                if !content_type.starts_with('.')
                    && !content_type.starts_with('_')
                    && content_type != "locales"
                    && content_type != "themes"
                    && content_type != "routes"
                    && content_type != "footer"
                {
                    return ContentType::from(content_type.to_string());
                }
            }
        }

        // Fallback: try to detect from any content type in the path
        if let Ok(available_types) = get_available_content_types() {
            for content_type in available_types {
                if path_str.contains(&format!("/{}/", content_type)) {
                    return ContentType::from(content_type);
                }
            }
        }

        ContentType::from("unknown".to_string())
    }

    /// Extract category from path: content/TYPE/LANG/CATEGORY/file.md
    pub fn extract_category_from_path(path: &Path) -> Option<String> {
        let path_str = path.to_string_lossy();
        let content_root =
            std::env::var("SITE_CONTENT_PATH").unwrap_or_else(|_| "site/content".to_string());

        if let Some(relative_path) = path_str.strip_prefix(&format!("{}/", content_root)) {
            let parts: Vec<&str> = relative_path.split('/').collect();

            // Expected structure: content_type/language/category/file.md
            if parts.len() >= 3 {
                let category = parts[2];

                // Skip special directories
                if !category.starts_with('.')
                    && !category.starts_with('_')
                    && category != "meta.toml"
                {
                    return Some(category.to_string());
                }
            }
        }

        None
    }

    /// Extract post ID from markdown filename
    pub fn extract_post_id_from_path(path: &Path) -> Option<String> {
        if let Some(file_name) = path.file_name() {
            if let Some(name_str) = file_name.to_str() {
                if name_str.ends_with(".md") && name_str != "meta.toml" {
                    return Some(name_str.trim_end_matches(".md").to_string());
                }
            }
        }
        None
    }

    /// Extract language from path: content/TYPE/LANG/...
    pub fn extract_language_from_path(path: &Path) -> Option<String> {
        let path_str = path.to_string_lossy();
        let content_root =
            std::env::var("SITE_CONTENT_PATH").unwrap_or_else(|_| "site/content".to_string());

        if let Some(relative_path) = path_str.strip_prefix(&format!("{}/", content_root)) {
            let parts: Vec<&str> = relative_path.split('/').collect();

            // Expected structure: content_type/language/...
            if parts.len() >= 2 {
                let language = parts[1];

                // Basic validation: language codes are typically 2-3 characters
                if language.len() >= 2 && language.len() <= 5 && !language.starts_with('.') {
                    return Some(language.to_string());
                }
            }
        }

        None
    }
}

/// Get available content types for dynamic detection
fn get_available_content_types() -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let content_root =
        std::env::var("SITE_CONTENT_PATH").unwrap_or_else(|_| "site/content".to_string());
    let config_path = format!("{}/content-kinds.toml", content_root);

    // Try to read content-kinds.toml
    if let Ok(config_content) = std::fs::read_to_string(&config_path) {
        if let Ok(config) = toml::from_str::<toml::Value>(&config_content) {
            let mut content_types = Vec::new();

            if let Some(content_kinds) = config.get("content_kinds") {
                if let Some(kinds_array) = content_kinds.as_array() {
                    for kind in kinds_array {
                        if let Some(kind_table) = kind.as_table() {
                            let enabled = kind_table
                                .get("enabled")
                                .and_then(|v| v.as_bool())
                                .unwrap_or(true);
                            if enabled {
                                if let Some(name) = kind_table.get("name").and_then(|v| v.as_str())
                                {
                                    content_types.push(name.to_string());
                                }
                            }
                        }
                    }
                }
            }

            if !content_types.is_empty() {
                return Ok(content_types);
            }
        }
    }

    // Fallback: scan directory structure
    Ok(vec!["content".to_string()])
}

/// Content watcher service that monitors file system changes
/// Production-safe with error recovery, rate limiting, and circuit breaker
/// patterns
pub struct ContentWatcher {
    content_root: PathBuf,
    rebuild_script: PathBuf,
    cache_timestamps: Arc<RwLock<HashMap<String, SystemTime>>>,
    event_sender: mpsc::UnboundedSender<ContentChangeEvent>,
    content_service: Option<Arc<ContentService>>,
    /// Rate limiting for rebuild operations
    last_rebuild_time: Arc<RwLock<Instant>>,
    /// Circuit breaker for consecutive failures
    consecutive_failures: Arc<RwLock<u32>>,
    /// Configuration for production deployment
    config: WatcherConfig,
}

/// Configuration for production-safe content watcher
#[derive(Debug, Clone)]
pub struct WatcherConfig {
    /// Minimum time between rebuilds (rate limiting)
    pub min_rebuild_interval: Duration,
    /// Maximum consecutive failures before circuit break
    pub max_consecutive_failures: u32,
    /// Circuit breaker cooldown period
    pub circuit_breaker_cooldown: Duration,
    /// Maximum events to process in a single batch
    pub max_batch_size: usize,
    /// Enable/disable production optimizations
    pub production_mode: bool,
    /// Enable/disable telemetry collection
    pub enable_telemetry: bool,
}

impl Default for WatcherConfig {
    fn default() -> Self {
        let is_production = std::env::var("ENVIRONMENT")
            .map(|e| e == "production")
            .unwrap_or(false);

        Self {
            min_rebuild_interval: if is_production {
                Duration::from_secs(30) // Longer interval in production
            } else {
                Duration::from_secs(2) // Faster development feedback
            },
            max_consecutive_failures: 3,
            circuit_breaker_cooldown: Duration::from_secs(300), // 5 minutes
            max_batch_size: if is_production { 50 } else { 100 },
            production_mode: is_production,
            enable_telemetry: is_production,
        }
    }
}

impl ContentWatcher {
    /// Create a new content watcher with default configuration
    pub fn new<P: AsRef<Path>>(content_root: P, rebuild_script: P) -> anyhow::Result<Self> {
        Self::new_with_config(content_root, rebuild_script, WatcherConfig::default())
    }

    /// Create a new content watcher with custom configuration
    pub fn new_with_config<P: AsRef<Path>>(
        content_root: P,
        rebuild_script: P,
        config: WatcherConfig,
    ) -> anyhow::Result<Self> {
        let (event_sender, _event_receiver) = mpsc::unbounded_channel();
        let cache_timestamps = Arc::new(RwLock::new(HashMap::new()));

        Ok(Self {
            content_root: content_root.as_ref().to_path_buf(),
            rebuild_script: rebuild_script.as_ref().to_path_buf(),
            cache_timestamps,
            event_sender,
            content_service: None,
            last_rebuild_time: Arc::new(RwLock::new(Instant::now())),
            consecutive_failures: Arc::new(RwLock::new(0)),
            config,
        })
    }

    /// Create a new content watcher with content service and default
    /// configuration
    pub fn with_content_service<P: AsRef<Path>>(
        content_root: P,
        rebuild_script: P,
        content_service: Arc<ContentService>,
    ) -> anyhow::Result<Self> {
        Self::with_content_service_and_config(
            content_root,
            rebuild_script,
            content_service,
            WatcherConfig::default(),
        )
    }

    /// Create a new content watcher with content service and custom
    /// configuration
    pub fn with_content_service_and_config<P: AsRef<Path>>(
        content_root: P,
        rebuild_script: P,
        content_service: Arc<ContentService>,
        config: WatcherConfig,
    ) -> anyhow::Result<Self> {
        let (event_sender, mut event_receiver) = mpsc::unbounded_channel();
        let cache_timestamps = Arc::new(RwLock::new(HashMap::new()));

        // Spawn event processing task with production-safe error handling
        let cache_clone = cache_timestamps.clone();
        let service_clone = content_service.clone();
        let last_rebuild_shared = Arc::new(RwLock::new(Instant::now()));
        let consecutive_failures_shared = Arc::new(RwLock::new(0u32));
        let config_clone = config.clone();

        // Create clones for the task
        let last_rebuild_clone = last_rebuild_shared.clone();
        let consecutive_failures_clone = consecutive_failures_shared.clone();

        tokio::spawn(async move {
            let mut pending_events = Vec::new();
            let mut error_count = 0u32;

            while let Some(event) = event_receiver.recv().await {
                pending_events.push(event);

                // Limit batch size in production
                if pending_events.len() >= config_clone.max_batch_size {
                    warn!(
                        "Event batch size limit reached ({}), processing immediately",
                        config_clone.max_batch_size
                    );
                    if let Err(e) = Self::process_events_safe(
                        &pending_events,
                        &cache_clone,
                        Some(&service_clone),
                        &last_rebuild_clone,
                        &consecutive_failures_clone,
                        &config_clone,
                    )
                    .await
                    {
                        error!("Error processing event batch: {}", e);
                        error_count += 1;
                        if error_count >= 5 {
                            error!("Too many processing errors, restarting event processor");
                            break;
                        }
                    } else {
                        error_count = 0; // Reset on success
                    }
                    pending_events.clear();
                    continue;
                }

                // Wait for debounce period
                tokio::time::sleep(Duration::from_millis(500)).await;

                // Process events after debounce period
                if let Err(e) = Self::process_events_safe(
                    &pending_events,
                    &cache_clone,
                    Some(&service_clone),
                    &last_rebuild_clone,
                    &consecutive_failures_clone,
                    &config_clone,
                )
                .await
                {
                    error!("Error processing events: {}", e);
                    error_count += 1;
                } else {
                    error_count = 0; // Reset on success
                }
                pending_events.clear();

                // Emergency break if too many errors
                if error_count >= 10 {
                    error!("Too many consecutive errors in event processing, stopping");
                    break;
                }
            }
        });

        Ok(Self {
            content_root: content_root.as_ref().to_path_buf(),
            rebuild_script: rebuild_script.as_ref().to_path_buf(),
            cache_timestamps,
            event_sender,
            content_service: Some(content_service),
            last_rebuild_time: last_rebuild_shared,
            consecutive_failures: consecutive_failures_shared,
            config,
        })
    }

    /// Start watching the content directory
    #[cfg(feature = "content-watcher")]
    pub async fn start_watching(&self) -> anyhow::Result<()> {
        info!(
            "Starting content watcher for directory: {:?}",
            self.content_root
        );

        let (tx, mut rx) = mpsc::unbounded_channel();
        let event_sender = self.event_sender.clone();

        let mut watcher = RecommendedWatcher::new(
            move |res: Result<Event, notify::Error>| {
                if let Err(e) = tx.send(res) {
                    error!("Failed to send file system event: {}", e);
                }
            },
            Config::default(),
        )?;

        // Dynamically watch all available content type directories
        if let Ok(content_types) = get_available_content_types() {
            for content_type in content_types {
                let content_dir = self.content_root.join(&content_type);
                if content_dir.exists() {
                    watcher.watch(&content_dir, RecursiveMode::Recursive)?;
                    info!("Watching {} directory: {:?}", content_type, content_dir);
                }
            }
        } else {
            warn!("Could not determine content types, watching entire content root");
            watcher.watch(&self.content_root, RecursiveMode::Recursive)?;
            info!("Watching content root directory: {:?}", self.content_root);
        }

        // Process file system events
        tokio::spawn(async move {
            while let Some(res) = rx.recv().await {
                match res {
                    Ok(event) => {
                        if let Err(e) = Self::handle_fs_event(event, &event_sender).await {
                            warn!("Error handling file system event: {}", e);
                        }
                    }
                    Err(e) => {
                        error!("File system watcher error: {}", e);
                    }
                }
            }
        });

        info!("Content watcher started successfully");
        Ok(())
    }

    /// Handle file system events with enhanced category support
    #[cfg(feature = "content-watcher")]
    async fn handle_fs_event(
        event: Event,
        event_sender: &mpsc::UnboundedSender<ContentChangeEvent>,
    ) -> anyhow::Result<()> {
        match event.kind {
            EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) => {
                for path in event.paths {
                    // Process markdown files and meta.toml files
                    if let Some(extension) = path.extension() {
                        let is_markdown = extension == "md";
                        let is_meta_toml = path
                            .file_name()
                            .and_then(|n| n.to_str())
                            .map(|n| n == "meta.toml")
                            .unwrap_or(false);

                        if is_markdown || is_meta_toml {
                            let content_type = ContentTypeHelper::from_path(&path);
                            if format!("{:?}", content_type).to_lowercase() != "unknown" {
                                let language = ContentTypeHelper::extract_language_from_path(&path);
                                let category = ContentTypeHelper::extract_category_from_path(&path);
                                let post_id = if is_markdown {
                                    ContentTypeHelper::extract_post_id_from_path(&path)
                                } else {
                                    None
                                };

                                let event_type =
                                    Self::determine_event_type(&event.kind, &path, is_meta_toml);

                                // Skip global meta.toml files at content-type level
                                if is_meta_toml && category.is_none() {
                                    let change_event = ContentChangeEvent {
                                        path: path.clone(),
                                        content_type,
                                        language: language.unwrap_or_default(),
                                        category: None,
                                        post_id: None,
                                        event_type: ContentEventType::GlobalMetaChanged,
                                        timestamp: SystemTime::now(),
                                    };

                                    debug!("Global meta change detected: {:?}", change_event);
                                    if let Err(e) = event_sender.send(change_event) {
                                        error!("Failed to send global meta change event: {}", e);
                                    }
                                } else if let Some(lang) = language {
                                    let change_event = ContentChangeEvent {
                                        path: path.clone(),
                                        content_type,
                                        language: lang,
                                        category,
                                        post_id,
                                        event_type,
                                        timestamp: SystemTime::now(),
                                    };

                                    debug!("Content change detected: {:?}", change_event);

                                    if let Err(e) = event_sender.send(change_event) {
                                        error!("Failed to send content change event: {}", e);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            _ => {} // Ignore other event types
        }

        Ok(())
    }

    /// Determine the type of content event based on file system event and path
    fn determine_event_type(
        event_kind: &EventKind,
        _path: &Path,
        is_meta_toml: bool,
    ) -> ContentEventType {
        if is_meta_toml {
            ContentEventType::CategoryMetaChanged
        } else {
            match event_kind {
                EventKind::Create(_) => ContentEventType::PostAdded,
                EventKind::Remove(_) => ContentEventType::PostRemoved,
                EventKind::Modify(_) => ContentEventType::PostChanged,
                _ => ContentEventType::PostChanged,
            }
        }
    }

    /// Production-safe event processing with rate limiting and circuit breaker
    async fn process_events_safe(
        events: &[ContentChangeEvent],
        cache_timestamps: &Arc<RwLock<HashMap<String, SystemTime>>>,
        content_service: Option<&Arc<ContentService>>,
        last_rebuild_time: &Arc<RwLock<Instant>>,
        consecutive_failures: &Arc<RwLock<u32>>,
        config: &WatcherConfig,
    ) -> anyhow::Result<()> {
        if events.is_empty() {
            return Ok(());
        }

        // Check circuit breaker
        let failure_count = *consecutive_failures.read().await;
        if failure_count >= config.max_consecutive_failures {
            let last_rebuild = *last_rebuild_time.read().await;
            if last_rebuild.elapsed() < config.circuit_breaker_cooldown {
                warn!(
                    "Circuit breaker active: {} consecutive failures, waiting for cooldown",
                    failure_count
                );
                return Err(anyhow::anyhow!("Circuit breaker active"));
            } else {
                // Reset circuit breaker after cooldown
                *consecutive_failures.write().await = 0;
                info!("Circuit breaker reset after cooldown period");
            }
        }

        // Rate limiting check
        let last_rebuild = *last_rebuild_time.read().await;
        if last_rebuild.elapsed() < config.min_rebuild_interval {
            debug!(
                "Rate limiting active: last rebuild {:?} ago, minimum interval {:?}",
                last_rebuild.elapsed(),
                config.min_rebuild_interval
            );
            return Ok(()); // Skip this batch due to rate limiting
        }

        match Self::process_events_internal(events, cache_timestamps, content_service, config).await
        {
            Ok(()) => {
                // Reset failure count on success
                *consecutive_failures.write().await = 0;
                *last_rebuild_time.write().await = Instant::now();
                Ok(())
            }
            Err(e) => {
                // Increment failure count
                let mut failures = consecutive_failures.write().await;
                *failures += 1;

                if config.enable_telemetry {
                    warn!(
                        "Content processing failed ({} consecutive failures): {}",
                        *failures, e
                    );
                }

                Err(e)
            }
        }
    }

    /// Internal event processing logic (original implementation)
    async fn process_events_internal(
        events: &[ContentChangeEvent],
        cache_timestamps: &Arc<RwLock<HashMap<String, SystemTime>>>,
        content_service: Option<&Arc<ContentService>>,
        config: &WatcherConfig,
    ) -> anyhow::Result<()> {
        info!("Processing {} content change events", events.len());
        if events.is_empty() {
            return Ok(());
        }

        info!("Processing {} content change events", events.len());

        // Process cache invalidation first if content service is available
        if let Some(service) = content_service {
            for event in events {
                let content_type_key = format!("{:?}", event.content_type).to_lowercase();

                // Skip unknown content types
                if content_type_key == "unknown" {
                    continue;
                }

                match &event.event_type {
                    ContentEventType::PostChanged
                    | ContentEventType::PostAdded
                    | ContentEventType::PostRemoved => {
                        if let Some(ref post_id) = event.post_id {
                            if let Some(ref category) = event.category {
                                debug!(
                                    "Invalidating post cache: {}/{}/{}/{}",
                                    content_type_key, event.language, category, post_id
                                );
                                service
                                    .invalidate_post_cache(
                                        &content_type_key,
                                        &event.language,
                                        category,
                                        post_id,
                                    )
                                    .await;
                            }
                        }
                        // Also invalidate category cache to update post lists
                        if let Some(ref category) = event.category {
                            debug!(
                                "Invalidating category cache: {}/{}/{}",
                                content_type_key, event.language, category
                            );
                            service
                                .invalidate_category_cache(
                                    &content_type_key,
                                    &event.language,
                                    category,
                                )
                                .await;
                        }
                    }
                    ContentEventType::PostMoved {
                        from_category,
                        to_category,
                    } => {
                        if let Some(ref post_id) = event.post_id {
                            debug!(
                                "Invalidating moved post cache: {}/{}: {} -> {}",
                                content_type_key, event.language, from_category, to_category
                            );
                            // Invalidate both source and destination category caches
                            service
                                .invalidate_category_cache(
                                    &content_type_key,
                                    &event.language,
                                    from_category,
                                )
                                .await;
                            service
                                .invalidate_category_cache(
                                    &content_type_key,
                                    &event.language,
                                    to_category,
                                )
                                .await;

                            // Invalidate post cache in both locations
                            service
                                .invalidate_post_cache(
                                    &content_type_key,
                                    &event.language,
                                    from_category,
                                    post_id,
                                )
                                .await;
                            service
                                .invalidate_post_cache(
                                    &content_type_key,
                                    &event.language,
                                    to_category,
                                    post_id,
                                )
                                .await;
                        }
                    }
                    ContentEventType::CategoryMetaChanged => {
                        if let Some(ref category) = event.category {
                            debug!(
                                "Invalidating category meta cache: {}/{}/{}",
                                content_type_key, event.language, category
                            );
                            service
                                .invalidate_category_cache(
                                    &content_type_key,
                                    &event.language,
                                    category,
                                )
                                .await;
                        }
                    }
                    ContentEventType::GlobalMetaChanged => {
                        debug!("Invalidating global metadata cache: {}", content_type_key);
                        service
                            .invalidate_global_metadata_cache(&content_type_key)
                            .await;
                    }
                    ContentEventType::TranslationAdded | ContentEventType::TranslationRemoved => {
                        if let Some(ref post_id) = event.post_id {
                            debug!(
                                "Invalidating translation cache: {}/{}",
                                content_type_key, post_id
                            );
                            service
                                .invalidate_translations_cache(&content_type_key, post_id)
                                .await;
                        }
                    }
                }
            }
        }

        // Group events by content type and language for efficient rebuilding
        let mut rebuild_needed = std::collections::HashSet::new();

        for event in events {
            let content_type_key = format!("{:?}", event.content_type).to_lowercase();

            // Skip unknown content types
            if content_type_key == "unknown" {
                continue;
            }

            let key = format!("{}:{}", content_type_key, event.language);
            rebuild_needed.insert(key);
        }

        // Rebuild content for affected types/languages with timeout
        let rebuild_timeout = if config.production_mode {
            Duration::from_secs(300) // 5 minutes in production
        } else {
            Duration::from_secs(60) // 1 minute in development
        };

        for rebuild_key in rebuild_needed {
            info!("Rebuilding content indexes for: {}", rebuild_key);

            // Use timeout for rebuild operation
            match tokio::time::timeout(rebuild_timeout, Self::rebuild_content_async()).await {
                Ok(Ok(())) => {
                    // Update cache timestamp on success
                    let mut timestamps = cache_timestamps.write().await;
                    timestamps.insert(rebuild_key.clone(), SystemTime::now());
                    info!(
                        "Content rebuild completed successfully for: {}",
                        rebuild_key
                    );
                }
                Ok(Err(e)) => {
                    error!("Failed to rebuild content for {}: {}", rebuild_key, e);
                    return Err(e);
                }
                Err(_) => {
                    let err = anyhow::anyhow!("Content rebuild timed out for: {}", rebuild_key);
                    error!("{}", err);
                    return Err(err);
                }
            }
        }

        Ok(())
    }

    /// Rebuild content asynchronously
    async fn rebuild_content_async() -> anyhow::Result<()> {
        let script_path = "scripts/content/build-localized-content.sh";

        info!("Executing content rebuild script: {}", script_path);

        let output = tokio::process::Command::new("bash")
            .arg(script_path)
            .current_dir(".")
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!("Content rebuild failed: {}", stderr));
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        debug!("Content rebuild output: {}", stdout);

        Ok(())
    }

    /// Check if content needs rebuilding based on file modification times
    pub async fn needs_rebuild(&self, content_type: &str, language: &str) -> bool {
        let cache_key = format!("{}:{}", content_type, language);
        let timestamps = self.cache_timestamps.read().await;

        if let Some(last_rebuild) = timestamps.get(&cache_key) {
            // Check if any content files are newer than the last rebuild
            let content_dir = self.content_root.join(content_type).join(language);

            if let Ok(_entries) = tokio::fs::read_dir(&content_dir).await {
                // This is a simplified check - in practice you'd walk the directory
                // For now, assume rebuild is needed if cache is older than 1 hour
                let one_hour_ago = SystemTime::now() - Duration::from_secs(3600);
                return *last_rebuild < one_hour_ago;
            }
        }

        // If no cache entry, assume rebuild is needed
        true
    }

    /// Manually trigger a content rebuild
    pub async fn trigger_rebuild(&self) -> anyhow::Result<()> {
        info!("Manually triggering content rebuild");

        // Check circuit breaker status
        let failure_count = *self.consecutive_failures.read().await;
        if failure_count >= self.config.max_consecutive_failures {
            let last_rebuild = *self.last_rebuild_time.read().await;
            if last_rebuild.elapsed() < self.config.circuit_breaker_cooldown {
                return Err(anyhow::anyhow!(
                    "Circuit breaker active: {} failures, cooldown until {:?}",
                    failure_count,
                    last_rebuild + self.config.circuit_breaker_cooldown
                ));
            }
        }

        // Reset failure count for manual trigger
        *self.consecutive_failures.write().await = 0;

        match Self::rebuild_content_async().await {
            Ok(()) => {
                *self.last_rebuild_time.write().await = Instant::now();
                info!("Manual content rebuild completed successfully");
                Ok(())
            }
            Err(e) => {
                *self.consecutive_failures.write().await += 1;
                error!("Manual content rebuild failed: {}", e);
                Err(e)
            }
        }
    }

    /// Get watcher health status
    pub async fn get_health_status(&self) -> WatcherHealthStatus {
        let failure_count = *self.consecutive_failures.read().await;
        let last_rebuild = *self.last_rebuild_time.read().await;
        let cache_size = self.cache_timestamps.read().await.len();

        let status = if failure_count >= self.config.max_consecutive_failures {
            if last_rebuild.elapsed() < self.config.circuit_breaker_cooldown {
                HealthStatus::CircuitBreakerOpen
            } else {
                HealthStatus::Recovering
            }
        } else if failure_count > 0 {
            HealthStatus::Degraded
        } else {
            HealthStatus::Healthy
        };

        WatcherHealthStatus {
            status,
            consecutive_failures: failure_count,
            last_rebuild_time: last_rebuild,
            cache_entries: cache_size,
            is_production_mode: self.config.production_mode,
            rebuild_rate_limit: self.config.min_rebuild_interval,
        }
    }

    /// Reset circuit breaker manually (for administrative recovery)
    pub async fn reset_circuit_breaker(&self) {
        *self.consecutive_failures.write().await = 0;
        info!("Circuit breaker manually reset");
    }

    /// Enable or disable production mode
    pub fn set_production_mode(&mut self, enabled: bool) {
        self.config.production_mode = enabled;
        if enabled {
            self.config.min_rebuild_interval = Duration::from_secs(30);
            self.config.max_batch_size = 50;
        } else {
            self.config.min_rebuild_interval = Duration::from_secs(2);
            self.config.max_batch_size = 100;
        }
        info!("Production mode set to: {}", enabled);
    }

    /// Get content root directory path
    pub fn get_content_root(&self) -> &Path {
        &self.content_root
    }

    /// Get rebuild script path
    pub fn get_rebuild_script(&self) -> &Path {
        &self.rebuild_script
    }

    /// Check if content service is available
    pub fn has_content_service(&self) -> bool {
        self.content_service.is_some()
    }

    /// Get configuration for monitoring
    pub fn get_config(&self) -> &WatcherConfig {
        &self.config
    }

    /// Update configuration at runtime
    pub fn update_config(&mut self, config: WatcherConfig) {
        self.config = config;
        info!("Watcher configuration updated");
    }
}

#[cfg(not(feature = "content-watcher"))]
impl ContentWatcher {
    pub fn new<P: AsRef<Path>>(_content_root: P, _rebuild_script: P) -> anyhow::Result<Self> {
        Err(anyhow::anyhow!("Content watcher feature not enabled"))
    }

    pub async fn start_watching(&self) -> anyhow::Result<()> {
        warn!("Content watcher is not available (feature not enabled)");
        Ok(())
    }

    pub async fn needs_rebuild(&self, _content_type: &str, _language: &str) -> bool {
        false
    }

    pub async fn trigger_rebuild(&self) -> anyhow::Result<()> {
        Err(anyhow::anyhow!("Content watcher feature not enabled"))
    }
}

/// Health status for monitoring and alerting
#[derive(Debug, Clone)]
pub struct WatcherHealthStatus {
    pub status: HealthStatus,
    pub consecutive_failures: u32,
    pub last_rebuild_time: Instant,
    pub cache_entries: usize,
    pub is_production_mode: bool,
    pub rebuild_rate_limit: Duration,
}

/// Health status enumeration
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum HealthStatus {
    Healthy,
    Degraded,
    CircuitBreakerOpen,
    Recovering,
}

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

    #[test]
    fn test_content_type_from_path() {
        // Get content root from environment or use test default
        let content_root =
            std::env::var("SITE_CONTENT_PATH").unwrap_or_else(|_| "site/content".to_string());

        // Test dynamic content type detection with generic paths
        // These tests use arbitrary content type names that don't assume any specific
        // configuration
        assert_eq!(
            format!(
                "{:?}",
                ContentTypeHelper::from_path(Path::new(&format!(
                    "/{}/news/en/post.md",
                    content_root
                )))
            )
            .to_lowercase(),
            "news"
        );
        assert_eq!(
            format!(
                "{:?}",
                ContentTypeHelper::from_path(Path::new(&format!(
                    "/{}/tutorials/es/guide.md",
                    content_root
                )))
            )
            .to_lowercase(),
            "tutorials"
        );
        assert_eq!(
            format!(
                "{:?}",
                ContentTypeHelper::from_path(Path::new(&format!(
                    "/{}/docs/fr/documentation.md",
                    content_root
                )))
            )
            .to_lowercase(),
            "docs"
        );
        // Test unknown paths
        assert_eq!(
            format!(
                "{:?}",
                ContentTypeHelper::from_path(Path::new("/other/file.md"))
            )
            .to_lowercase(),
            "unknown"
        );
        // Test special directories that should be ignored
        assert_eq!(
            format!(
                "{:?}",
                ContentTypeHelper::from_path(Path::new(&format!(
                    "/{}/locales/en/messages.ftl",
                    content_root
                )))
            )
            .to_lowercase(),
            "unknown"
        );
    }

    #[test]
    fn test_extract_language_from_path() {
        // Get content root from environment or use test default
        let content_root =
            std::env::var("SITE_CONTENT_PATH").unwrap_or_else(|_| "site/content".to_string());

        // Test dynamic language extraction with generic content types
        // These tests don't assume specific content types exist and use configurable
        // content root
        assert_eq!(
            ContentWatcher::extract_language_from_path(Path::new(&format!(
                "/{}/news/en/post.md",
                content_root
            ))),
            "en"
        );
        assert_eq!(
            ContentWatcher::extract_language_from_path(Path::new(&format!(
                "/{}/tutorials/es/guide.md",
                content_root
            ))),
            "es"
        );
        assert_eq!(
            ContentWatcher::extract_language_from_path(Path::new(&format!(
                "/{}/docs/fr/documentation.md",
                content_root
            ))),
            "fr"
        );
        assert_eq!(
            ContentWatcher::extract_language_from_path(Path::new(&format!(
                "/{}/help/de/manual.md",
                content_root
            ))),
            "de"
        );
        // Test fallback for invalid paths
        assert_eq!(
            ContentWatcher::extract_language_from_path(Path::new(&format!(
                "/{}/other/file.md",
                content_root
            ))),
            rustelo_core_lib::i18n::get_default_language()
        );
        // Test paths without proper structure
        assert_eq!(
            ContentWatcher::extract_language_from_path(Path::new("/some/random/path.md")),
            rustelo_core_lib::i18n::get_default_language()
        );
    }
}
