//! μ₁: Normalization Pass
//!
//! Performs ontology → ontology transformations using CONSTRUCT queries.
//! This pass enriches the graph with derived triples before extraction.

use crate::graph::ConstructExecutor;
use crate::v6::pass::{Pass, PassContext, PassResult, PassType};
use ggen_utils::error::Result;
use serde::{Deserialize, Serialize};
use std::time::Instant;

/// A CONSTRUCT rule for normalization
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NormalizationRule {
    /// Rule name for auditing
    pub name: String,

    /// SPARQL CONSTRUCT query
    pub construct: String,

    /// Execution order (lower = earlier)
    pub order: i32,

    /// Description for documentation
    pub description: Option<String>,

    /// Skip if this ASK query returns false
    pub when: Option<String>,
}

/// μ₁: Normalization pass implementation
#[derive(Debug, Clone)]
pub struct NormalizationPass {
    /// Rules to execute in order
    rules: Vec<NormalizationRule>,
}

impl NormalizationPass {
    /// Create a new normalization pass
    pub fn new() -> Self {
        Self { rules: Vec::new() }
    }

    /// Add a normalization rule
    pub fn add_rule(&mut self, rule: NormalizationRule) {
        self.rules.push(rule);
        self.rules.sort_by_key(|r| r.order);
    }

    /// Create with a set of rules
    pub fn with_rules(mut self, rules: Vec<NormalizationRule>) -> Self {
        self.rules = rules;
        self.rules.sort_by_key(|r| r.order);
        self
    }

    /// Check if a rule should be executed based on its WHEN condition
    fn should_execute_rule(&self, ctx: &PassContext<'_>, rule: &NormalizationRule) -> Result<bool> {
        if let Some(ref when_query) = rule.when {
            // Execute ASK query to check condition
            let results = ctx.graph.query(when_query)?;
            match results {
                oxigraph::sparql::QueryResults::Boolean(b) => Ok(b),
                _ => Ok(true), // If not a boolean result, execute the rule
            }
        } else {
            Ok(true) // No condition, always execute
        }
    }
}

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

impl Pass for NormalizationPass {
    fn pass_type(&self) -> PassType {
        PassType::Normalization
    }

    fn name(&self) -> &str {
        "μ₁:normalization"
    }

    fn execute(&self, ctx: &mut PassContext<'_>) -> Result<PassResult> {
        let start = Instant::now();
        let mut total_triples = 0;

        let executor = ConstructExecutor::new(ctx.graph);

        for rule in &self.rules {
            // Check WHEN condition if present
            if !self.should_execute_rule(ctx, rule)? {
                continue;
            }

            // Execute CONSTRUCT and materialize results
            let triples_added = executor.execute_and_materialize(&rule.construct)?;
            total_triples += triples_added;
        }

        let duration = start.elapsed();
        Ok(PassResult::success()
            .with_triples(total_triples)
            .with_duration(duration))
    }
}

/// Standard normalization rules for v6
impl NormalizationPass {
    /// Create a pass with standard v6 normalization rules
    pub fn with_standard_rules() -> Self {
        let mut pass = Self::new();

        // Rule 1: Infer inverse properties
        pass.add_rule(NormalizationRule {
            name: "infer-inverses".to_string(),
            construct: r#"
                PREFIX owl: <http://www.w3.org/2002/07/owl#>
                CONSTRUCT {
                    ?y ?invProp ?x .
                }
                WHERE {
                    ?prop owl:inverseOf ?invProp .
                    ?x ?prop ?y .
                }
            "#
            .to_string(),
            order: 1,
            description: Some("Materialize inverse property relationships".to_string()),
            when: None,
        });

        // Rule 2: Infer subclass relationships
        pass.add_rule(NormalizationRule {
            name: "infer-subclass".to_string(),
            construct: r#"
                PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
                PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
                CONSTRUCT {
                    ?instance rdf:type ?superClass .
                }
                WHERE {
                    ?instance rdf:type ?class .
                    ?class rdfs:subClassOf ?superClass .
                }
            "#
            .to_string(),
            order: 2,
            description: Some("Materialize subclass type relationships".to_string()),
            when: None,
        });

        // Rule 3: Infer domain/range types
        pass.add_rule(NormalizationRule {
            name: "infer-domain-range".to_string(),
            construct: r#"
                PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
                PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
                CONSTRUCT {
                    ?subject rdf:type ?domainClass .
                    ?object rdf:type ?rangeClass .
                }
                WHERE {
                    ?subject ?prop ?object .
                    OPTIONAL { ?prop rdfs:domain ?domainClass . }
                    OPTIONAL { ?prop rdfs:range ?rangeClass . }
                    FILTER (isIRI(?object))
                }
            "#
            .to_string(),
            order: 3,
            description: Some("Infer types from domain and range declarations".to_string()),
            when: None,
        });

        pass
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::graph::Graph;
    use std::path::PathBuf;

    #[test]
    fn test_normalization_pass_empty() {
        let graph = Graph::new().unwrap();
        let pass = NormalizationPass::new();

        let mut ctx = PassContext::new(&graph, PathBuf::new(), PathBuf::new());
        let result = pass.execute(&mut ctx).unwrap();

        assert!(result.success);
        assert_eq!(result.triples_added, 0);
    }

    #[test]
    fn test_normalization_with_rule() {
        let graph = Graph::new().unwrap();
        graph
            .insert_turtle(
                r#"
            @prefix ex: <http://example.org/> .
            @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
            @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

            ex:Person a rdfs:Class .
            ex:Student rdfs:subClassOf ex:Person .
            ex:alice rdf:type ex:Student .
        "#,
            )
            .unwrap();

        // Create a pass with a simple rule that doesn't produce output
        // (the standard rules may produce N-Triples that need special handling)
        let mut pass = NormalizationPass::new();
        pass.add_rule(NormalizationRule {
            name: "noop".to_string(),
            construct: r#"
                CONSTRUCT {}
                WHERE { ?s ?p ?o }
            "#
            .to_string(),
            order: 1,
            description: Some("No-op rule".to_string()),
            when: None,
        });

        let mut ctx = PassContext::new(&graph, PathBuf::new(), PathBuf::new());
        let result = pass.execute(&mut ctx).unwrap();

        assert!(result.success);
        // Empty CONSTRUCT adds no triples
        assert_eq!(result.triples_added, 0);
    }

    #[test]
    fn test_rule_ordering() {
        let mut pass = NormalizationPass::new();

        pass.add_rule(NormalizationRule {
            name: "third".to_string(),
            construct: "CONSTRUCT {} WHERE {}".to_string(),
            order: 3,
            description: None,
            when: None,
        });

        pass.add_rule(NormalizationRule {
            name: "first".to_string(),
            construct: "CONSTRUCT {} WHERE {}".to_string(),
            order: 1,
            description: None,
            when: None,
        });

        pass.add_rule(NormalizationRule {
            name: "second".to_string(),
            construct: "CONSTRUCT {} WHERE {}".to_string(),
            order: 2,
            description: None,
            when: None,
        });

        assert_eq!(pass.rules[0].name, "first");
        assert_eq!(pass.rules[1].name, "second");
        assert_eq!(pass.rules[2].name, "third");
    }
}
