//! Durability configuration extractor.
//!
//! Detects WAL durability settings that impact data integrity guarantees.
//! Critical for systems that must survive crashes or power failures.

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

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

/// Extractor for durability configuration.
///
/// Detects:
/// - DurabilityLevel::Eventual (risky - no fsync)
/// - DurabilityLevel::Batched (balanced - periodic fsync)
/// - DurabilityLevel::Immediate (safe - fsync after every write)
/// - YAML/TOML config: `durability: "eventual"` or `fsync_strategy = "none"`
pub struct DurabilityConfigExtractor {
    /// Rust enum patterns
    durability_enum: Regex,
    /// YAML/TOML patterns
    yaml_durability: Regex,
    toml_fsync: Regex,
    /// Batched configuration
    batched_pattern: Regex,
}

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

impl DurabilityConfigExtractor {
    /// Create a new durability config extractor.
    ///
    /// # Panics
    /// Panics if any regex pattern is invalid (programmer error).
    #[allow(clippy::expect_used)]
    pub fn new() -> Self {
        Self {
            // Rust: DurabilityLevel::Eventual | ::Batched | ::Immediate
            durability_enum: Regex::new(r"DurabilityLevel::(Eventual|Batched|Immediate)")
                .expect("valid regex"),

            // YAML: durability: "eventual" | "batched" | "immediate"
            yaml_durability: Regex::new(
                r#"(?i)durability\s*:\s*["']?(eventual|batched|immediate)["']?"#,
            )
            .expect("valid regex"),

            // TOML: fsync_strategy = "none" | "batched" | "immediate"
            toml_fsync: Regex::new(
                r#"(?i)fsync_strategy\s*=\s*["']?(none|batched|immediate)["']?"#,
            )
            .expect("valid regex"),

            // Batched with parameters: DurabilityLevel::batched_with(max_writes, max_duration)
            batched_pattern: Regex::new(r"DurabilityLevel::batched(?:_with)?\(")
                .expect("valid regex"),
        }
    }

    /// Determine confidence based on context.
    fn confidence_for_file(&self, file: &str) -> f32 {
        if file.contains("test") || file.contains("example") || file.contains("bench") {
            0.5 // Test/example code doesn't reflect production config
        } else {
            1.0 // Production code
        }
    }

    /// Extract strategy name and normalize it.
    fn normalize_strategy(&self, strategy: &str) -> &'static str {
        match strategy.to_lowercase().as_str() {
            "eventual" | "none" => "eventual",
            "batched" => "batched",
            "immediate" => "immediate",
            _ => "unknown",
        }
    }
}

impl Extractor for DurabilityConfigExtractor {
    fn name(&self) -> &str {
        "durability_config"
    }

    fn languages(&self) -> &[Language] {
        &[Language::Rust, Language::Go, Language::Yaml, Language::Toml, Language::Json]
    }

    fn extract(
        &self,
        path_segments: &[String],
        content: &str,
        _language: Language,
        file: &str,
    ) -> Vec<ExtractedClaim> {
        let mut claims = Vec::new();
        let confidence = self.confidence_for_file(file);

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

            // Check Rust enum patterns
            if let Some(cap) = self.durability_enum.captures(line) {
                let level = cap.get(1).map_or("", |m| m.as_str());
                let normalized = self.normalize_strategy(level);

                let mut concept_path = path_segments.to_vec();
                concept_path.push("wal".to_string());
                concept_path.push("durability".to_string());

                claims.push(ExtractedClaim {
                    concept_path: format!("code://{}", concept_path.join("/")),
                    predicate: "strategy".to_string(),
                    value: ObjectValue::Text(normalized.to_string()),
                    file: file.to_string(),
                    line: line_num,
                    matched_text: line.trim().to_string(),
                    confidence,
                    description: format!("WAL durability set to {}", normalized),
                });
            }

            // Check YAML durability patterns
            if let Some(cap) = self.yaml_durability.captures(line) {
                let level = cap.get(1).map_or("", |m| m.as_str());
                let normalized = self.normalize_strategy(level);

                let mut concept_path = path_segments.to_vec();
                concept_path.push("wal".to_string());
                concept_path.push("durability".to_string());

                claims.push(ExtractedClaim {
                    concept_path: format!("code://{}", concept_path.join("/")),
                    predicate: "strategy".to_string(),
                    value: ObjectValue::Text(normalized.to_string()),
                    file: file.to_string(),
                    line: line_num,
                    matched_text: line.trim().to_string(),
                    confidence,
                    description: format!("WAL durability configured as {}", normalized),
                });
            }

            // Check TOML fsync_strategy patterns
            if let Some(cap) = self.toml_fsync.captures(line) {
                let strategy = cap.get(1).map_or("", |m| m.as_str());
                let normalized = self.normalize_strategy(strategy);

                let mut concept_path = path_segments.to_vec();
                concept_path.push("wal".to_string());
                concept_path.push("durability".to_string());

                claims.push(ExtractedClaim {
                    concept_path: format!("code://{}", concept_path.join("/")),
                    predicate: "strategy".to_string(),
                    value: ObjectValue::Text(normalized.to_string()),
                    file: file.to_string(),
                    line: line_num,
                    matched_text: line.trim().to_string(),
                    confidence,
                    description: format!("Fsync strategy set to {}", normalized),
                });
            }

            // Check for batched configuration with custom parameters
            if self.batched_pattern.is_match(line) {
                let mut concept_path = path_segments.to_vec();
                concept_path.push("wal".to_string());
                concept_path.push("durability".to_string());

                claims.push(ExtractedClaim {
                    concept_path: format!("code://{}", concept_path.join("/")),
                    predicate: "strategy".to_string(),
                    value: ObjectValue::Text("batched".to_string()),
                    file: file.to_string(),
                    line: line_num,
                    matched_text: line.trim().to_string(),
                    confidence: confidence * 0.9, // Slightly lower since we're not parsing params
                    description: "WAL durability set to batched with custom parameters".to_string(),
                });
            }
        }

        claims
    }
}

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

    #[test]
    fn test_rust_eventual() {
        let extractor = DurabilityConfigExtractor::new();
        let content = r#"
            let journal = Journal::open(&wal_path)
                .with_durability(DurabilityLevel::Eventual);
        "#;

        let claims = extractor.extract(
            &["rust".to_string(), "myproject".to_string()],
            content,
            Language::Rust,
            "src/wal.rs",
        );

        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].predicate, "strategy");
        if let ObjectValue::Text(ref value) = claims[0].value {
            assert_eq!(value, "eventual");
        } else {
            panic!("Expected Text value");
        }
        assert!(claims[0].concept_path.contains("wal/durability"));
        assert_eq!(claims[0].confidence, 1.0);
    }

    #[test]
    fn test_rust_batched() {
        let extractor = DurabilityConfigExtractor::new();
        let content = r#"
            DurabilityLevel::Batched { max_writes: 100, max_duration: Duration::from_secs(1) }
        "#;

        let claims =
            extractor.extract(&["rust".to_string()], content, Language::Rust, "src/config.rs");

        assert_eq!(claims.len(), 1);
        if let ObjectValue::Text(ref value) = claims[0].value {
            assert_eq!(value, "batched");
        } else {
            panic!("Expected Text value");
        }
    }

    #[test]
    fn test_rust_immediate() {
        let extractor = DurabilityConfigExtractor::new();
        let content = r#"
            let guard = FsyncGuard::new(file, path, DurabilityLevel::Immediate);
        "#;

        let claims =
            extractor.extract(&["rust".to_string()], content, Language::Rust, "src/guard.rs");

        assert_eq!(claims.len(), 1);
        if let ObjectValue::Text(ref value) = claims[0].value {
            assert_eq!(value, "immediate");
        } else {
            panic!("Expected Text value");
        }
    }

    #[test]
    fn test_yaml_config() {
        let extractor = DurabilityConfigExtractor::new();
        let content = r#"
wal:
  durability: "eventual"
  max_size: 1GB
"#;

        let claims = extractor.extract(
            &["config".to_string()],
            content,
            Language::Yaml,
            "config/storage.yaml",
        );

        assert_eq!(claims.len(), 1);
        if let ObjectValue::Text(ref value) = claims[0].value {
            assert_eq!(value, "eventual");
        } else {
            panic!("Expected Text value");
        }
    }

    #[test]
    fn test_toml_fsync_none() {
        let extractor = DurabilityConfigExtractor::new();
        let content = r#"
[wal]
fsync_strategy = "none"
max_file_size = 104857600
"#;

        let claims =
            extractor.extract(&["config".to_string()], content, Language::Toml, "config.toml");

        assert_eq!(claims.len(), 1);
        if let ObjectValue::Text(ref value) = claims[0].value {
            assert_eq!(value, "eventual"); // Normalized from "none" to "eventual"
        } else {
            panic!("Expected Text value");
        }
    }

    #[test]
    fn test_batched_with_params() {
        let extractor = DurabilityConfigExtractor::new();
        let content = r#"
            let level = DurabilityLevel::batched_with(50, Duration::from_millis(100));
        "#;

        let claims =
            extractor.extract(&["rust".to_string()], content, Language::Rust, "src/journal.rs");

        assert_eq!(claims.len(), 1);
        if let ObjectValue::Text(ref value) = claims[0].value {
            assert_eq!(value, "batched");
        } else {
            panic!("Expected Text value");
        }
    }

    #[test]
    fn test_confidence_in_test_file() {
        let extractor = DurabilityConfigExtractor::new();
        let content = r#"
            let journal = Journal::open(&wal_path)
                .with_durability(DurabilityLevel::Eventual);
        "#;

        let claims =
            extractor.extract(&["rust".to_string()], content, Language::Rust, "src/wal_test.rs");

        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].confidence, 0.5); // Test file gets reduced confidence
    }

    #[test]
    fn test_multiple_durability_settings() {
        let extractor = DurabilityConfigExtractor::new();
        let content = r#"
            if testing {
                journal.with_durability(DurabilityLevel::Eventual);
            } else {
                journal.with_durability(DurabilityLevel::Immediate);
            }
        "#;

        let claims =
            extractor.extract(&["rust".to_string()], content, Language::Rust, "src/config.rs");

        assert_eq!(claims.len(), 2);
        // Should detect both eventual and immediate
        let values: Vec<_> = claims
            .iter()
            .filter_map(|c| {
                if let ObjectValue::Text(ref v) = c.value {
                    Some(v.as_str())
                } else {
                    None
                }
            })
            .collect();
        assert!(values.contains(&"eventual"));
        assert!(values.contains(&"immediate"));
    }
}
