use crate::constraints::NativeValidator;
use crate::constraints::constraint_error::ConstraintError;
use crate::shacl_engine::Engine;
use crate::validation_report::result::ValidationResult;
use crate::value_nodes::ValueNodes;
use rudof_rdf::rdf_core::{
    NeighsRDF, Rdf, SHACLPath,
    term::{Object, Triple},
};
use shacl_ir::compiled::component_ir::ComponentIR;
use shacl_ir::compiled::shape::ShapeIR;
use shacl_ir::components::LessThan;
use shacl_ir::schema_ir::SchemaIR;
use std::fmt::Debug;

#[cfg(feature = "sparql")]
use {crate::constraints::SparqlValidator, rudof_rdf::rdf_core::query::QueryRDF};

impl<R: NeighsRDF + Debug + 'static> NativeValidator<R> for LessThan {
    fn validate_native(
        &self,
        component: &ComponentIR,
        shape: &ShapeIR,
        store: &R,
        _engine: &mut dyn Engine<R>,
        value_nodes: &ValueNodes<R>,
        _source_shape: Option<&ShapeIR>,
        maybe_path: Option<SHACLPath>,
        _shapes_graph: &SchemaIR,
    ) -> Result<Vec<ValidationResult>, ConstraintError> {
        let mut validation_results = Vec::new();
        let component = Object::iri(component.into());

        for (focus_node, nodes) in value_nodes.iter() {
            let subject: R::Subject = <R as Rdf>::term_as_subject(focus_node).unwrap();
            let iri_owned: R::IRI = self.iri().clone().into();
            match store.triples_with_subject_predicate(&subject, &iri_owned) {
                Ok(triples_iter) => {
                    // Collect nodes to compare
                    for triple in triples_iter {
                        let value = triple.obj();
                        let node1 = <R as Rdf>::term_as_object(value).unwrap();
                        for value2 in nodes.iter() {
                            let node2 = <R as Rdf>::term_as_object(value2).unwrap();
                            let message = match node2.partial_cmp(&node1) {
                                None => Some(format!(
                                    "LessThan constraint violated: {node1} is not comparable to {node2}"
                                )),
                                Some(ord) if ord.is_ge() => Some(format!(
                                    "LessThan constraint violated: {node1} is not less than {node2}"
                                )),
                                _ => None,
                            };
                            if let Some(msg) = message {
                                let validation_result =
                                    ValidationResult::new(shape.id().clone(), component.clone(), shape.severity())
                                        .with_message(msg.as_str())
                                        .with_path(maybe_path.clone());
                                validation_results.push(validation_result);
                            }
                        }
                    }
                },
                Err(e) => {
                    let message = format!(
                        "LessThan: Error trying to find triples for subject {} and predicate {}: {e}",
                        subject,
                        self.iri()
                    );
                    let validation_result =
                        ValidationResult::new(shape.id().clone(), component.clone(), shape.severity())
                            .with_message(message.as_str())
                            .with_path(maybe_path.clone());
                    validation_results.push(validation_result);
                },
            };
        }
        Ok(validation_results)
    }
}

#[cfg(feature = "sparql")]
impl<R: QueryRDF + Debug + 'static> SparqlValidator<R> for LessThan {
    fn validate_sparql(
        &self,
        _component: &ComponentIR,
        _shape: &ShapeIR,
        _store: &R,
        _value_nodes: &ValueNodes<R>,
        _source_shape: Option<&ShapeIR>,
        _maybe_path: Option<SHACLPath>,
        _shapes_graph: &SchemaIR,
    ) -> Result<Vec<ValidationResult>, ConstraintError> {
        Err(ConstraintError::NotImplemented("LessThan".to_string()))
    }
}
