use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info, warn};

use crate::error::CliError;
use crate::registry::{CommandEntry, LookupResult, Registry};

pub struct RegistryCache {
    index: HashMap<String, CommandEntry>,
}

impl RegistryCache {
    fn load(cache_json: &Path) -> Result<Self, CliError> {
        let registry = Registry::from_json(cache_json)?;
        let count = registry.commands.len();
        let index = registry.into_index();
        info!("registry loaded: {} commands ({} entries in index)", count, index.len());
        Ok(Self { index })
    }

    pub fn lookup(&self, name: &str) -> LookupResult {
        match self.index.get(name) {
            Some(entry) => LookupResult::from_entry(entry),
            None => LookupResult::not_found(),
        }
    }

}

pub type SharedCache = Arc<RwLock<Option<RegistryCache>>>;

pub fn new_shared() -> SharedCache {
    Arc::new(RwLock::new(None))
}

pub async fn reload(cache: &SharedCache, cache_json: &Path) -> Result<(), CliError> {
    match RegistryCache::load(cache_json) {
        Ok(new_cache) => {
            *cache.write().await = Some(new_cache);
            Ok(())
        }
        Err(e) => {
            warn!("registry reload failed: {}", e);
            Err(e)
        }
    }
}

/// Spawn a file watcher that invalidates the cache when the JSON cache file changes.
pub fn spawn_watcher(cache: SharedCache, cache_json: PathBuf) {
    tokio::task::spawn_blocking(move || {
        let cache_clone = cache.clone();
        let path_clone = cache_json.clone();

        let rt = tokio::runtime::Handle::current();

        let mut watcher: RecommendedWatcher =
            notify::recommended_watcher(move |res: Result<Event, _>| match res {
                Ok(event) if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_)) => {
                    debug!("registry cache changed, invalidating");
                    let c = cache_clone.clone();
                    let p = path_clone.clone();
                    rt.spawn(async move {
                        if let Err(e) = reload(&c, &p).await {
                            warn!("watcher-triggered reload failed: {}", e);
                        }
                    });
                }
                _ => {}
            })
            .expect("watcher init failed");

        if let Some(parent) = cache_json.parent() {
            watcher
                .watch(parent, RecursiveMode::NonRecursive)
                .expect("watch failed");
        }

        // Block this thread to keep the watcher alive.
        std::thread::park();
    });
}
