use crate::error::ValidationError;
use crate::ir::{IRComponent, IRSchema, IRShape, ShapeLabelIdx};
use crate::validator::cache::{SharedValidationCache, ValidationCache};
use crate::validator::constraints::{BasicSparqlValidator, ShaclComponent, ValidatorDeref, object_as_sparql};
use crate::validator::engine::{Engine, select};
use crate::validator::nodes::{FocusNodes, ValueNodes};
use crate::validator::report::ValidationResult;
use indoc::formatdoc;
use rudof_iri::IriS;
use rudof_rdf::rdf_core::query::QueryRDF;
use rudof_rdf::rdf_core::term::{Object, Term};
use rudof_rdf::rdf_core::{NeighsRDF, SHACLPath};
use std::fmt::Debug;

pub struct SparqlEngine {
    cache: SharedValidationCache,
}

impl SparqlEngine {
    pub fn new() -> Self {
        Self {
            cache: SharedValidationCache::new(),
        }
    }
}

impl<S: QueryRDF + NeighsRDF + Debug + 'static> Engine<S> for SparqlEngine {
    fn evaluate(
        &mut self,
        store: &S,
        shape: &IRShape,
        component: &IRComponent,
        value_nodes: &ValueNodes<S>,
        source_shape: Option<&IRShape>,
        maybe_path: Option<&SHACLPath>,
        shapes_graph: &IRSchema,
    ) -> Result<Vec<ValidationResult>, ValidationError> {
        let shacl_component = ShaclComponent::new(component);
        let validator: &dyn BasicSparqlValidator<S> = shacl_component.deref();

        validator.validate_sparql(
            component,
            shape,
            store,
            self,
            value_nodes,
            source_shape,
            maybe_path,
            shapes_graph,
        )
    }

    // If s is a shape in a shapes graph SG and s has value t for sh:targetNode
    // in SG then { t } is a target from any data graph for s in SG.
    //
    // We resolve this directly instead of issuing a SPARQL `BIND` because the latter
    // round-trips the term through the backend's literal parser, which canonicalises
    // some lexical forms (e.g. `"4.0"^^xsd:decimal` → `"4"^^xsd:decimal`) and diverges
    // from the Native engine's behaviour.
    fn target_node(&self, _: &S, node: &Object) -> Result<FocusNodes<S>, ValidationError> {
        let node: S::Term = node.clone().into();
        if node.is_blank_node() {
            return Err(ValidationError::TargetNodeBNode);
        }
        Ok(FocusNodes::single(node))
    }

    fn target_class(&self, store: &S, class: &Object) -> Result<FocusNodes<S>, ValidationError> {
        let class_sparql = object_as_sparql(class).ok_or(ValidationError::TargetClassNotIri)?;

        let query = formatdoc! {"
            PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
            PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

            SELECT DISTINCT ?this
            WHERE {{
                ?this rdf:type/rdfs:subClassOf* {class_sparql} .
            }}
        "};

        let results = select(store, &query, "this")?;
        Ok(FocusNodes::new(results))
    }

    fn target_subject_of(&self, store: &S, predicate: &IriS) -> Result<FocusNodes<S>, ValidationError> {
        let query = formatdoc! {"
            SELECT DISTINCT ?this
            WHERE {{
                ?this <{}> ?any .
            }}
        ", predicate};

        let results = select(store, &query, "this")?;
        Ok(FocusNodes::new(results))
    }

    fn target_object_of(&self, store: &S, predicate: &IriS) -> Result<FocusNodes<S>, ValidationError> {
        let query = formatdoc! {"
            SELECT DISTINCT ?this
            WHERE {{
                ?any <{}> ?this.
            }}
        ", predicate};

        let results = select(store, &query, "this")?;
        Ok(FocusNodes::new(results))
    }

    fn implicit_target_class(&self, store: &S, shape: &Object) -> Result<FocusNodes<S>, ValidationError> {
        let shape_sparql = object_as_sparql(shape).ok_or(ValidationError::TargetClassNotIri)?;

        let query = formatdoc! {"
            PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
            PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

            SELECT DISTINCT ?this
            WHERE {{
                ?this rdf:type/rdfs:subClassOf* {shape_sparql} .
            }}
        "};

        let results = select(store, &query, "this")?;
        Ok(FocusNodes::new(results))
    }

    fn record_validation(&mut self, node: Object, shape_idx: ShapeLabelIdx, results: Vec<ValidationResult>) {
        self.cache.record(node, shape_idx, results)
    }

    fn has_validated(&self, node: &Object, shape_idx: ShapeLabelIdx) -> bool {
        self.cache.has_validated(node, shape_idx)
    }

    fn get_cached_results(&self, node: &Object, shape_idx: ShapeLabelIdx) -> Option<Vec<ValidationResult>> {
        self.cache.get_results(node, shape_idx)
    }

    fn fork(&self) -> Box<dyn Engine<S>> {
        Box::new(SparqlEngine {
            cache: self.cache.clone(),
        })
    }
}

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