//! Unbounded resource limits extractor.
//!
//! Detects unbounded resource configurations that can cause resource
//! exhaustion, memory bloat, or DoS conditions:
//! - max_queue_size: None (unbounded queue growth)
//! - prefetch_count: u16::MAX (unbounded prefetch)
//! - max_requeue_count: None (infinite requeue loops)

use regex::Regex;
use stemedb_core::types::ObjectValue;

use super::Extractor;
use crate::types::{Language, Observation};

/// Extractor for unbounded resource limit configurations.
pub struct UnboundedResourcesExtractor {
    /// Unbounded queue pattern: max_queue_size: None
    max_queue_pattern: Regex,
    /// Unbounded prefetch pattern: prefetch_count: u16::MAX
    prefetch_pattern: Regex,
    /// Unbounded requeue pattern: max_requeue_count: None
    requeue_pattern: Regex,
}

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

impl UnboundedResourcesExtractor {
    /// Create a new unbounded resources extractor with compiled regexes.
    ///
    /// # Panics
    /// Panics if any regex pattern is invalid (programmer error).
    #[allow(clippy::expect_used)]
    pub fn new() -> Self {
        Self {
            // Matches: max_queue_size: None, max_queue_size: Option::None
            max_queue_pattern: Regex::new(r"max_queue_size\s*:\s*(?:Option::)?None")
                .expect("valid regex"),
            // Matches: prefetch_count: u16::MAX, prefetch_count = u16::MAX
            prefetch_pattern: Regex::new(r"prefetch_count\s*[:\=]\s*u16::MAX")
                .expect("valid regex"),
            // Matches: max_requeue_count: None, max_requeue_count: Option::None
            requeue_pattern: Regex::new(r"max_requeue_count\s*:\s*(?:Option::)?None")
                .expect("valid regex"),
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn extract_observation(
        &self,
        path_segments: &[String],
        file: &str,
        line: usize,
        matched_text: &str,
        resource_type: &str,
        leaf_segments: &[&str],
        description: &str,
    ) -> Observation {
        let mut concept_path = path_segments.to_vec();
        for segment in leaf_segments {
            concept_path.push(segment.to_string());
        }

        Observation {
            concept_path: format!("code://{}", concept_path.join("/")),
            predicate: "bounded".to_string(),
            value: ObjectValue::Boolean(false), // Resource IS unbounded
            file: file.to_string(),
            line,
            matched_text: matched_text.to_string(),
            confidence: 1.0,
            description: format!("{} resource is unbounded: {}", resource_type, description),
        }
    }
}

impl Extractor for UnboundedResourcesExtractor {
    fn name(&self) -> &str {
        "unbounded_resources"
    }

    fn languages(&self) -> &[Language] {
        &[Language::Rust]
    }

    fn extract(
        &self,
        path_segments: &[String],
        content: &str,
        _language: Language,
        file: &str,
    ) -> Vec<Observation> {
        let mut observations = Vec::new();

        for (line_idx, line) in content.lines().enumerate() {
            let line_num = line_idx + 1;

            // Detection 1: max_queue_size: None
            if let Some(matched) = self.max_queue_pattern.find(line) {
                observations.push(self.extract_observation(
                    path_segments,
                    file,
                    line_num,
                    matched.as_str(),
                    "queue",
                    &["queue", "max_size"],
                    "Unbounded queue can grow without limit, causing OOM",
                ));
            }

            // Detection 2: prefetch_count: u16::MAX
            if let Some(matched) = self.prefetch_pattern.find(line) {
                observations.push(self.extract_observation(
                    path_segments,
                    file,
                    line_num,
                    matched.as_str(),
                    "consumer",
                    &["consumer", "prefetch_count"],
                    "Unbounded prefetch can exhaust memory under load",
                ));
            }

            // Detection 3: max_requeue_count: None
            if let Some(matched) = self.requeue_pattern.find(line) {
                observations.push(self.extract_observation(
                    path_segments,
                    file,
                    line_num,
                    matched.as_str(),
                    "consumer",
                    &["consumer", "requeue_limit"],
                    "Unbounded requeue can create infinite retry loops",
                ));
            }
        }

        observations
    }

    fn screening_patterns(&self) -> Vec<&str> {
        vec!["max_queue_size", "prefetch_count", "max_requeue_count"]
    }

    fn verifiable_predicates(&self) -> Vec<(&str, &str)> {
        vec![
            ("queue/max_size", "bounded"),
            ("consumer/prefetch_count", "bounded"),
            ("consumer/requeue_limit", "bounded"),
        ]
    }
}

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

    #[test]
    fn detects_unbounded_queue_size() {
        let extractor = UnboundedResourcesExtractor::new();
        let code = r#"
            Config {
                max_queue_size: None,
            }
        "#;
        let obs = extractor.extract(
            &["rust".into(), "msgqueue".into(), "src".into(), "config".into()],
            code,
            Language::Rust,
            "src/config.rs",
        );
        assert_eq!(obs.len(), 1);
        assert!(obs[0].concept_path.ends_with("queue/max_size"));
        assert_eq!(obs[0].predicate, "bounded");
        assert_eq!(obs[0].value, ObjectValue::Boolean(false));
    }

    #[test]
    fn detects_unbounded_prefetch() {
        let extractor = UnboundedResourcesExtractor::new();
        let code = r#"
            ConsumerConfig {
                prefetch_count: u16::MAX,
            }
        "#;
        let obs = extractor.extract(
            &["rust".into(), "msgqueue".into(), "src".into(), "config".into()],
            code,
            Language::Rust,
            "src/config.rs",
        );
        assert_eq!(obs.len(), 1);
        assert!(obs[0].concept_path.ends_with("consumer/prefetch_count"));
        assert_eq!(obs[0].predicate, "bounded");
    }

    #[test]
    fn detects_unbounded_requeue() {
        let extractor = UnboundedResourcesExtractor::new();
        let code = r#"
            ConsumerConfig {
                max_requeue_count: None,
            }
        "#;
        let obs = extractor.extract(
            &["rust".into(), "msgqueue".into(), "src".into(), "consumer".into()],
            code,
            Language::Rust,
            "src/consumer.rs",
        );
        assert_eq!(obs.len(), 1);
        assert!(obs[0].concept_path.ends_with("consumer/requeue_limit"));
        assert_eq!(obs[0].predicate, "bounded");
    }

    #[test]
    fn no_false_positives_for_bounded() {
        let extractor = UnboundedResourcesExtractor::new();
        let code = r#"
            Config {
                max_queue_size: Some(1000),
                prefetch_count: 100,
                max_requeue_count: Some(3),
            }
        "#;
        let obs = extractor.extract(
            &["rust".into(), "msgqueue".into(), "src".into(), "config".into()],
            code,
            Language::Rust,
            "src/config.rs",
        );
        assert!(obs.is_empty(), "Should not detect bounded resources");
    }
}
