use crate::constraints::NativeValidator;
use crate::constraints::Validator;
use crate::constraints::constraint_error::ConstraintError;
use crate::constraints::get_shape_from_idx;
use crate::focus_nodes::FocusNodes;
use crate::shacl_engine::Engine;
use crate::shape_validation::Validate;
use crate::validation_report::result::ValidationResult;
use crate::value_nodes::ValueNodes;
use rudof_rdf::rdf_core::{NeighsRDF, SHACLPath, query::QueryRDF, term::Object};
use shacl_ir::compiled::component_ir::ComponentIR;
use shacl_ir::compiled::shape::ShapeIR;
use shacl_ir::components::Node;
use shacl_ir::schema_ir::SchemaIR;
use std::fmt::Debug;
use tracing::trace;

#[cfg(feature = "sparql")]
use {crate::constraints::SparqlValidator, crate::shacl_engine::sparql::SparqlEngine};

impl<S: NeighsRDF + Debug> Validator<S> for Node {
    fn validate(
        &self,
        component: &ComponentIR,
        shape: &ShapeIR,
        store: &S,
        engine: &mut dyn Engine<S>,
        value_nodes: &ValueNodes<S>,
        _source_shape: Option<&ShapeIR>,
        maybe_path: Option<SHACLPath>,
        shapes_graph: &SchemaIR,
    ) -> Result<Vec<ValidationResult>, ConstraintError> {
        let mut validation_results = Vec::new();
        let shape_idx = self.shape();
        let node_shape = get_shape_from_idx(shapes_graph, shape_idx)?;
        for (focus_node, nodes) in value_nodes.iter() {
            trace!(
                "Validating Node constraint for shape {} and node: {focus_node}",
                shape.id()
            );
            for node in nodes.iter() {
                let node_object = S::term_as_object(node)?;
                let focus_nodes = FocusNodes::single(node.clone());
                if engine.has_validated(&node_object, *shape_idx) {
                    trace!(
                        "Skipping validation for Node constraint for shape {} and node: {focus_node} since already validated",
                        shape.id()
                    );
                    continue;
                }
                let inner_results = node_shape.validate(store, engine, Some(&focus_nodes), Some(shape), shapes_graph);
                let is_valid = match inner_results {
                    Err(_) => false,
                    Ok(results) => results.is_empty(),
                };
                if !is_valid {
                    let message = format!(
                        "Shape {}: Node({node_shape}) constraint not satisfied for {node}",
                        shape.id(),
                    );
                    let component = Object::iri(component.into());
                    let result = ValidationResult::new(node_object.clone(), component.clone(), shape.severity())
                        .with_message(message.as_str())
                        .with_path(maybe_path.clone());
                    validation_results.push(result.clone());
                    engine.record_validation(node_object, *shape_idx, vec![result]);
                } else {
                    engine.record_validation(node_object, *shape_idx, Vec::new());
                }
            }
        }
        Ok(validation_results)
    }
}

impl<S: NeighsRDF + Debug + 'static> NativeValidator<S> for Node {
    fn validate_native(
        &self,
        component: &ComponentIR,
        shape: &ShapeIR,
        store: &S,
        engine: &mut dyn Engine<S>,
        value_nodes: &ValueNodes<S>,
        source_shape: Option<&ShapeIR>,
        maybe_path: Option<SHACLPath>,
        shapes_graph: &SchemaIR,
    ) -> Result<Vec<ValidationResult>, ConstraintError> {
        self.validate(
            component,
            shape,
            store,
            engine,
            value_nodes,
            source_shape,
            maybe_path,
            shapes_graph,
        )
    }
}

#[cfg(feature = "sparql")]
impl<S: QueryRDF + NeighsRDF + Debug + 'static> SparqlValidator<S> for Node {
    fn validate_sparql(
        &self,
        component: &ComponentIR,
        shape: &ShapeIR,
        store: &S,
        value_nodes: &ValueNodes<S>,
        source_shape: Option<&ShapeIR>,
        maybe_path: Option<SHACLPath>,
        shapes_graph: &SchemaIR,
    ) -> Result<Vec<ValidationResult>, ConstraintError> {
        self.validate(
            component,
            shape,
            store,
            &mut SparqlEngine::new(),
            value_nodes,
            source_shape,
            maybe_path,
            shapes_graph,
        )
    }
}
