use std::{borrow::Cow, cmp::Ordering, fmt::Display, sync::Arc};

use bevy_ecs::prelude::*;
use iri_s::IriS;
use prefixmap::{error::PrefixMapError, PrefixMap};
use rudof_rdf::rdf_core::{
    term::{
        self as r_term, literal::Literal, BlankNode, Iri, IriOrBlankNode, Term as Rudof_Term,
        TermKind,
    },
    FocusRDF, Matcher, NeighsRDF, RDFError, SHACLPath,
};
use shacl_ir::compiled::schema_ir::SchemaIR as ShaclSchemaIR;
use sophia_api::term::Term;

use crate::prelude::*;

#[derive(Component)]
pub struct ShaclShapes {
    shacl: ShaclSchemaIR,
}
impl ShaclShapes {
    pub fn new(shacl: ShaclSchemaIR) -> Self {
        Self { shacl }
    }

    pub fn ir(&self) -> &ShaclSchemaIR {
        &self.shacl
    }
}
unsafe impl Send for ShaclShapes {}

#[derive(Clone, Debug)]
pub struct Rdf {
    focus: Option<MyTerm<'static>>,
    triples: Arc<Vec<MyQuad<'static>>>,
}
impl Rdf {
    pub fn new(triples: &Triples) -> Self {
        Self {
            focus: None,
            triples: triples.0.clone(),
        }
    }
}

impl FocusRDF for Rdf {
    fn set_focus(&mut self, focus: &Self::Term) {
        self.focus = Some(focus.clone());
    }

    fn get_focus(&self) -> Option<&Self::Term> {
        self.focus.as_ref()
    }
}

impl NeighsRDF for Rdf {
    fn triples(&self) -> std::result::Result<impl Iterator<Item = Self::Triple>, Self::Err> {
        Ok(self.triples.iter().cloned())
    }

    fn triples_matching<S, P, O>(
        &self,
        subject: &S,
        predicate: &P,
        object: &O,
    ) -> Result<impl Iterator<Item = Self::Triple> + '_, Self::Err>
    where
        S: Matcher<Self::Subject>,
        P: Matcher<Self::IRI>,
        O: Matcher<Self::Term>,
    {
        let subject: Option<MyTerm<'static>> = subject.value().cloned();
        let predicate: Option<MyTerm<'static>> = predicate.value().cloned();
        let object: Option<MyTerm<'static>> = object.value().cloned();
        let triples = self.triples()?.filter_map(move |triple| {
            let subject_matches = subject
                .as_ref()
                .map(|s| s == &triple.subject)
                .unwrap_or(true);

            let predicate_matches = predicate
                .as_ref()
                .map(|s| s == &triple.predicate)
                .unwrap_or(true);

            let object_matches = object.as_ref().map(|s| s == &triple.object).unwrap_or(true);

            if subject_matches && predicate_matches && object_matches {
                Some(triple)
            } else {
                None
            }
        });
        Ok(triples)
    }
}

impl<'b> TryInto<oxrdf::Term> for MyTerm<'static> {
    type Error = ();

    fn try_into(self) -> std::result::Result<oxrdf::Term, Self::Error> {
        use oxigraph::{model as M, model::Term as T};
        let output = match &self.ty {
            Some(sophia_api::prelude::TermKind::Iri) => {
                T::NamedNode(M::NamedNode::new(self.as_str()).map_err(|_| ())?)
            }
            Some(sophia_api::prelude::TermKind::Literal) => {
                if let Some(dt) = sophia_api::prelude::Term::datatype(&self) {
                    let dt = M::NamedNode::new(dt.as_str()).map_err(|_| ())?;
                    T::Literal(M::Literal::new_typed_literal(self.value.as_ref(), dt))
                } else if let Some(lang) = self.language_tag() {
                    T::Literal(
                        M::Literal::new_language_tagged_literal(self.value.as_ref(), lang.as_str())
                            .map_err(|_| ())?,
                    )
                } else {
                    T::Literal(M::Literal::new_simple_literal(self.value.as_ref()))
                }
            }
            Some(sophia_api::prelude::TermKind::BlankNode) => {
                T::BlankNode(M::BlankNode::new(self.value.as_ref()).map_err(|_| ())?)
            }
            _ => {
                return Err(());
            }
        };
        return Ok(output);
    }
}

impl TryFrom<MyTerm<'static>> for IriOrBlankNode {
    type Error = ();

    fn try_from(term: MyTerm<'static>) -> std::result::Result<Self, Self::Error> {
        let MyTerm { ty, value, .. } = term;
        if let Some(t) = ty {
            match t {
                sophia_api::term::TermKind::Iri => {
                    return Ok(IriOrBlankNode::Iri(IriS::new_unchecked(value.as_ref())))
                }
                sophia_api::term::TermKind::BlankNode => {
                    return Ok(IriOrBlankNode::BlankNode(value.to_string()))
                }
                _ => {}
            }
        }
        Err(())
    }
}

impl From<IriS> for MyTerm<'static> {
    fn from(value: IriS) -> Self {
        MyTerm::named_node(value.as_str(), 0..0).to_owned()
    }
}

impl From<IriOrBlankNode> for MyTerm<'static> {
    fn from(value: IriOrBlankNode) -> Self {
        match value {
            IriOrBlankNode::BlankNode(id) => MyTerm::blank_node(id, 0..0),
            IriOrBlankNode::Iri(iri_s) => MyTerm::named_node(iri_s.as_str(), 0..0).to_owned(),
        }
    }
}

impl From<MyTerm<'static>> for IriS {
    fn from(value: MyTerm<'static>) -> Self {
        IriS::new_unchecked(value.as_str())
    }
}

impl Iri for MyTerm<'static> {
    fn as_str(&self) -> &str {
        self.as_str()
    }
}

impl From<MyQuad<'static>> for MyTerm<'static> {
    fn from(value: MyQuad<'static>) -> Self {
        tracing::error!(
            "From<MyQuad<'static>> for MyTerm<'static> is not supported, using subject"
        );
        value.subject
    }
}

impl Rudof_Term for MyTerm<'static> {
    fn kind(&self) -> TermKind {
        if let Some(ty) = self.ty {
            map(ty)
        } else {
            TermKind::BlankNode
        }
    }

    fn lexical_form(&self) -> String {
        self.value.to_string()
    }
}

impl BlankNode for MyTerm<'static> {
    fn new(id: impl Into<String>) -> Self {
        MyTerm::blank_node(id.into(), 0..0)
    }

    fn id(&self) -> &str {
        &self.value
    }
}

impl Literal for MyTerm<'static> {
    fn lexical_form(&self) -> &str {
        self.value.as_ref()
    }

    fn lang(&self) -> Option<r_term::literal::Lang> {
        match &self.context {
            TermContext::LangTag(cow) => return r_term::literal::Lang::new(cow.as_ref()).ok(),
            _ => {}
        }
        None
    }

    fn datatype(&self) -> prefixmap::IriRef {
        let str = match &self.context {
            TermContext::DataType(cow) => cow.as_ref(),
            _ => "http://www.w3.org/2001/XMLSchema#string",
        };

        prefixmap::IriRef::Iri(IriS::new_unchecked(str))
    }

    fn to_concrete_literal(&self) -> Option<r_term::literal::ConcreteLiteral> {
        if self.ty == Some(sophia_api::term::TermKind::Literal) {
            Some(self.clone().into())
        } else {
            None
        }
    }
}

impl From<r_term::Object> for MyTerm<'static> {
    fn from(value: r_term::Object) -> Self {
        use r_term::Object;
        match value {
            Object::Iri(iri_s) => MyTerm::named_node(iri_s.as_str(), 0..0).to_owned(),
            Object::BlankNode(b) => MyTerm::blank_node(b, 0..0),
            Object::Literal(sliteral) => sliteral.into(),
            Object::Triple { subject, .. } => (*subject).into(),
        }
    }
}

fn dt_to_slit(value: &str, dt: &str) -> r_term::literal::ConcreteLiteral {
    use r_term::literal::ConcreteLiteral as SLiteral;
    if "http://www.w3.org/2001/XMLSchema#boolean" == dt {
        if let Ok(b) = SLiteral::parse_bool(value) {
            return SLiteral::BooleanLiteral(b);
        }
    }

    if "http://www.w3.org/2001/XMLSchema#integer" == dt {
        if let Ok(b) = SLiteral::parse_integer(value) {
            return SLiteral::NumericLiteral(r_term::literal::NumericLiteral::Integer(b));
        }
    }

    if "http://www.w3.org/2001/XMLSchema#double" == dt {
        if let Ok(b) = SLiteral::parse_double(value) {
            return SLiteral::NumericLiteral(r_term::literal::NumericLiteral::Double(b));
        }
    }

    if "http://www.w3.org/2001/XMLSchema#decimal" == dt {
        if let Ok(b) = SLiteral::parse_decimal(value) {
            return SLiteral::NumericLiteral(r_term::literal::NumericLiteral::Decimal(b));
        }
    }

    SLiteral::DatatypeLiteral {
        lexical_form: value.to_string(),
        datatype: prefixmap::IriRef::Iri(IriS::new_unchecked(&dt)),
    }
}

impl From<MyTerm<'static>> for r_term::Object {
    fn from(term: MyTerm<'static>) -> Self {
        let MyTerm { ty, value, .. } = &term;
        match ty.unwrap_or(sophia_api::prelude::TermKind::BlankNode) {
            sophia_api::term::TermKind::Iri => {
                r_term::Object::Iri(IriS::new_unchecked(value.as_ref()))
            }
            sophia_api::term::TermKind::BlankNode => r_term::Object::BlankNode(value.to_string()),
            sophia_api::term::TermKind::Literal => r_term::Object::Literal(term.into()),
            _ => {
                tracing::error!(
                    "Unsupported into srdf::Object for MyTerm with type {:?}",
                    ty
                );

                r_term::Object::Iri(IriS::default())
            }
        }
    }
}

impl From<r_term::literal::ConcreteLiteral> for MyTerm<'static> {
    fn from(value: r_term::literal::ConcreteLiteral) -> Self {
        let context = match value.lang() {
            Some(dt) => TermContext::LangTag(Cow::Owned(dt.to_string())),
            None => TermContext::DataType(Cow::Owned(value.datatype().to_string())),
        };
        MyTerm::literal(value.lexical_form(), 0..0, context)
    }
}

impl From<bool> for MyTerm<'static> {
    fn from(value: bool) -> Self {
        MyTerm::literal(
            value.to_string(),
            0..0,
            TermContext::DataType(Cow::Borrowed("http://www.w3.org/2001/XMLSchema#boolean")),
        )
    }
}

impl From<String> for MyTerm<'static> {
    fn from(value: String) -> Self {
        MyTerm::literal(
            value,
            0..0,
            TermContext::DataType(Cow::Borrowed("http://www.w3.org/2001/XMLSchema#string")),
        )
    }
}

impl From<f64> for MyTerm<'static> {
    fn from(value: f64) -> Self {
        MyTerm::literal(
            value.to_string(),
            0..0,
            TermContext::DataType(Cow::Borrowed("http://www.w3.org/2001/XMLSchema#double")),
        )
    }
}

impl From<i128> for MyTerm<'static> {
    fn from(value: i128) -> Self {
        MyTerm::literal(
            value.to_string(),
            0..0,
            TermContext::DataType(Cow::Borrowed("http://www.w3.org/2001/XMLSchema#integer")),
        )
    }
}

impl From<MyTerm<'static>> for r_term::literal::ConcreteLiteral {
    fn from(t: MyTerm<'static>) -> Self {
        let value = t.value;
        let context = t.context;
        match context {
            TermContext::None => r_term::literal::ConcreteLiteral::StringLiteral {
                lexical_form: value.to_string(),
                lang: None,
            },
            TermContext::DataType(dt) => dt_to_slit(value.as_ref(), dt.as_ref()),
            TermContext::LangTag(cow) => r_term::literal::ConcreteLiteral::StringLiteral {
                lexical_form: value.to_string(),
                lang: r_term::literal::Lang::new(cow).ok(),
            },
        }
    }
}
impl r_term::Triple<MyTerm<'static>, MyTerm<'static>, MyTerm<'static>> for MyQuad<'static> {
    fn new(
        subject: impl Into<MyTerm<'static>>,
        predicate: impl Into<MyTerm<'static>>,
        object: impl Into<MyTerm<'static>>,
    ) -> Self {
        MyQuad {
            subject: subject.into(),
            predicate: predicate.into(),
            object: object.into(),
            span: 0..0,
        }
    }

    fn subj(&self) -> &MyTerm<'static> {
        &self.subject
    }

    fn pred(&self) -> &MyTerm<'static> {
        &self.predicate
    }

    fn obj(&self) -> &MyTerm<'static> {
        &self.object
    }

    fn into_components(self) -> (MyTerm<'static>, MyTerm<'static>, MyTerm<'static>) {
        let MyQuad {
            subject,
            predicate,
            object,
            ..
        } = self;
        (subject, predicate, object)
    }
}

// impl From<IriOrBlankNode> for MyTerm<'static> {
//     fn from(value: IriOrBlankNode) -> Self {
//         todo!()
//     }
// }

impl rudof_rdf::rdf_core::Rdf for Rdf {
    type Subject = MyTerm<'static>;

    type IRI = MyTerm<'static>;

    type Term = MyTerm<'static>;

    type BNode = MyTerm<'static>;

    type Literal = MyTerm<'static>;

    type Triple = MyQuad<'static>;

    type Err = &'static str;

    fn qualify_iri(&self, iri: &Self::IRI) -> String {
        iri.value.to_string()
    }

    fn qualify_subject(&self, subj: &Self::Subject) -> String {
        subj.value.to_string()
    }

    fn qualify_term(&self, term: &Self::Term) -> String {
        term.value.to_string()
    }

    fn prefixmap(&self) -> Option<PrefixMap> {
        None
    }

    fn resolve_prefix_local(
        &self,
        prefix: &str,
        _local: &str,
    ) -> std::result::Result<IriS, PrefixMapError> {
        Err(PrefixMapError::PrefixNotFound {
            prefix: prefix.to_string(),
            prefixmap: PrefixMap::new(),
        })
    }

    fn term_as_literal(term: &Self::Term) -> Result<Self::Literal, RDFError> {
        if term.ty == Some(sophia_api::term::TermKind::Literal) {
            <Self::Term as TryInto<Self::Literal>>::try_into(term.clone()).map_err(|_| {
                RDFError::TermAsLiteral {
                    term: term.to_string(),
                }
            })
        } else {
            Err(RDFError::TermAsLiteral {
                term: term.to_string(),
            })
        }
    }

    fn term_as_sliteral(term: &Self::Term) -> Result<r_term::literal::ConcreteLiteral, RDFError> {
        let lit = Self::term_as_literal(term)?;
        let slit =
            <Self::Literal as TryInto<r_term::literal::ConcreteLiteral>>::try_into(lit.clone())
                .map_err(|_| RDFError::LiteralAsSLiteral {
                    literal: lit.to_string(),
                })?;
        Ok(slit)
    }

    fn term_as_subject(term: &Self::Term) -> Result<Self::Subject, RDFError> {
        if !(term.ty == Some(sophia_api::term::TermKind::BlankNode)
            || term.ty == Some(sophia_api::term::TermKind::Iri))
        {
            return Err(RDFError::TermAsSubject {
                term: term.to_string(),
            });
        }
        <Self::Term as TryInto<Self::Subject>>::try_into(term.clone()).map_err(|_e| {
            RDFError::TermAsSubject {
                term: term.to_string(),
            }
        })
    }

    // Cannot use the default implementation because the types are the same between term and iri
    // Which makes mapping impossible to fail
    fn term_as_iri(term: &Self::Term) -> Result<Self::IRI, RDFError> {
        if term.ty == Some(sophia_api::term::TermKind::Iri) {
            <Self::Term as TryInto<Self::IRI>>::try_into(term.clone()).map_err(|_| {
                RDFError::TermAsIri {
                    term: term.to_string(),
                }
            })
        } else {
            Err(RDFError::TermAsIri {
                term: term.to_string(),
            })
        }
    }

    fn term_as_iris(term: &Self::Term) -> Result<IriS, RDFError> {
        if term.ty != Some(sophia_api::term::TermKind::Iri) {
            return Err(RDFError::TermAsIriS {
                term: term.to_string(),
            });
        }

        let iri = <Self::Term as TryInto<Self::IRI>>::try_into(term.clone()).map_err(|_| {
            RDFError::TermAsIriS {
                term: term.to_string(),
            }
        })?;
        let iri_s: IriS = iri.into();
        Ok(iri_s)
    }

    fn term_as_bnode(term: &Self::Term) -> Result<Self::BNode, RDFError> {
        if term.ty != Some(sophia_api::term::TermKind::BlankNode) {
            return Err(RDFError::TermAsBNode {
                term: term.to_string(),
            });
        }
        <Self::Term as TryInto<Self::BNode>>::try_into(term.clone()).map_err(|_| {
            RDFError::TermAsBNode {
                term: term.to_string(),
            }
        })
    }

    fn compare(&self, term1: &Self::Term, term2: &Self::Term) -> Result<Ordering, RDFError> {
        term1
            .partial_cmp(&term2)
            .ok_or_else(|| RDFError::ComparisonError {
                term1: term1.to_string(),
                term2: term2.to_string(),
            })
    }
}

fn map(term: sophia_api::term::TermKind) -> r_term::TermKind {
    match term {
        sophia_api::term::TermKind::Iri => TermKind::Iri,
        sophia_api::term::TermKind::Literal => TermKind::Literal,
        sophia_api::term::TermKind::BlankNode => TermKind::BlankNode,
        sophia_api::term::TermKind::Triple => TermKind::Triple,
        sophia_api::term::TermKind::Variable => TermKind::BlankNode,
    }
}

impl r_term::Subject for MyTerm<'_> {
    fn kind(&self) -> r_term::TermKind {
        if let Some(ty) = self.ty {
            map(ty)
        } else {
            TermKind::BlankNode
        }
    }
}
impl Matcher<MyTerm<'static>> for MyTerm<'static> {
    fn value(&self) -> Option<&MyTerm<'static>> {
        Some(self)
    }
}

pub struct PrefixedPath<'a> {
    pub path: &'a SHACLPath,
    pub prefixes: &'a Prefixes,
}
impl<'a> PrefixedPath<'a> {
    pub fn new(path: &'a SHACLPath, prefixes: &'a Prefixes) -> Self {
        Self { path, prefixes }
    }
}

impl<'a> Display for PrefixedPath<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.path {
            SHACLPath::Predicate { pred } => {
                if let Some(shorten) = self.prefixes.shorten(pred.as_str()) {
                    write!(f, "{shorten}")
                } else {
                    write!(f, "{pred}")
                }
            }
            SHACLPath::Alternative { paths } => {
                write!(f, "(")?;
                let mut first = true;
                for p in paths {
                    if !first {
                        write!(f, " | ")?;
                    }
                    write!(f, "{}", Self::new(p, self.prefixes))?;
                    first = false;
                }
                write!(f, ")")
            }
            SHACLPath::Sequence { paths } => {
                write!(f, "(")?;
                let mut first = true;
                for p in paths {
                    if !first {
                        write!(f, " / ")?;
                    }
                    write!(f, "{}", Self::new(p, self.prefixes))?;
                    first = false;
                }
                write!(f, ")")
            }
            SHACLPath::Inverse { path } => {
                write!(f, "^({})", Self::new(path, self.prefixes))
            }
            SHACLPath::ZeroOrMore { path } => {
                write!(f, "({})*", Self::new(path, self.prefixes))
            }
            SHACLPath::OneOrMore { path } => {
                write!(f, "({})+", Self::new(path, self.prefixes))
            }
            SHACLPath::ZeroOrOne { path } => {
                write!(f, "({})?", Self::new(path, self.prefixes))
            }
        }
    }
}
