use crate::graph::GraphStore;
use crate::state::StateDb;
use std::sync::Arc;

/// Design pattern enforcement with built-in and custom rule packs.
pub struct Enforcer {
    db: StateDb,
    graph: Arc<GraphStore>,
}

impl Enforcer {
    pub fn new(db: StateDb, graph: Arc<GraphStore>) -> Self {
        Self { db, graph }
    }

    /// Run enforcement for a rule pack. Built-in packs: "generic", "boro", "value_partition",
    /// "hierarchy", "ies4". Custom rules stored in SQLite are also checked for the given
    /// pack name.
    pub fn enforce(&self, rule_pack: &str) -> anyhow::Result<String> {
        self.enforce_with_feedback(rule_pack, None)
    }

    /// Run enforcement with optional feedback-based suppression.
    pub fn enforce_with_feedback(&self, rule_pack: &str, feedback_db: Option<&StateDb>) -> anyhow::Result<String> {
        let mut violations = Vec::new();
        let mut total_rules = 0u32;
        let mut passed_rules = 0u32;

        match rule_pack {
            "generic" => self.run_generic_rules(&mut violations, &mut total_rules, &mut passed_rules),
            "boro" => self.run_boro_rules(&mut violations, &mut total_rules, &mut passed_rules),
            "value_partition" => self.run_value_partition_rules(&mut violations, &mut total_rules, &mut passed_rules),
            "hierarchy" => self.run_hierarchy_rules(&mut violations, &mut total_rules, &mut passed_rules),
            "ies4" => self.run_ies4_rules(&mut violations, &mut total_rules, &mut passed_rules),
            _ => {}
        }

        // Also run custom rules for this pack
        self.run_custom_rules(rule_pack, &mut violations, &mut total_rules, &mut passed_rules);

        // Apply feedback adjustments
        let mut filtered_violations: Vec<serde_json::Value> = Vec::new();
        let mut suppressed_count: u64 = 0;

        for violation in violations {
            if let Some(db) = feedback_db {
                let rule = violation["rule"].as_str().unwrap_or("");
                let entity = violation["entity"].as_str().unwrap_or("");
                match crate::feedback::get_feedback_adjustment(db, "enforce", rule, entity) {
                    crate::feedback::FeedbackAction::Suppress => {
                        suppressed_count += 1;
                        continue;
                    }
                    crate::feedback::FeedbackAction::Downgrade => {
                        let original = violation["severity"].as_str().unwrap_or("info");
                        let downgraded = crate::feedback::downgrade_severity(original);
                        let mut adjusted = violation.clone();
                        adjusted["original_severity"] = serde_json::json!(original);
                        adjusted["adjusted_severity"] = serde_json::json!(downgraded);
                        adjusted["severity"] = serde_json::json!(downgraded);
                        filtered_violations.push(adjusted);
                    }
                    crate::feedback::FeedbackAction::Keep => {
                        filtered_violations.push(violation);
                    }
                }
            } else {
                filtered_violations.push(violation);
            }
        }

        let compliance = if total_rules > 0 {
            passed_rules as f64 / total_rules as f64
        } else {
            1.0
        };

        let result = serde_json::json!({
            "rule_pack": rule_pack,
            "violations": filtered_violations,
            "total_rules": total_rules,
            "passed_rules": passed_rules,
            "compliance": compliance,
            "suppressed_count": suppressed_count,
        });

        Ok(result.to_string())
    }

    /// Add a custom SPARQL rule to the database.
    pub fn add_custom_rule(&self, id: &str, rule_pack: &str, query: &str, severity: &str, message: &str) {
        let conn = self.db.conn();
        let _ = conn.execute(
            "INSERT OR REPLACE INTO enforce_rules (id, rule_pack, query, severity, message, enabled) \
             VALUES (?1, ?2, ?3, ?4, ?5, 1)",
            rusqlite::params![id, rule_pack, query, severity, message],
        );
    }

    fn run_generic_rules(&self, violations: &mut Vec<serde_json::Value>, total: &mut u32, passed: &mut u32) {
        // Rule: orphan classes (no subclass parent, not used as domain/range)
        self.check_orphan_classes(violations, total, passed);
        // Rule: missing domain
        self.check_missing_domain(violations, total, passed);
        // Rule: missing range
        self.check_missing_range(violations, total, passed);
        // Rule: missing label
        self.check_missing_label(violations, total, passed);
    }

    fn check_orphan_classes(&self, violations: &mut Vec<serde_json::Value>, total: &mut u32, passed: &mut u32) {
        *total += 1;
        let query = "SELECT DISTINCT ?c WHERE { \
            ?c a <http://www.w3.org/2002/07/owl#Class> . \
            FILTER NOT EXISTS { ?c <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?parent } \
            FILTER NOT EXISTS { ?child <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?c } \
            FILTER NOT EXISTS { ?p <http://www.w3.org/2000/01/rdf-schema#domain> ?c } \
            FILTER NOT EXISTS { ?p <http://www.w3.org/2000/01/rdf-schema#range> ?c } \
        }";

        let orphans = self.query_iris(query, "c");
        if orphans.is_empty() {
            *passed += 1;
        } else {
            for orphan in orphans {
                violations.push(serde_json::json!({
                    "rule": "orphan_class",
                    "severity": "warning",
                    "entity": orphan,
                    "message": "Class has no parent, children, or property references",
                }));
            }
        }
    }

    fn check_missing_domain(&self, violations: &mut Vec<serde_json::Value>, total: &mut u32, passed: &mut u32) {
        *total += 1;
        let query = "SELECT DISTINCT ?p WHERE { \
            { ?p a <http://www.w3.org/2002/07/owl#ObjectProperty> } UNION \
            { ?p a <http://www.w3.org/2002/07/owl#DatatypeProperty> } \
            FILTER NOT EXISTS { ?p <http://www.w3.org/2000/01/rdf-schema#domain> ?d } \
        }";

        let props = self.query_iris(query, "p");
        if props.is_empty() {
            *passed += 1;
        } else {
            for prop in props {
                violations.push(serde_json::json!({
                    "rule": "missing_domain",
                    "severity": "warning",
                    "entity": prop,
                    "message": "Property has no rdfs:domain",
                }));
            }
        }
    }

    fn check_missing_range(&self, violations: &mut Vec<serde_json::Value>, total: &mut u32, passed: &mut u32) {
        *total += 1;
        let query = "SELECT DISTINCT ?p WHERE { \
            { ?p a <http://www.w3.org/2002/07/owl#ObjectProperty> } UNION \
            { ?p a <http://www.w3.org/2002/07/owl#DatatypeProperty> } \
            FILTER NOT EXISTS { ?p <http://www.w3.org/2000/01/rdf-schema#range> ?r } \
        }";

        let props = self.query_iris(query, "p");
        if props.is_empty() {
            *passed += 1;
        } else {
            for prop in props {
                violations.push(serde_json::json!({
                    "rule": "missing_range",
                    "severity": "warning",
                    "entity": prop,
                    "message": "Property has no rdfs:range",
                }));
            }
        }
    }

    fn check_missing_label(&self, violations: &mut Vec<serde_json::Value>, total: &mut u32, passed: &mut u32) {
        *total += 1;
        let query = "SELECT DISTINCT ?c WHERE { \
            ?c a <http://www.w3.org/2002/07/owl#Class> . \
            FILTER NOT EXISTS { ?c <http://www.w3.org/2000/01/rdf-schema#label> ?l } \
        }";

        let classes = self.query_iris(query, "c");
        if classes.is_empty() {
            *passed += 1;
        } else {
            for cls in classes {
                violations.push(serde_json::json!({
                    "rule": "missing_label",
                    "severity": "info",
                    "entity": cls,
                    "message": "Class has no rdfs:label",
                }));
            }
        }
    }

    /// IES4 design-pattern enforcement (Information Exchange Standard, the UK
    /// cross-sector ontology framework custodied by DBT since March 2025;
    /// canonical home `IES-Org/ont-ies`). Three rules that go beyond the
    /// existing BORO pack:
    ///
    /// 1. **4D identity uniqueness** — a class cannot be both `ies:Particular`
    ///    and `ies:ClassOfEntity` (the type-vs-token distinction is
    ///    foundational to IES's 4D mereology).
    /// 2. **State has subject** — a class subclassing `ies:State` should
    ///    declare or appear with `ies:isStateOf` linking it to an entity
    ///    (the state pattern is meaningless without its bearer).
    /// 3. **Event has participant pattern** — a class subclassing `ies:Event`
    ///    should appear as range of some property implementing the
    ///    participant pattern (`ies:isParticipantIn`, `ies:involves`, etc.),
    ///    OR have at least one declared subclass-of-Event participant
    ///    (events without participants are incomplete IES4 model).
    ///
    /// Academic grounding: FOUST 7 paper "Comparing IES and BORO"
    /// (CEUR Vol-4176, JOWO 2024). Closes #24.
    fn run_ies4_rules(&self, violations: &mut Vec<serde_json::Value>, total: &mut u32, passed: &mut u32) {
        const IES_NS: &str = "http://ies.data.gov.uk/ontology/ies4#";

        // Rule 1: 4D identity uniqueness — class is BOTH Particular and ClassOfEntity.
        *total += 1;
        let q = format!(
            "SELECT DISTINCT ?c WHERE {{ \
                ?c <http://www.w3.org/2000/01/rdf-schema#subClassOf> <{ies}Particular> . \
                ?c <http://www.w3.org/2000/01/rdf-schema#subClassOf> <{ies}ClassOfEntity> . \
            }}",
            ies = IES_NS
        );
        let overlap = self.query_iris(&q, "c");
        if overlap.is_empty() {
            *passed += 1;
        } else {
            for cls in overlap {
                violations.push(serde_json::json!({
                    "rule": "ies4_particular_class_overlap",
                    "severity": "error",
                    "entity": cls,
                    "message": "IES4 4D principle violation: class is subclass of both ies:Particular and ies:ClassOfEntity (type-vs-token clash)",
                }));
            }
        }

        // Rule 2: State has subject — class subclasses ies:State but no
        // ies:isStateOf usage anywhere targets it as state-side.
        *total += 1;
        let q = format!(
            "SELECT DISTINCT ?state WHERE {{ \
                ?state <http://www.w3.org/2000/01/rdf-schema#subClassOf> <{ies}State> . \
                FILTER NOT EXISTS {{ \
                    {{ ?state <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?r . \
                       ?r <http://www.w3.org/2002/07/owl#onProperty> <{ies}isStateOf> . }} \
                    UNION \
                    {{ ?indiv a ?state . ?indiv <{ies}isStateOf> ?bearer . }} \
                }} \
            }}",
            ies = IES_NS
        );
        let states = self.query_iris(&q, "state");
        if states.is_empty() {
            *passed += 1;
        } else {
            for s in states {
                violations.push(serde_json::json!({
                    "rule": "ies4_state_without_subject",
                    "severity": "warning",
                    "entity": s,
                    "message": "IES4 State subclass has no ies:isStateOf restriction or instance-level usage — the state pattern requires a bearer (Entity it is a state of)",
                }));
            }
        }

        // Rule 3: Event has participant pattern — class subclasses ies:Event
        // but no restriction or instance-level usage links a participant via
        // ies:isParticipantIn / ies:involvesParticipant.
        *total += 1;
        let q = format!(
            "SELECT DISTINCT ?ev WHERE {{ \
                ?ev <http://www.w3.org/2000/01/rdf-schema#subClassOf> <{ies}Event> . \
                FILTER NOT EXISTS {{ \
                    {{ ?ev <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?r . \
                       ?r <http://www.w3.org/2002/07/owl#onProperty> ?prop . \
                       FILTER(?prop IN (<{ies}isParticipantIn>, <{ies}involvesParticipant>, <{ies}hasParticipant>)) }} \
                    UNION \
                    {{ ?indiv a ?ev . \
                       {{ ?indiv <{ies}isParticipantIn> ?_ }} UNION \
                       {{ ?_ <{ies}involvesParticipant> ?indiv }} UNION \
                       {{ ?_ <{ies}hasParticipant> ?indiv }} }} \
                }} \
            }}",
            ies = IES_NS
        );
        let events = self.query_iris(&q, "ev");
        if events.is_empty() {
            *passed += 1;
        } else {
            for e in events {
                violations.push(serde_json::json!({
                    "rule": "ies4_event_without_participant",
                    "severity": "warning",
                    "entity": e,
                    "message": "IES4 Event subclass has no participant restriction or instance — events require participants to be meaningful in the 4D model",
                }));
            }
        }
    }

    fn run_boro_rules(&self, violations: &mut Vec<serde_json::Value>, total: &mut u32, passed: &mut u32) {
        // BORO rule: entities that subclass ies:Entity must have a corresponding State class
        *total += 1;
        let query = "SELECT DISTINCT ?entity WHERE { \
            ?entity <http://www.w3.org/2000/01/rdf-schema#subClassOf> <http://ies.data.gov.uk/ontology/ies4#Entity> . \
            FILTER NOT EXISTS { \
                ?state <http://www.w3.org/2000/01/rdf-schema#subClassOf> <http://ies.data.gov.uk/ontology/ies4#State> . \
                ?state <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?entity . \
            } \
        }";

        let entities = self.query_iris(query, "entity");
        if entities.is_empty() {
            *passed += 1;
        } else {
            for entity in entities {
                violations.push(serde_json::json!({
                    "rule": "missing_state_class",
                    "severity": "error",
                    "entity": entity,
                    "message": "BORO: Entity subclass has no corresponding State class",
                }));
            }
        }
    }

    fn run_value_partition_rules(&self, violations: &mut Vec<serde_json::Value>, total: &mut u32, passed: &mut u32) {
        // Find classes that have multiple subclasses (partition candidates)
        let parent_query = "SELECT DISTINCT ?parent WHERE { \
            ?child1 <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?parent . \
            ?child2 <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?parent . \
            FILTER(?child1 != ?child2) \
            FILTER NOT EXISTS { ?parent <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?grandparent } \
        }";

        let parents = self.query_iris(parent_query, "parent");

        for parent in &parents {
            // Get children of this parent
            let children_query = format!(
                "SELECT DISTINCT ?child WHERE {{ ?child <http://www.w3.org/2000/01/rdf-schema#subClassOf> <{}> }}",
                parent
            );
            let children = self.query_iris(&children_query, "child");

            if children.len() < 2 {
                continue;
            }

            // Check if children are pairwise disjoint
            *total += 1;
            let mut all_disjoint = true;
            for i in 0..children.len() {
                for j in (i + 1)..children.len() {
                    let disjoint_query = format!(
                        "ASK {{ <{}> <http://www.w3.org/2002/07/owl#disjointWith> <{}> }}",
                        children[i], children[j]
                    );
                    if let Ok(result) = self.graph.sparql_select(&disjoint_query)
                        && !result.contains("true") {
                            all_disjoint = false;
                            break;
                        }
                }
                if !all_disjoint {
                    break;
                }
            }

            if all_disjoint {
                *passed += 1;
            } else {
                violations.push(serde_json::json!({
                    "rule": "partition_not_disjoint",
                    "severity": "warning",
                    "entity": parent,
                    "message": format!("Value partition: children of {} are not pairwise disjoint", parent),
                }));
            }
        }
    }

    fn run_hierarchy_rules(&self, violations: &mut Vec<serde_json::Value>, total: &mut u32, passed: &mut u32) {
        // Rule 1: Flat hierarchy — classes with too many direct children (>5)
        // These are candidates for intermediate grouping classes.
        *total += 1;
        let flat_query = "SELECT ?parent (COUNT(DISTINCT ?child) AS ?count) WHERE { \
            ?child <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?parent . \
            ?child a ?type . \
            FILTER(?type IN (<http://www.w3.org/2002/07/owl#Class>, <http://www.w3.org/2000/01/rdf-schema#Class>)) \
        } GROUP BY ?parent HAVING (COUNT(DISTINCT ?child) > 5) ORDER BY DESC(?count)";

        let flat_parents = self.query_flat_hierarchy(flat_query);
        if flat_parents.is_empty() {
            *passed += 1;
        } else {
            for (parent, count, children) in &flat_parents {
                violations.push(serde_json::json!({
                    "rule": "flat_hierarchy",
                    "severity": "info",
                    "entity": parent,
                    "message": format!(
                        "Flat hierarchy: {} has {} direct children. Consider adding intermediate grouping classes to deepen the subClassOf chain and improve RDFS inference. Children: {}",
                        parent, count, children.join(", ")
                    ),
                    "children_count": count,
                    "children": children,
                }));
            }
        }

        // Rule 2: Shallow max depth — hierarchy should be at least 3 levels deep
        *total += 1;
        let depth_query = "SELECT (MAX(?depth) AS ?max_depth) WHERE { \
            SELECT ?class (COUNT(?ancestor) AS ?depth) WHERE { \
                ?class <http://www.w3.org/2000/01/rdf-schema#subClassOf>+ ?ancestor . \
            } GROUP BY ?class \
        }";

        if let Ok(json) = self.graph.sparql_select(depth_query)
            && let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&json)
            && let Some(results) = parsed["results"].as_array()
            && let Some(first) = results.first()
            && let Some(depth_str) = first["max_depth"].as_str()
        {
            let depth: f64 = depth_str
                .split('"')
                .nth(1)
                .unwrap_or(depth_str)
                .parse()
                .unwrap_or(0.0);
            if depth >= 3.0 {
                *passed += 1;
            } else {
                violations.push(serde_json::json!({
                    "rule": "shallow_hierarchy",
                    "severity": "warning",
                    "entity": "graph",
                    "message": format!(
                        "Shallow hierarchy: max depth is {:.0}. Ontologies with deeper hierarchies produce richer RDFS inference. Consider adding intermediate grouping classes.",
                        depth
                    ),
                    "max_depth": depth,
                }));
            }
        } else {
            *passed += 1; // Can't determine depth — don't penalize
        }

        // Rule 3: Low average depth — average should be above 2.0
        *total += 1;
        let avg_query = "SELECT (AVG(?depth) AS ?avg_depth) WHERE { \
            SELECT ?class (COUNT(?ancestor) AS ?depth) WHERE { \
                ?class <http://www.w3.org/2000/01/rdf-schema#subClassOf>+ ?ancestor . \
            } GROUP BY ?class \
        }";

        if let Ok(json) = self.graph.sparql_select(avg_query)
            && let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&json)
            && let Some(results) = parsed["results"].as_array()
            && let Some(first) = results.first()
            && let Some(avg_str) = first["avg_depth"].as_str()
        {
            let avg: f64 = avg_str
                .split('"')
                .nth(1)
                .unwrap_or(avg_str)
                .parse()
                .unwrap_or(0.0);
            if avg >= 2.0 {
                *passed += 1;
            } else {
                violations.push(serde_json::json!({
                    "rule": "low_avg_depth",
                    "severity": "info",
                    "entity": "graph",
                    "message": format!(
                        "Low average hierarchy depth: {:.2}. An average above 2.0 indicates well-structured intermediate groupings that produce richer RDFS inference.",
                        avg
                    ),
                    "avg_depth": avg,
                }));
            }
        } else {
            *passed += 1;
        }

        // Rule 4: RDFS inference potential — estimate inferred triples vs raw
        *total += 1;
        let raw_query = "SELECT (COUNT(*) AS ?count) WHERE { ?s ?p ?o }";
        let subclass_query = "SELECT (COUNT(*) AS ?count) WHERE { \
            ?s <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?o \
        }";

        let raw_count = self.query_count(raw_query);
        let subclass_count = self.query_count(subclass_query);

        if raw_count > 0 {
            let ratio = subclass_count as f64 / raw_count as f64;
            if ratio >= 0.05 {
                // At least 5% of triples are subClassOf — good hierarchy density
                *passed += 1;
            } else {
                violations.push(serde_json::json!({
                    "rule": "low_hierarchy_density",
                    "severity": "info",
                    "entity": "graph",
                    "message": format!(
                        "Low hierarchy density: only {:.1}% of triples are rdfs:subClassOf ({} of {}). Dense hierarchies drive richer RDFS inference.",
                        ratio * 100.0, subclass_count, raw_count
                    ),
                    "subclass_count": subclass_count,
                    "total_triples": raw_count,
                    "density_pct": ratio * 100.0,
                }));
            }
        } else {
            *passed += 1;
        }
    }

    fn query_flat_hierarchy(&self, query: &str) -> Vec<(String, u64, Vec<String>)> {
        let mut results = Vec::new();
        if let Ok(json) = self.graph.sparql_select(query)
            && let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&json)
            && let Some(rows) = parsed["results"].as_array()
        {
            for row in rows {
                let parent = row["parent"]
                    .as_str()
                    .unwrap_or("")
                    .trim_matches(|c| c == '<' || c == '>')
                    .to_string();
                let count: u64 = row["count"]
                    .as_str()
                    .unwrap_or("0")
                    .split('"')
                    .nth(1)
                    .unwrap_or("0")
                    .parse()
                    .unwrap_or(0);

                // Get the children names
                let children_query = format!(
                    "SELECT ?child WHERE {{ ?child <http://www.w3.org/2000/01/rdf-schema#subClassOf> <{}> }}",
                    parent
                );
                let children = self.query_iris(&children_query, "child");
                let short_children: Vec<String> = children
                    .iter()
                    .map(|c| c.rsplit_once('#').or(c.rsplit_once('/')).map(|(_, n)| n.to_string()).unwrap_or(c.clone()))
                    .collect();

                results.push((parent, count, short_children));
            }
        }
        results
    }

    fn query_count(&self, query: &str) -> u64 {
        if let Ok(json) = self.graph.sparql_select(query)
            && let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&json)
            && let Some(results) = parsed["results"].as_array()
            && let Some(first) = results.first()
            && let Some(count_str) = first["count"].as_str()
        {
            count_str
                .split('"')
                .nth(1)
                .unwrap_or(count_str)
                .parse()
                .unwrap_or(0)
        } else {
            0
        }
    }

    fn run_custom_rules(&self, rule_pack: &str, violations: &mut Vec<serde_json::Value>, total: &mut u32, passed: &mut u32) {
        let conn = self.db.conn();
        let mut stmt = conn
            .prepare("SELECT id, query, severity, message FROM enforce_rules WHERE rule_pack = ?1 AND enabled = 1")
            .unwrap();

        let rules: Vec<(String, String, String, String)> = stmt
            .query_map(rusqlite::params![rule_pack], |row| {
                Ok((
                    row.get::<_, String>(0)?,
                    row.get::<_, String>(1)?,
                    row.get::<_, String>(2)?,
                    row.get::<_, Option<String>>(3)?.unwrap_or_default(),
                ))
            })
            .unwrap()
            .filter_map(|r| r.ok())
            .collect();

        for (id, query, severity, message) in rules {
            *total += 1;
            if let Ok(result) = self.graph.sparql_select(&query) {
                // ASK query: true = violation
                if result.contains("\"result\":true") || result.contains("\"result\": true") {
                    violations.push(serde_json::json!({
                        "rule": id,
                        "severity": severity,
                        "entity": "graph",
                        "message": message,
                    }));
                } else {
                    *passed += 1;
                }
            }
        }
    }

    fn query_iris(&self, query: &str, var: &str) -> Vec<String> {
        if let Ok(json) = self.graph.sparql_select(query)
            && let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&json)
                && let Some(results) = parsed["results"].as_array() {
                    return results
                        .iter()
                        .filter_map(|r| {
                            r[var].as_str().map(|s| s.trim_matches(|c| c == '<' || c == '>').to_string())
                        })
                        .collect();
                }
        Vec::new()
    }
}
