//! Async blocking operations extractor.
//!
//! Detects blocking operations inside async contexts, which can cause
//! runtime stalls and prevent other async tasks from making progress:
//! - std::thread::sleep in async fn
//! - Blocking I/O calls in async contexts
//! - Synchronous network calls in async runtimes

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

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

/// Extractor for blocking operations in async contexts.
pub struct AsyncBlockingExtractor {
    /// Pattern for std::thread::sleep
    thread_sleep: Regex,
    /// Pattern for async fn or async move
    async_context: Regex,
}

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

impl AsyncBlockingExtractor {
    /// Create a new async blocking extractor with compiled regexes.
    ///
    /// # Panics
    /// Panics if any regex pattern is invalid (programmer error).
    #[allow(clippy::expect_used)]
    pub fn new() -> Self {
        Self {
            thread_sleep: Regex::new(r"std::thread::sleep|thread::sleep\s*\(")
                .expect("valid regex"),
            async_context: Regex::new(r"async\s+fn|async\s+move").expect("valid regex"),
        }
    }
}

impl Extractor for AsyncBlockingExtractor {
    fn name(&self) -> &str {
        "async_blocking"
    }

    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();
        let lines: Vec<&str> = content.lines().collect();

        // Track async context depth (simple brace counting)
        let mut in_async_context = false;
        let mut brace_depth = 0;
        let mut async_start_depth = 0;

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

            // Check if entering async context
            if self.async_context.is_match(line) {
                in_async_context = true;
                async_start_depth = brace_depth;
            }

            // Track brace depth
            for ch in line.chars() {
                match ch {
                    '{' => brace_depth += 1,
                    '}' => {
                        brace_depth -= 1;
                        // Exit async context when braces balance
                        if in_async_context && brace_depth <= async_start_depth {
                            in_async_context = false;
                        }
                    }
                    _ => {}
                }
            }

            // Detect blocking operations inside async context
            if in_async_context {
                if let Some(matched) = self.thread_sleep.find(line) {
                    let mut concept_path = path_segments.to_vec();
                    concept_path.push("async".to_string());
                    concept_path.push("runtime".to_string());

                    observations.push(Observation {
                        concept_path: format!("code://{}", concept_path.join("/")),
                        predicate: "blocking_forbidden".to_string(),
                        value: ObjectValue::Boolean(false), // Code violates (has blocking)
                        file: file.to_string(),
                        line: line_num,
                        matched_text: matched.as_str().to_string(),
                        confidence: 1.0,
                        description: "Blocking operation in async context: Use tokio::time::sleep or async-compatible alternatives instead of std::thread::sleep to avoid blocking the async runtime".to_string(),
                    });
                }
            }
        }

        observations
    }

    fn screening_patterns(&self) -> Vec<&str> {
        vec![r"async\s+fn", r"thread::sleep", r"std::thread::sleep"]
    }

    fn verifiable_predicates(&self) -> Vec<(&str, &str)> {
        vec![("async/runtime", "blocking_forbidden")]
    }
}

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

    #[test]
    fn detects_thread_sleep_in_async_fn() {
        let extractor = AsyncBlockingExtractor::new();
        let code = r#"
async fn process_message(msg: Message) -> Result<()> {
    println!("Processing: {:?}", msg);
    std::thread::sleep(Duration::from_secs(1));
    Ok(())
}
        "#;
        let obs = extractor.extract(
            &["rust".into(), "msgqueue".into(), "src".into(), "processor".into()],
            code,
            Language::Rust,
            "src/processor.rs",
        );
        assert_eq!(obs.len(), 1);
        assert!(obs[0].concept_path.ends_with("async/runtime"));
        assert_eq!(obs[0].predicate, "blocking_forbidden");
        assert_eq!(obs[0].value, ObjectValue::Boolean(false));
        assert!(obs[0].description.contains("tokio::time::sleep"));
    }

    #[test]
    fn detects_thread_sleep_in_async_move() {
        let extractor = AsyncBlockingExtractor::new();
        let code = r#"
tokio::spawn(async move {
    thread::sleep(Duration::from_millis(100));
    process();
});
        "#;
        let obs = extractor.extract(
            &["rust".into(), "msgqueue".into(), "src".into()],
            code,
            Language::Rust,
            "src/main.rs",
        );
        assert_eq!(obs.len(), 1);
    }

    #[test]
    fn no_false_positive_outside_async() {
        let extractor = AsyncBlockingExtractor::new();
        let code = r#"
fn sync_function() {
    std::thread::sleep(Duration::from_secs(1));
}
        "#;
        let obs = extractor.extract(
            &["rust".into(), "msgqueue".into(), "src".into()],
            code,
            Language::Rust,
            "src/sync.rs",
        );
        assert!(obs.is_empty(), "Should not flag blocking in sync context");
    }

    #[test]
    fn no_false_positive_for_tokio_sleep() {
        let extractor = AsyncBlockingExtractor::new();
        let code = r#"
async fn process() {
    tokio::time::sleep(Duration::from_secs(1)).await;
}
        "#;
        let obs = extractor.extract(
            &["rust".into(), "msgqueue".into(), "src".into()],
            code,
            Language::Rust,
            "src/async.rs",
        );
        assert!(obs.is_empty(), "Should not flag tokio::time::sleep");
    }
}
