//! Change handler trait for processing configuration changes.

use crate::domain::error::Result;
use crate::domain::stream::change::ConfigChange;

/// Strategy for handling errors during change processing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ErrorStrategy {
    /// Stop processing on first error.
    #[default]
    Stop,
    /// Continue processing, collect errors.
    Continue,
    /// Retry on failure.
    Retry {
        /// Maximum retry attempts.
        max_attempts: u32,
        /// Delay between retries in milliseconds.
        delay_ms: u64,
    },
}

impl ErrorStrategy {
    /// Creates a stop strategy.
    #[must_use]
    pub const fn stop() -> Self {
        Self::Stop
    }

    /// Creates a continue strategy.
    #[must_use]
    pub const fn continue_on_error() -> Self {
        Self::Continue
    }

    /// Creates a retry strategy.
    #[must_use]
    pub const fn retry(max_attempts: u32, delay_ms: u64) -> Self {
        Self::Retry {
            max_attempts,
            delay_ms,
        }
    }
}

/// Handler for configuration changes.
///
/// Implement this trait to react to configuration changes.
///
/// # Example
///
/// ```rust
/// use cfgmatic_reactive::prelude::{ChangeHandler, ConfigChange, Result};
///
/// struct LogHandler;
///
/// impl ChangeHandler for LogHandler {
///     fn handle(&self, change: &ConfigChange) -> Result<()> {
///         println!("Config changed: {:?}", change);
///         Ok(())
///     }
/// }
///
/// assert!(LogHandler.name().ends_with("LogHandler"));
/// ```
pub trait ChangeHandler: Send + Sync {
    /// Handle a configuration change.
    ///
    /// # Errors
    ///
    /// Returns an error if handling fails.
    fn handle(&self, change: &ConfigChange) -> Result<()>;

    /// Returns the handler name for debugging.
    fn name(&self) -> &str {
        std::any::type_name::<Self>()
    }

    /// Returns the error strategy for this handler.
    fn error_strategy(&self) -> ErrorStrategy {
        ErrorStrategy::default()
    }

    /// Checks if this handler can handle the given change.
    fn can_handle(&self, _change: &ConfigChange) -> bool {
        true
    }
}

/// Handler with async support.
#[cfg(feature = "async")]
#[async_trait::async_trait]
pub trait AsyncChangeHandler: Send + Sync {
    /// Handle a configuration change asynchronously.
    ///
    /// # Errors
    ///
    /// Returns an error if handling fails.
    async fn handle(&self, change: &ConfigChange) -> Result<()>;

    /// Returns the handler name for debugging.
    fn name(&self) -> &str {
        std::any::type_name::<Self>()
    }

    /// Returns the error strategy for this handler.
    fn error_strategy(&self) -> ErrorStrategy {
        ErrorStrategy::default()
    }

    /// Checks if this handler can handle the given change.
    fn can_handle(&self, _change: &ConfigChange) -> bool {
        true
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::{ChangeKind, ChangeSource, ConfigChange, ReactiveError};

    struct TestHandler;

    impl ChangeHandler for TestHandler {
        fn handle(&self, _change: &ConfigChange) -> Result<()> {
            Ok(())
        }
    }

    struct FilteringHandler;

    impl ChangeHandler for FilteringHandler {
        fn handle(&self, change: &ConfigChange) -> Result<()> {
            if self.can_handle(change) {
                Ok(())
            } else {
                Err(ReactiveError::handler_failed("unexpected source"))
            }
        }

        fn error_strategy(&self) -> ErrorStrategy {
            ErrorStrategy::continue_on_error()
        }

        fn can_handle(&self, change: &ConfigChange) -> bool {
            matches!(change.source, ChangeSource::File { .. })
        }
    }

    #[test]
    fn test_error_strategy_default() {
        let strategy = ErrorStrategy::default();
        assert!(matches!(strategy, ErrorStrategy::Stop));
    }

    #[test]
    fn test_error_strategy_constructors() {
        let stop = ErrorStrategy::stop();
        assert!(matches!(stop, ErrorStrategy::Stop));

        let cont = ErrorStrategy::continue_on_error();
        assert!(matches!(cont, ErrorStrategy::Continue));

        let retry = ErrorStrategy::retry(3, 100);
        assert!(matches!(
            retry,
            ErrorStrategy::Retry {
                max_attempts: 3,
                delay_ms: 100
            }
        ));
    }

    #[test]
    fn test_change_handler_default_methods() {
        let handler = TestHandler;
        let change = ConfigChange::file_change("1", "/etc/app.toml", ChangeKind::Modified);

        assert!(handler.handle(&change).is_ok());
        assert!(handler.name().ends_with("TestHandler"));
        assert_eq!(handler.error_strategy(), ErrorStrategy::Stop);
        assert!(handler.can_handle(&change));
    }

    #[test]
    fn test_change_handler_custom_strategy_and_filtering() {
        let handler = FilteringHandler;
        let file_change = ConfigChange::file_change("1", "/etc/app.toml", ChangeKind::Modified);
        let env_change =
            ConfigChange::new("2", ChangeKind::Modified, ChangeSource::env("APP_PORT"));

        assert_eq!(handler.error_strategy(), ErrorStrategy::Continue);
        assert!(handler.can_handle(&file_change));
        assert!(!handler.can_handle(&env_change));
        assert!(handler.handle(&file_change).is_ok());
        assert!(handler.handle(&env_change).is_err());
    }

    #[cfg(feature = "async")]
    struct AsyncTestHandler;

    #[cfg(feature = "async")]
    #[async_trait::async_trait]
    impl AsyncChangeHandler for AsyncTestHandler {
        async fn handle(&self, _change: &ConfigChange) -> Result<()> {
            Ok(())
        }
    }

    #[cfg(feature = "async")]
    #[tokio::test]
    async fn test_async_change_handler_default_methods() {
        let handler = AsyncTestHandler;
        let change = ConfigChange::file_change("1", "/etc/app.toml", ChangeKind::Modified);

        assert!(handler.handle(&change).await.is_ok());
        assert!(handler.name().ends_with("AsyncTestHandler"));
        assert_eq!(handler.error_strategy(), ErrorStrategy::Stop);
        assert!(handler.can_handle(&change));
    }
}
