//! Type Validator
//!
//! Validates that tasks reference types that actually exist in the codebase.
//! Uses the Kuzu knowledge graph to look up type definitions and cross-checks
//! them against type names mentioned in task descriptions and generated code.

use std::collections::HashSet;
use std::sync::Arc;

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

use crate::db::KuzuGraph;
use crate::types::{Severity, Task, TaskType, ValidationResult};

/// Validates type references in tasks against the knowledge graph.
pub struct TypeValidator {
    kuzu: Arc<KuzuGraph>,
}

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

    /// Validate type references across all supplied tasks.
    #[instrument(skip(self, tasks), fields(task_count = tasks.len()))]
    pub async fn validate_types(
        &self,
        tasks: &[Task],
    ) -> Result<Vec<ValidationResult>> {
        let mut results = Vec::new();

        for task in tasks {
            let result = self.check_task_types(task).await?;
            results.push(result);
        }

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

        Ok(results)
    }

    /// For a single task, extract mentioned type names from the description
    /// (and generated code if present), then verify they exist in the graph.
    async fn check_task_types(&self, task: &Task) -> Result<ValidationResult> {
        // Collect known type names from the Kuzu graph for this task's target
        // file and its dependencies.
        let known_types = self.collect_known_types(&task.target_file).await?;

        // Extract type names mentioned in the task description.
        let mut mentioned = self.extract_type_references(&task.description);

        // Also scan generated code if present.
        if let Some(ref code) = task.generated_code {
            let code_types = self.extract_type_references(code);
            mentioned.extend(code_types);
        }

        // Delete tasks are allowed to reference types that will be removed.
        if task.task_type == TaskType::Delete {
            return Ok(ValidationResult {
                constraint_id: format!("types_{}", task.id),
                passed: true,
                message: "Delete tasks are exempt from type reference checks".to_string(),
                severity: Severity::Info,
            });
        }

        // Create tasks are allowed to introduce new types.
        if task.task_type == TaskType::Create && known_types.is_empty() {
            return Ok(ValidationResult {
                constraint_id: format!("types_{}", task.id),
                passed: true,
                message: "Create task introduces a new file; type check deferred".to_string(),
                severity: Severity::Info,
            });
        }

        // Check for missing types.
        let missing: Vec<String> = mentioned
            .iter()
            .filter(|t| !known_types.contains(t.as_str()) && !is_builtin_type(t))
            .cloned()
            .collect();

        if missing.is_empty() {
            Ok(ValidationResult {
                constraint_id: format!("types_{}", task.id),
                passed: true,
                message: format!(
                    "All referenced types are valid ({} checked)",
                    mentioned.len()
                ),
                severity: Severity::Info,
            })
        } else {
            warn!(
                task_id = %task.id,
                missing = ?missing,
                "Undefined type references"
            );
            Ok(ValidationResult {
                constraint_id: format!("types_{}", task.id),
                passed: false,
                message: format!(
                    "Undefined types referenced in task '{}': {}",
                    task.id,
                    missing.join(", ")
                ),
                severity: Severity::Error,
            })
        }
    }

    /// Query the Kuzu graph for all type names defined in `target_file` and
    /// any files it imports.
    async fn collect_known_types(&self, target_file: &str) -> Result<HashSet<String>> {
        let mut known = HashSet::new();

        // Types defined in the target file itself.
        let query = format!(
            "MATCH (f:File {{path: '{}'}})-[:DEFINES]->(t:Type) \
             RETURN t.name AS type_name",
            target_file
        );
        let rows = self.kuzu.execute_query(&query).await?;
        for row in &rows {
            if let Some(name) = row.get("type_name") {
                known.insert(name.clone());
            }
        }

        // Types available through imports (file-level dependencies).
        let dep_files = self.kuzu.get_dependencies(target_file).await?;
        for dep_file in &dep_files {
            if let Some(entity) = self.kuzu.get_file(dep_file).await? {
                for type_def in &entity.types {
                    known.insert(type_def.name.clone());
                }
                // Also add exported function names as callable identifiers.
                for export in &entity.exports {
                    known.insert(export.clone());
                }
            }
        }

        // Also pull types from the target file entity directly (covers the
        // case where the graph DEFINES query returns nothing but the entity
        // does have types populated).
        if let Some(entity) = self.kuzu.get_file(target_file).await? {
            for type_def in &entity.types {
                known.insert(type_def.name.clone());
            }
        }

        Ok(known)
    }

    /// Extract potential type references from text. Looks for PascalCase
    /// identifiers that are likely type/struct/enum names.
    fn extract_type_references(&self, text: &str) -> HashSet<String> {
        // Match PascalCase identifiers (at least two words, like `FooBar`,
        // `ValidationResult`, etc.). Single uppercase words (like `Result`)
        // are also captured.
        let re = Regex::new(r"\b([A-Z][a-z]+(?:[A-Z][a-z0-9]*)+)\b")
            .expect("regex should compile");

        let mut types: HashSet<String> = re
            .find_iter(text)
            .map(|m| m.as_str().to_string())
            .collect();

        // Also capture generic references like `Vec<Foo>` or `Option<Bar>`.
        let generic_re = Regex::new(r"<([A-Z][a-zA-Z0-9]+)>")
            .expect("regex should compile");

        for cap in generic_re.captures_iter(text) {
            if let Some(inner) = cap.get(1) {
                types.insert(inner.as_str().to_string());
            }
        }

        types
    }
}

/// Returns true if the type name is a well-known built-in or standard library
/// type that does not need to be defined in the project.
fn is_builtin_type(name: &str) -> bool {
    const BUILTINS: &[&str] = &[
        // Rust primitives and standard types
        "String", "Vec", "HashMap", "HashSet", "Option", "Result",
        "Box", "Arc", "Rc", "Mutex", "RwLock", "Cell", "RefCell",
        "Path", "PathBuf", "Duration", "Instant",
        // Common library types
        "DateTime", "Utc", "Regex", "Error",
        // Serde
        "Serialize", "Deserialize",
        // Generic markers
        "Send", "Sync", "Clone", "Debug", "Display",
        "Default", "PartialEq", "Eq", "Hash", "Ord", "PartialOrd",
    ];

    BUILTINS.contains(&name)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{Task, TaskStatus, TaskType};
    use chrono::Utc;
    use std::path::Path;

    fn make_task(id: &str, task_type: TaskType, desc: &str) -> Task {
        Task {
            id: id.to_string(),
            scroll_id: "scroll_1".to_string(),
            description: desc.to_string(),
            target_file: format!("src/{}.rs", id),
            task_type,
            status: TaskStatus::Pending,
            dependencies: vec![],
            generated_code: None,
            diff: None,
            created_at: Utc::now(),
            group_label: None,
        }
    }

    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() -> TypeValidator {
        let kuzu = Arc::new(
            KuzuGraph::new(Path::new(&format!("/tmp/furnace_test_tv_kuzu_{}_{}", std::process::id(), next_test_id())))
                .expect("failed to create test KuzuGraph"),
        );
        TypeValidator::new(kuzu)
    }

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

    #[tokio::test]
    async fn test_create_task_is_deferred() {
        let v = make_validator().await;
        let task = make_task("t1", TaskType::Create, "Create FooBar struct");
        let results = v.validate_types(&[task]).await.unwrap();
        assert_eq!(results.len(), 1);
        assert!(results[0].passed);
        assert!(results[0].message.contains("deferred"));
    }

    #[tokio::test]
    async fn test_delete_task_is_exempt() {
        let v = make_validator().await;
        let task = make_task("t1", TaskType::Delete, "Delete OldStruct");
        let results = v.validate_types(&[task]).await.unwrap();
        assert_eq!(results.len(), 1);
        assert!(results[0].passed);
        assert!(results[0].message.contains("exempt"));
    }

    #[tokio::test]
    async fn test_modify_task_with_builtins_passes() {
        let v = make_validator().await;
        let task = make_task(
            "t1",
            TaskType::Modify,
            "Add a HashMap and Vec to the existing module",
        );
        let results = v.validate_types(&[task]).await.unwrap();
        assert_eq!(results.len(), 1);
        // All referenced types are builtins, so this should pass.
        assert!(results[0].passed);
    }

    #[test]
    fn test_extract_type_references() {
        let v = TypeValidator::new(Arc::new(
            KuzuGraph::new(Path::new(&format!("/tmp/furnace_test_tv_extract_{}_{}", std::process::id(), next_test_id())))
                .expect("create graph"),
        ));
        let text = "Implement ValidationReport and check ConstraintType";
        let types = v.extract_type_references(text);
        assert!(types.contains("ValidationReport"));
        assert!(types.contains("ConstraintType"));
    }

    #[test]
    fn test_extract_generic_type_references() {
        let v = TypeValidator::new(Arc::new(
            KuzuGraph::new(Path::new(&format!("/tmp/furnace_test_tv_gen_{}_{}", std::process::id(), next_test_id())))
                .expect("create graph"),
        ));
        let text = "Returns Vec<ValidationResult> and Option<ScrollStatus>";
        let types = v.extract_type_references(text);
        assert!(types.contains("ValidationResult"));
        assert!(types.contains("ScrollStatus"));
    }

    #[test]
    fn test_is_builtin_type() {
        assert!(is_builtin_type("String"));
        assert!(is_builtin_type("Vec"));
        assert!(is_builtin_type("HashMap"));
        assert!(is_builtin_type("Option"));
        assert!(!is_builtin_type("ValidationReport"));
        assert!(!is_builtin_type("Morpheus"));
    }

    #[tokio::test]
    async fn test_multiple_tasks() {
        let v = make_validator().await;
        let tasks = vec![
            make_task("t1", TaskType::Create, "Create FooBar"),
            make_task("t2", TaskType::Delete, "Delete OldThing"),
            make_task("t3", TaskType::Test, "Test with HashMap and Vec"),
        ];
        let results = v.validate_types(&tasks).await.unwrap();
        assert_eq!(results.len(), 3);
    }
}
