//! Constraint Validator
//!
//! Validates SCROLL constraints against the current codebase state using the
//! Kuzu knowledge graph. Supports all four constraint types: Must, MustNot,
//! Should, and ShouldNot.

use std::sync::Arc;

use anyhow::Result;
use regex::Regex;
use tracing::{debug, instrument, warn};

use crate::db::KuzuGraph;
use crate::types::{Constraint, ConstraintType, Severity, ValidationResult};

/// Validates individual constraints by querying the knowledge graph for
/// pattern matches and codebase conformance.
pub struct ConstraintValidator {
    kuzu: Arc<KuzuGraph>,
}

impl ConstraintValidator {
    pub fn new(kuzu: Arc<KuzuGraph>) -> Self {
        Self { kuzu }
    }

    /// Validate all constraints in a SCROLL, returning one `ValidationResult`
    /// per constraint.
    #[instrument(skip(self, constraints), fields(count = constraints.len()))]
    pub async fn validate_all(
        &self,
        constraints: &[Constraint],
    ) -> Result<Vec<ValidationResult>> {
        let mut results = Vec::with_capacity(constraints.len());

        for constraint in constraints {
            let result = self.validate_constraint(constraint).await?;
            results.push(result);
        }

        debug!(
            total = results.len(),
            passed = results.iter().filter(|r| r.passed).count(),
            "Constraint validation complete"
        );

        Ok(results)
    }

    /// Dispatch to the appropriate handler based on constraint type.
    async fn validate_constraint(
        &self,
        constraint: &Constraint,
    ) -> Result<ValidationResult> {
        match &constraint.constraint_type {
            ConstraintType::Must => self.validate_must(constraint).await,
            ConstraintType::MustNot => self.validate_must_not(constraint).await,
            ConstraintType::Should => self.validate_should(constraint).await,
            ConstraintType::ShouldNot => self.validate_should_not(constraint).await,
        }
    }

    /// MUST constraints: the pattern **must** exist in the codebase.
    /// Failure is an Error-severity violation.
    async fn validate_must(&self, constraint: &Constraint) -> Result<ValidationResult> {
        let matches = self.query_pattern_in_graph(&constraint.pattern).await?;

        if matches.is_empty() {
            Ok(ValidationResult {
                constraint_id: constraint.id.clone(),
                passed: false,
                message: format!(
                    "MUST constraint violated: pattern '{}' not found in codebase. {}",
                    constraint.pattern, constraint.description
                ),
                severity: Severity::Error,
            })
        } else {
            Ok(ValidationResult {
                constraint_id: constraint.id.clone(),
                passed: true,
                message: format!(
                    "MUST constraint satisfied: pattern '{}' found in {} location(s)",
                    constraint.pattern,
                    matches.len()
                ),
                severity: Severity::Info,
            })
        }
    }

    /// MUST NOT constraints: the pattern **must not** exist in the codebase.
    /// Presence is an Error-severity violation.
    async fn validate_must_not(&self, constraint: &Constraint) -> Result<ValidationResult> {
        let matches = self.query_pattern_in_graph(&constraint.pattern).await?;

        if matches.is_empty() {
            Ok(ValidationResult {
                constraint_id: constraint.id.clone(),
                passed: true,
                message: format!(
                    "MUST NOT constraint satisfied: pattern '{}' not found",
                    constraint.pattern
                ),
                severity: Severity::Info,
            })
        } else {
            Ok(ValidationResult {
                constraint_id: constraint.id.clone(),
                passed: false,
                message: format!(
                    "MUST NOT constraint violated: pattern '{}' found in {} location(s). {}",
                    constraint.pattern,
                    matches.len(),
                    constraint.description
                ),
                severity: Severity::Error,
            })
        }
    }

    /// SHOULD constraints: the pattern **should** exist. Absence is a Warning.
    async fn validate_should(&self, constraint: &Constraint) -> Result<ValidationResult> {
        let matches = self.query_pattern_in_graph(&constraint.pattern).await?;

        if matches.is_empty() {
            warn!(
                constraint_id = %constraint.id,
                pattern = %constraint.pattern,
                "SHOULD constraint not satisfied"
            );
            Ok(ValidationResult {
                constraint_id: constraint.id.clone(),
                passed: true, // SHOULD violations don't fail validation
                message: format!(
                    "SHOULD constraint advisory: pattern '{}' not found. {}",
                    constraint.pattern, constraint.description
                ),
                severity: Severity::Warning,
            })
        } else {
            Ok(ValidationResult {
                constraint_id: constraint.id.clone(),
                passed: true,
                message: format!(
                    "SHOULD constraint satisfied: pattern '{}' found in {} location(s)",
                    constraint.pattern,
                    matches.len()
                ),
                severity: Severity::Info,
            })
        }
    }

    /// SHOULD NOT constraints: the pattern **should not** exist. Presence is a Warning.
    async fn validate_should_not(&self, constraint: &Constraint) -> Result<ValidationResult> {
        let matches = self.query_pattern_in_graph(&constraint.pattern).await?;

        if matches.is_empty() {
            Ok(ValidationResult {
                constraint_id: constraint.id.clone(),
                passed: true,
                message: format!(
                    "SHOULD NOT constraint satisfied: pattern '{}' not found",
                    constraint.pattern
                ),
                severity: Severity::Info,
            })
        } else {
            warn!(
                constraint_id = %constraint.id,
                pattern = %constraint.pattern,
                match_count = matches.len(),
                "SHOULD NOT constraint not satisfied"
            );
            Ok(ValidationResult {
                constraint_id: constraint.id.clone(),
                passed: true, // SHOULD NOT violations don't fail validation
                message: format!(
                    "SHOULD NOT constraint advisory: pattern '{}' found in {} location(s). {}",
                    constraint.pattern,
                    matches.len(),
                    constraint.description
                ),
                severity: Severity::Warning,
            })
        }
    }

    /// Query the knowledge graph for occurrences of a pattern. Falls back to a
    /// regex-based check on exported type/function names when the graph query
    /// yields no structured results.
    async fn query_pattern_in_graph(&self, pattern: &str) -> Result<Vec<String>> {
        // First try a CONTAINS query (functions/patterns in the graph)
        let query = format!(
            "MATCH (f:File)-[:CONTAINS]->(p:Pattern {{name: '{}'}}) RETURN f.path AS file_path",
            pattern
        );

        let graph_results = self.kuzu.execute_query(&query).await?;

        if !graph_results.is_empty() {
            return Ok(graph_results
                .into_iter()
                .filter_map(|row| row.get("file_path").cloned())
                .collect());
        }

        // Fall back: search DEFINES for types matching the pattern
        let defines_query = "MATCH (f:File)-[:DEFINES]->(t:Type) \
                             RETURN f.path AS file_path, t.name AS type_name";

        let type_results = self.kuzu.execute_query(defines_query).await?;

        // Compile the pattern as a regex; if it fails, treat it as a literal
        let re = Regex::new(pattern).ok();

        let mut matched_files = Vec::new();
        for row in &type_results {
            if let Some(type_name) = row.get("type_name") {
                let is_match = match &re {
                    Some(regex) => regex.is_match(type_name),
                    None => type_name.contains(pattern),
                };
                if is_match {
                    if let Some(file_path) = row.get("file_path") {
                        matched_files.push(file_path.clone());
                    }
                }
            }
        }

        Ok(matched_files)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{Constraint, ConstraintSource, ConstraintType};
    use std::path::Path;

    fn next_test_id() -> u64 {
        static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
        COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
    }

    async fn make_validator() -> ConstraintValidator {
        let kuzu = Arc::new(
            KuzuGraph::new(Path::new(&format!("/tmp/furnace_test_cv_kuzu_{}_{}", std::process::id(), next_test_id())))
                .expect("failed to create test KuzuGraph"),
        );
        ConstraintValidator::new(kuzu)
    }

    fn make_constraint(id: &str, ct: ConstraintType, pattern: &str) -> Constraint {
        Constraint {
            id: id.to_string(),
            constraint_type: ct,
            description: format!("Test constraint for {}", pattern),
            pattern: pattern.to_string(),
            source: ConstraintSource::UserSpecified,
        }
    }

    #[tokio::test]
    async fn test_must_constraint_not_found() {
        let v = make_validator().await;
        let c = make_constraint("c1", ConstraintType::Must, "NonExistentPattern");
        let results = v.validate_all(&[c]).await.unwrap();
        assert_eq!(results.len(), 1);
        assert!(!results[0].passed);
        assert_eq!(results[0].severity, Severity::Error);
    }

    #[tokio::test]
    async fn test_must_not_constraint_not_found_passes() {
        let v = make_validator().await;
        let c = make_constraint("c2", ConstraintType::MustNot, "NonExistentPattern");
        let results = v.validate_all(&[c]).await.unwrap();
        assert_eq!(results.len(), 1);
        assert!(results[0].passed);
        assert_eq!(results[0].severity, Severity::Info);
    }

    #[tokio::test]
    async fn test_should_constraint_not_found_is_warning_but_passes() {
        let v = make_validator().await;
        let c = make_constraint("c3", ConstraintType::Should, "SomePattern");
        let results = v.validate_all(&[c]).await.unwrap();
        assert_eq!(results.len(), 1);
        // SHOULD constraints pass even when the pattern is absent
        assert!(results[0].passed);
        assert_eq!(results[0].severity, Severity::Warning);
    }

    #[tokio::test]
    async fn test_should_not_constraint_not_found_passes() {
        let v = make_validator().await;
        let c = make_constraint("c4", ConstraintType::ShouldNot, "BadPattern");
        let results = v.validate_all(&[c]).await.unwrap();
        assert_eq!(results.len(), 1);
        assert!(results[0].passed);
    }

    #[tokio::test]
    async fn test_empty_constraints_returns_empty() {
        let v = make_validator().await;
        let results = v.validate_all(&[]).await.unwrap();
        assert!(results.is_empty());
    }

    #[tokio::test]
    async fn test_multiple_constraints() {
        let v = make_validator().await;
        let constraints = vec![
            make_constraint("c1", ConstraintType::Must, "PatternA"),
            make_constraint("c2", ConstraintType::MustNot, "PatternB"),
            make_constraint("c3", ConstraintType::Should, "PatternC"),
        ];
        let results = v.validate_all(&constraints).await.unwrap();
        assert_eq!(results.len(), 3);
    }
}
