//! Configuration change streams and utilities.

use std::path::PathBuf;
use std::time::SystemTime;

use tokio::sync::mpsc;

/// Source of configuration change.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChangeSource {
    /// Changed from file.
    File(PathBuf),
    /// Changed from environment variable.
    Environment(String),
    /// Changed from remote source.
    Remote(String),
    /// Manual/programmatic change.
    Manual,
    /// Unknown source.
    Unknown,
}

/// A change notification for a configuration value.
#[derive(Debug, Clone)]
pub struct ConfigChange<T> {
    /// Previous value.
    pub old: T,
    /// New value.
    pub new: T,
    /// Path to the changed field (if applicable).
    pub path: Option<String>,
    /// Source of the change.
    pub source: ChangeSource,
    /// When the change occurred.
    pub timestamp: SystemTime,
}

impl<T> ConfigChange<T> {
    /// Create a new change notification.
    pub fn new(old: T, new: T) -> Self {
        Self {
            old,
            new,
            path: None,
            source: ChangeSource::Unknown,
            timestamp: SystemTime::now(),
        }
    }

    /// Set the path.
    #[must_use]
    pub fn with_path(mut self, path: impl Into<String>) -> Self {
        self.path = Some(path.into());
        self
    }

    /// Set the source.
    #[must_use]
    pub fn with_source(mut self, source: ChangeSource) -> Self {
        self.source = source;
        self
    }
}

/// Stream of configuration changes.
pub struct ConfigStream<T> {
    pub(crate) receiver: mpsc::Receiver<ConfigChange<T>>,
}

impl<T> ConfigStream<T> {
    /// Create a new stream from a receiver.
    ///
    /// This is useful for creating custom configuration sources.
    pub const fn new(receiver: mpsc::Receiver<ConfigChange<T>>) -> Self {
        Self { receiver }
    }

    /// Receive the next change.
    pub async fn next(&mut self) -> Option<ConfigChange<T>> {
        self.receiver.recv().await
    }

    /// Try to receive the next change without blocking.
    pub fn try_next(&mut self) -> Result<Option<ConfigChange<T>>, mpsc::error::TryRecvError> {
        self.receiver.try_recv().map(Some).or_else(|e| match e {
            mpsc::error::TryRecvError::Empty => Ok(None),
            mpsc::error::TryRecvError::Disconnected => Err(e),
        })
    }
}

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

    #[test]
    fn test_config_change_builder() {
        let change = ConfigChange::new(1, 2)
            .with_path("/server/port")
            .with_source(ChangeSource::Manual);

        assert_eq!(change.old, 1);
        assert_eq!(change.new, 2);
        assert_eq!(change.path, Some("/server/port".to_string()));
        assert_eq!(change.source, ChangeSource::Manual);
    }

    #[tokio::test]
    async fn test_config_stream_next() {
        let (tx, rx) = mpsc::channel(10);
        let mut stream = ConfigStream::new(rx);

        tx.send(ConfigChange::new(1, 2)).await.unwrap();

        let change = stream.next().await.unwrap();
        assert_eq!(change.old, 1);
        assert_eq!(change.new, 2);
    }
}
