//! Subscription builder for fluent configuration.

use super::{Subscription, SubscriptionId, SubscriptionKind};
use crate::domain::HandlerId;
use std::path::PathBuf;
use std::time::Duration;

/// Default debounce duration in milliseconds.
const DEFAULT_DEBOUNCE_MS: u64 = 100;

/// Default maximum batch size.
const DEFAULT_MAX_BATCH_SIZE: usize = 100;

/// Builder for creating subscriptions.
///
/// # Example
///
/// ```rust
/// use cfgmatic_reactive::prelude::Subscription;
///
/// let sub = Subscription::builder()
///     .file("/path/to/config.json")
///     .debounce_ms(200)
///     .priority(10)
///     .build();
///
/// assert!(sub.active);
/// ```
#[derive(Debug, Clone)]
pub struct SubscriptionBuilder {
    id: Option<SubscriptionId>,
    kind: SubscriptionKind,
    handler_id: Option<HandlerId>,
    debounce: Duration,
    max_batch_size: usize,
    active: bool,
    priority: i32,
}

impl SubscriptionBuilder {
    /// Creates a new subscription builder.
    #[must_use]
    pub const fn new() -> Self {
        Self {
            id: None,
            kind: SubscriptionKind::All,
            handler_id: None,
            debounce: Duration::from_millis(DEFAULT_DEBOUNCE_MS),
            max_batch_size: DEFAULT_MAX_BATCH_SIZE,
            active: true,
            priority: 0,
        }
    }

    /// Sets the subscription ID.
    #[must_use]
    pub fn id(mut self, id: impl Into<String>) -> Self {
        self.id = Some(SubscriptionId::new(id));
        self
    }

    /// Sets the handler ID.
    #[must_use]
    pub fn handler_id(mut self, id: impl Into<String>) -> Self {
        self.handler_id = Some(HandlerId::new(id));
        self
    }

    /// Sets the subscription to watch a file.
    #[must_use]
    pub fn file(mut self, path: impl Into<PathBuf>) -> Self {
        self.kind = SubscriptionKind::file(path);
        self
    }

    /// Sets the subscription to watch a file recursively.
    #[must_use]
    pub fn file_recursive(mut self, path: impl Into<PathBuf>) -> Self {
        self.kind = SubscriptionKind::file_recursive(path);
        self
    }

    /// Sets the subscription to watch a directory.
    #[must_use]
    pub fn directory(mut self, path: impl Into<PathBuf>) -> Self {
        self.kind = SubscriptionKind::directory(path);
        self
    }

    /// Sets the subscription to watch a directory with patterns.
    #[must_use]
    pub fn directory_with_patterns(
        mut self,
        path: impl Into<PathBuf>,
        patterns: Vec<String>,
    ) -> Self {
        self.kind = SubscriptionKind::directory_with_patterns(path, patterns);
        self
    }

    /// Sets the subscription to watch an environment variable.
    #[must_use]
    pub fn env(mut self, name: impl Into<String>) -> Self {
        self.kind = SubscriptionKind::env(name);
        self
    }

    /// Sets the subscription kind directly.
    #[must_use]
    pub fn kind(mut self, kind: SubscriptionKind) -> Self {
        self.kind = kind;
        self
    }

    /// Sets the debounce duration in milliseconds.
    #[must_use]
    pub const fn debounce_ms(mut self, ms: u64) -> Self {
        self.debounce = Duration::from_millis(ms);
        self
    }

    /// Sets the debounce duration.
    #[must_use]
    pub const fn debounce(mut self, duration: Duration) -> Self {
        self.debounce = duration;
        self
    }

    /// Sets the maximum batch size.
    #[must_use]
    pub const fn max_batch_size(mut self, size: usize) -> Self {
        self.max_batch_size = size;
        self
    }

    /// Sets whether the subscription is active.
    #[must_use]
    pub const fn active(mut self, active: bool) -> Self {
        self.active = active;
        self
    }

    /// Activates the subscription.
    #[must_use]
    pub const fn enable(mut self) -> Self {
        self.active = true;
        self
    }

    /// Deactivates the subscription.
    #[must_use]
    pub const fn disable(mut self) -> Self {
        self.active = false;
        self
    }

    /// Sets the priority.
    #[must_use]
    pub const fn priority(mut self, priority: i32) -> Self {
        self.priority = priority;
        self
    }

    /// Builds the subscription.
    #[must_use]
    pub fn build(self) -> Subscription {
        Subscription {
            id: self.id.unwrap_or_else(SubscriptionId::generate),
            kind: self.kind,
            handler_id: self.handler_id.unwrap_or_default(),
            debounce: self.debounce,
            max_batch_size: self.max_batch_size,
            active: self.active,
            priority: self.priority,
        }
    }
}

impl Default for SubscriptionBuilder {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_builder_default() {
        let sub = SubscriptionBuilder::new().build();

        assert!(sub.active);
        assert_eq!(sub.debounce, Duration::from_millis(100));
        assert_eq!(sub.max_batch_size, 100);
        assert_eq!(sub.priority, 0);
    }

    #[test]
    fn test_builder_file() {
        let sub = SubscriptionBuilder::new().file("/path/to/file").build();

        assert!(matches!(sub.kind, SubscriptionKind::File { .. }));
    }

    #[test]
    fn test_builder_directory_with_patterns() {
        let sub = SubscriptionBuilder::new()
            .directory_with_patterns("/path", vec!["*.json".to_string()])
            .build();

        assert!(matches!(sub.kind, SubscriptionKind::Directory { .. }));
    }

    #[test]
    fn test_builder_chain() {
        let sub = SubscriptionBuilder::new()
            .id("custom-id")
            .handler_id("handler-1")
            .file("/path")
            .debounce_ms(500)
            .max_batch_size(50)
            .priority(100)
            .disable()
            .build();

        assert_eq!(sub.id.as_str(), "custom-id");
        assert_eq!(sub.handler_id.as_str(), "handler-1");
        assert_eq!(sub.debounce, Duration::from_millis(500));
        assert_eq!(sub.max_batch_size, 50);
        assert_eq!(sub.priority, 100);
        assert!(!sub.active);
    }

    #[test]
    fn test_builder_enable_and_env_kind() {
        let sub = SubscriptionBuilder::new()
            .env("APP_PORT")
            .active(false)
            .enable()
            .build();

        assert!(matches!(sub.kind, SubscriptionKind::Env { .. }));
        assert!(sub.active);
    }

    #[test]
    fn test_builder_kind_override_and_recursive_file() {
        let sub = SubscriptionBuilder::new()
            .file_recursive("/etc/app/config.toml")
            .kind(SubscriptionKind::directory("/etc/app"))
            .debounce(Duration::from_secs(2))
            .build();

        assert!(matches!(sub.kind, SubscriptionKind::Directory { .. }));
        assert_eq!(sub.debounce, Duration::from_secs(2));
    }
}
