//! Subscription types for reactive configuration.

mod builder;

use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::time::Duration;

use super::HandlerId;

pub use builder::SubscriptionBuilder;

/// Unique identifier for a subscription.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SubscriptionId(String);

impl SubscriptionId {
    /// Creates a new subscription ID.
    #[must_use]
    pub fn new(id: impl Into<String>) -> Self {
        Self(id.into())
    }

    /// Creates a random subscription ID.
    #[must_use]
    pub fn generate() -> Self {
        Self(format!("sub-{}", uuid::Uuid::new_v4()))
    }

    /// Returns the ID as a string slice.
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl Default for SubscriptionId {
    fn default() -> Self {
        Self::generate()
    }
}

impl std::fmt::Display for SubscriptionId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Kind of subscription.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SubscriptionKind {
    /// Subscribe to file changes.
    File {
        /// Path to watch.
        path: PathBuf,
        /// Watch recursively.
        recursive: bool,
    },
    /// Subscribe to directory changes.
    Directory {
        /// Directory path.
        path: PathBuf,
        /// File patterns to include.
        patterns: Vec<String>,
    },
    /// Subscribe to environment variable changes.
    Env {
        /// Variable name.
        name: String,
    },
    /// Subscribe to all changes.
    All,
}

impl SubscriptionKind {
    /// Creates a file subscription.
    #[must_use]
    pub fn file(path: impl Into<PathBuf>) -> Self {
        Self::File {
            path: path.into(),
            recursive: false,
        }
    }

    /// Creates a recursive file subscription.
    #[must_use]
    pub fn file_recursive(path: impl Into<PathBuf>) -> Self {
        Self::File {
            path: path.into(),
            recursive: true,
        }
    }

    /// Creates a directory subscription.
    #[must_use]
    pub fn directory(path: impl Into<PathBuf>) -> Self {
        Self::Directory {
            path: path.into(),
            patterns: vec!["*".to_string()],
        }
    }

    /// Creates a directory subscription with patterns.
    #[must_use]
    pub fn directory_with_patterns(path: impl Into<PathBuf>, patterns: Vec<String>) -> Self {
        Self::Directory {
            path: path.into(),
            patterns,
        }
    }

    /// Creates an environment variable subscription.
    #[must_use]
    pub fn env(name: impl Into<String>) -> Self {
        Self::Env { name: name.into() }
    }

    /// Returns the path if this is a file or directory subscription.
    #[must_use]
    pub const fn path(&self) -> Option<&PathBuf> {
        match self {
            Self::File { path, .. } | Self::Directory { path, .. } => Some(path),
            _ => None,
        }
    }

    /// Checks if a path matches this subscription kind.
    #[must_use]
    pub fn matches(&self, path: &Path) -> bool {
        match self {
            Self::File { path: sub_path, .. } => path == *sub_path,
            Self::Directory {
                path: dir_path,
                patterns,
            } => {
                if !path.starts_with(dir_path) {
                    return false;
                }
                if patterns.is_empty() || patterns.contains(&"*".to_string()) {
                    return true;
                }
                if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
                    return patterns
                        .iter()
                        .any(|p| glob_match::glob_match(p, file_name));
                }
                false
            }
            Self::Env { .. } => false,
            Self::All => true,
        }
    }
}

/// Configuration for a subscription.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Subscription {
    /// Unique identifier.
    pub id: SubscriptionId,
    /// Kind of subscription.
    pub kind: SubscriptionKind,
    /// Handler ID for this subscription.
    pub handler_id: HandlerId,
    /// Debounce duration for batching changes.
    pub debounce: Duration,
    /// Maximum batch size.
    pub max_batch_size: usize,
    /// Whether the subscription is active.
    pub active: bool,
    /// Priority for handler execution (higher = earlier).
    pub priority: i32,
}

impl Subscription {
    /// Creates a new subscription using the builder pattern.
    #[must_use]
    pub const fn builder() -> SubscriptionBuilder {
        SubscriptionBuilder::new()
    }

    /// Creates a file subscription.
    #[must_use]
    pub fn file(path: impl Into<PathBuf>) -> Self {
        Self::builder().file(path).build()
    }

    /// Creates a directory subscription.
    #[must_use]
    pub fn directory(path: impl Into<PathBuf>) -> Self {
        Self::builder().directory(path).build()
    }

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

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

impl Default for Subscription {
    fn default() -> Self {
        Self {
            id: SubscriptionId::generate(),
            kind: SubscriptionKind::All,
            handler_id: HandlerId::default(),
            debounce: Duration::from_millis(100),
            max_batch_size: 100,
            active: true,
            priority: 0,
        }
    }
}

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

    #[test]
    fn test_subscription_id() {
        let id = SubscriptionId::new("test-id");
        assert_eq!(id.as_str(), "test-id");
        assert_eq!(id.to_string(), "test-id");
    }

    #[test]
    fn test_subscription_id_generate() {
        let id = SubscriptionId::generate();
        assert!(id.as_str().starts_with("sub-"));
    }

    #[test]
    fn test_subscription_kind_file() {
        let kind = SubscriptionKind::file("/path/to/file");
        assert!(kind.path().is_some());
    }

    #[test]
    fn test_subscription_kind_matches() {
        let kind = SubscriptionKind::file("/path/to/file");
        assert!(kind.matches(Path::new("/path/to/file")));
        assert!(!kind.matches(Path::new("/other/path")));

        let kind = SubscriptionKind::All;
        assert!(kind.matches(Path::new("/any/path")));
    }

    #[test]
    fn test_subscription_kind_directory_patterns_and_env() {
        let directory = SubscriptionKind::directory_with_patterns(
            "/etc/myapp",
            vec!["*.toml".to_string(), "*.json".to_string()],
        );
        let recursive_file = SubscriptionKind::file_recursive("/etc/myapp/config.toml");
        let env = SubscriptionKind::env("APP_PORT");

        assert!(directory.matches(Path::new("/etc/myapp/config.toml")));
        assert!(directory.matches(Path::new("/etc/myapp/config.json")));
        assert!(!directory.matches(Path::new("/etc/myapp/config.yaml")));
        assert_eq!(
            recursive_file.path(),
            Some(&std::path::PathBuf::from("/etc/myapp/config.toml"))
        );
        assert!(matches!(env, SubscriptionKind::Env { .. }));
        assert!(!env.matches(Path::new("/etc/myapp/config.toml")));
    }

    #[test]
    fn test_subscription_builder() {
        let sub = Subscription::builder()
            .file("/path/to/file")
            .debounce_ms(200)
            .priority(10)
            .build();

        assert!(sub.active);
        assert_eq!(sub.debounce, Duration::from_millis(200));
        assert_eq!(sub.priority, 10);
    }

    #[test]
    fn test_subscription_activate_deactivate() {
        let sub = Subscription::default().deactivate();
        assert!(!sub.active);

        let sub = sub.activate();
        assert!(sub.active);
    }

    #[test]
    fn test_subscription_default_contract() {
        let sub = Subscription::default();

        assert!(matches!(sub.kind, SubscriptionKind::All));
        assert_eq!(sub.handler_id.as_str(), "default");
        assert_eq!(sub.priority, 0);
        assert_eq!(sub.max_batch_size, 100);
        assert!(sub.active);
    }
}
