use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Result, Watcher};
use std::path::Path;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};

pub type EventHandler = Arc<dyn Fn(&Event) + Send + Sync>;

pub struct FileWatcher {
    watcher: RecommendedWatcher,
    event_handlers: Arc<Mutex<Vec<EventHandler>>>,
}

pub struct Reactor {
    rx: UnboundedReceiver<notify::Result<Event>>,
    event_handlers: Arc<Mutex<Vec<EventHandler>>>,
    debounce_duration: Duration,
    last_event_time: Arc<Mutex<Option<Instant>>>,
}

impl FileWatcher {
    pub fn new() -> Result<Self> {
        let event_handlers = Arc::new(Mutex::new(Vec::new()));
        let handlers = Arc::clone(&event_handlers);

        let (tx, rx): (
            UnboundedSender<notify::Result<Event>>,
            UnboundedReceiver<notify::Result<Event>>,
        ) = unbounded_channel();

        let watcher = RecommendedWatcher::new(
            move |res| {
                if let Err(e) = tx.send(res) {
                    eprintln!("Failed to send event: {}", e);
                }
            },
            notify::Config::default(),
        )?;

        tokio::spawn(async move {
            let mut reactor = Reactor::new(rx, handlers);
            reactor.run().await;
        });

        Ok(Self {
            watcher,
            event_handlers,
        })
    }

    pub fn watch<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
        self.watcher.watch(path.as_ref(), RecursiveMode::Recursive)
    }

    pub async fn on_change<F>(&mut self, handler: F)
    where
        F: Fn(&Event) + Send + Sync + 'static,
    {
        let mut handlers = self.event_handlers.lock().await;
        handlers.push(Arc::new(handler));
    }
}

impl Reactor {
    pub fn new(
        rx: UnboundedReceiver<notify::Result<Event>>,
        event_handlers: Arc<Mutex<Vec<EventHandler>>>,
    ) -> Self {
        Self {
            rx,
            event_handlers,
            debounce_duration: Duration::from_millis(100),
            last_event_time: Arc::new(Mutex::new(None)),
        }
    }

    pub async fn run(&mut self) {
        loop {
            match self.rx.recv().await {
                Some(Ok(event)) => {
                    if self.should_process_event(&event).await {
                        self.process_event(&event).await;
                    }
                }
                Some(Err(e)) => eprintln!("Watch error: {:?}", e),
                None => break,
            }
        }
    }

    async fn should_process_event(&self, event: &Event) -> bool {
        match event.kind {
            EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) => {
                let mut last_time = self.last_event_time.lock().await;
                let now = Instant::now();

                match *last_time {
                    Some(last) if now.duration_since(last) < self.debounce_duration => false,
                    _ => {
                        *last_time = Some(now);
                        true
                    }
                }
            }
            _ => false,
        }
    }

    async fn process_event(&self, event: &Event) {
        let handlers = self.event_handlers.lock().await;
        for handler in handlers.iter() {
            handler(event);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::sync::atomic::{AtomicUsize, Ordering};
    use tokio::time::sleep;

    #[tokio::test]
    async fn test_file_watcher() {
        let temp_dir = tempfile::TempDir::new().unwrap();
        let test_file = temp_dir.path().join("test.txt");

        let counter = Arc::new(AtomicUsize::new(0));
        let counter_clone = Arc::clone(&counter);

        let mut watcher = FileWatcher::new().unwrap();
        watcher
            .on_change(move |_event| {
                counter_clone.fetch_add(1, Ordering::SeqCst);
            })
            .await;

        watcher.watch(temp_dir.path()).unwrap();

        sleep(Duration::from_millis(100)).await;
        fs::write(&test_file, "Hello").unwrap();
        sleep(Duration::from_millis(300)).await;

        assert!(counter.load(Ordering::SeqCst) > 0);
    }
}
