//! File watcher service for configuration changes.

use crate::application::Dispatcher;
use crate::constants::DEFAULT_DEBOUNCE_MS;
use crate::domain::{Event, EventKind, EventMetadata, Result};
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;

/// File watcher service that integrates with the dispatcher.
pub struct FileWatcherService {
    /// Dispatcher for events.
    dispatcher: Arc<Dispatcher>,
    /// Debounce time in milliseconds.
    debounce_ms: u64,
}

impl FileWatcherService {
    /// Create a new file watcher service.
    #[must_use]
    pub const fn new(dispatcher: Arc<Dispatcher>) -> Self {
        Self {
            dispatcher,
            debounce_ms: DEFAULT_DEBOUNCE_MS,
        }
    }

    /// Set the debounce time.
    #[must_use]
    pub const fn with_debounce(mut self, ms: u64) -> Self {
        self.debounce_ms = ms;
        self
    }

    /// Notify of a file change.
    ///
    /// # Errors
    ///
    /// Returns an error if dispatching the change event fails.
    pub fn notify_change(&self, path: &Path) -> Result<()> {
        let event = Event::new(
            EventKind::Change,
            path.to_string_lossy().to_string(),
            EventMetadata::new("file-watcher"),
        );
        self.dispatcher.dispatch(&event)
    }

    /// Notify of a file creation.
    ///
    /// # Errors
    ///
    /// Returns an error if dispatching the create event fails.
    pub fn notify_create(&self, path: &Path) -> Result<()> {
        let event = Event::new(
            EventKind::Create,
            path.to_string_lossy().to_string(),
            EventMetadata::new("file-watcher"),
        );
        self.dispatcher.dispatch(&event)
    }

    /// Notify of a file deletion.
    ///
    /// # Errors
    ///
    /// Returns an error if dispatching the delete event fails.
    pub fn notify_delete(&self, path: &Path) -> Result<()> {
        let event = Event::new(
            EventKind::Delete,
            path.to_string_lossy().to_string(),
            EventMetadata::new("file-watcher"),
        );
        self.dispatcher.dispatch(&event)
    }

    /// Get the debounce duration.
    #[must_use]
    pub const fn debounce_duration(&self) -> Duration {
        Duration::from_millis(self.debounce_ms)
    }

    /// Get the dispatcher.
    #[must_use]
    pub const fn dispatcher(&self) -> &Arc<Dispatcher> {
        &self.dispatcher
    }
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;
    use std::sync::atomic::{AtomicUsize, Ordering};

    use super::*;
    use crate::domain::{Handler, HandlerId};

    struct CountingHandler {
        id: HandlerId,
        calls: Arc<AtomicUsize>,
    }

    impl Handler for CountingHandler {
        fn id(&self) -> &HandlerId {
            &self.id
        }

        fn handle(&self, _event: &Event) -> Result<()> {
            self.calls.fetch_add(1, Ordering::SeqCst);
            Ok(())
        }
    }

    #[test]
    fn test_file_watcher_service_dispatches_events() {
        let dispatcher = Arc::new(Dispatcher::new());
        let calls = Arc::new(AtomicUsize::new(0));

        dispatcher
            .register_handler(CountingHandler {
                id: HandlerId::new("watcher-handler"),
                calls: calls.clone(),
            })
            .unwrap();

        let watcher = FileWatcherService::new(dispatcher.clone()).with_debounce(250);

        watcher
            .notify_change(Path::new("/tmp/config.toml"))
            .unwrap();
        watcher
            .notify_create(Path::new("/tmp/config.toml"))
            .unwrap();
        watcher
            .notify_delete(Path::new("/tmp/config.toml"))
            .unwrap();

        assert_eq!(calls.load(Ordering::SeqCst), 3);
        assert_eq!(watcher.debounce_duration(), Duration::from_millis(250));
        assert!(Arc::ptr_eq(watcher.dispatcher(), &dispatcher));
    }
}
