use crate::context_entry_value::ContextEntryValue;
#[cfg(not(target_family = "wasm"))]
use crate::manifest::Manifest;
#[cfg(not(target_family = "wasm"))]
use crate::manifest_error::ManifestError;
#[cfg(not(target_family = "wasm"))]
use crate::manifest_map::ManifestMap;
#[cfg(not(target_family = "wasm"))]
use crate::manifest_mode::ManifestShExSyntaxMode;
#[cfg(not(target_family = "wasm"))]
use ValidationType::*;
#[cfg(not(target_family = "wasm"))]
use prefixmap::IriRef;
#[cfg(not(target_family = "wasm"))]
use rudof_iri::IriS;
#[cfg(not(target_family = "wasm"))]
use rudof_rdf::rdf_core::term::{Object, literal::ConcreteLiteral};
#[cfg(not(target_family = "wasm"))]
use rudof_rdf::{
    rdf_core::RDFFormat,
    rdf_impl::{OxigraphInMemory, ReaderMode},
};
use serde::de::{self};
use serde::{Deserialize, Deserializer, Serialize};
#[cfg(not(target_family = "wasm"))]
use shex_ast::ir::external_resolver::SchemaExternalResolver;
#[cfg(not(target_family = "wasm"))]
use shex_ast::ir::shape_label::ShapeLabel;
#[cfg(not(target_family = "wasm"))]
use shex_ast::ir::{map_state::MapState, schema_ir::SchemaIR, semantic_actions_registry::SemanticActionsRegistry};
#[cfg(not(target_family = "wasm"))]
use shex_ast::shapemap::ValidationStatus;
#[cfg(not(target_family = "wasm"))]
use shex_ast::{Node, ShExParser, ast::Schema as SchemaJson};
#[cfg(not(target_family = "wasm"))]
use shex_ast::{ResolveMethod, ir::ast2ir::AST2IR};
#[cfg(not(target_family = "wasm"))]
use shex_validation::{Validator, ValidatorConfig};
use std::collections::HashMap;
use std::fmt::{self, Display};
#[cfg(not(target_family = "wasm"))]
use std::path::Path;
#[cfg(not(target_family = "wasm"))]
use std::path::PathBuf;
#[cfg(not(target_family = "wasm"))]
use std::str::FromStr;
#[cfg(not(target_family = "wasm"))]
use tracing::{debug, trace};
#[cfg(not(target_family = "wasm"))]
use url::Url;

#[allow(unused)]
#[derive(Deserialize, Debug)]
#[serde(from = "ManifestValidationJson")]
pub struct ManifestValidation {
    entry_names: Vec<String>,
    map: HashMap<String, ValidationEntry>,
}

#[derive(Deserialize, Serialize, Debug)]
struct ManifestValidationJson {
    #[serde(rename = "@context")]
    context: Vec<ContextEntryValue>,

    #[serde(rename = "@graph")]
    graph: Vec<ManifestValidationGraph>,
}

impl From<ManifestValidationJson> for ManifestValidation {
    fn from(m: ManifestValidationJson) -> Self {
        let entries = &m.graph[0].entries;
        let names = entries.iter().map(|e| e.name.clone()).collect();
        let mut map: HashMap<String, ValidationEntry> = HashMap::new();
        for entry in entries {
            map.insert(entry.name.clone(), entry.clone());
        }
        ManifestValidation {
            entry_names: names,
            map,
        }
    }
}

#[derive(Deserialize, Serialize, Debug)]
struct ManifestValidationGraph {
    #[serde(rename = "@id")]
    id: String,

    #[serde(rename = "@type")]
    type_: String,

    #[serde(rename = "rdfs:comment")]
    comment: String,

    entries: Vec<ValidationEntry>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct ValidationEntry {
    #[serde(rename = "@id")]
    id: String,

    #[serde(rename = "@type")]
    type_: String,
    action: Action,
    #[serde(rename = "extensionResults")]
    extension_results: Vec<ExtensionResult>,
    name: String,
    #[serde(rename = "trait")]
    trait_: Option<Vec<String>>,
    comment: String,
    status: String,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct Action {
    schema: String,
    shape: Option<String>,
    data: String,
    map: Option<String>,
    focus: Option<Focus>,
    #[serde(rename = "shapeExterns")]
    shape_externs: Option<String>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct ExtensionResult {
    extension: String,
    prints: String,
}

#[derive(Serialize, Debug, Clone)]
enum Focus {
    Single(String),
    Typed(String, String),
}

impl<'de> Deserialize<'de> for Focus {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct FocusVisitor;

        impl<'de> de::Visitor<'de> for FocusVisitor {
            type Value = Focus;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("Focus")
            }

            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(Focus::Single(value.to_string()))
            }

            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
            where
                V: de::MapAccess<'de>,
            {
                if let Some("@value") = map.next_key()? {
                    let value: String = map.next_value()?;
                    if let Some("@type") = map.next_key()? {
                        let type_: String = map.next_value()?;
                        Ok(Focus::Typed(value, type_))
                    } else {
                        Err(de::Error::missing_field("@type"))
                    }
                } else {
                    Err(de::Error::missing_field("@value"))
                }
            }
        }
        deserializer.deserialize_any(FocusVisitor {})
    }
}

impl Display for Focus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Focus::Single(str) => write!(f, "{str}"),
            Focus::Typed(str, str_type) => write!(f, "{str}^^{str_type}"),
        }
    }
}

#[cfg(not(target_family = "wasm"))]
fn change_extension(name: String, old_extension: String, new_extension: String) -> String {
    if name.ends_with(&old_extension) {
        let (first, _) = name.split_at(name.len() - old_extension.len());
        format!("{first}{new_extension}")
    } else {
        name
    }
}

#[cfg(not(target_family = "wasm"))]
fn parse_schema(
    path: &Path,
    _base: Option<&str>,
    entry_name: &String,
    manifest_shex_syntax_mode: ManifestShExSyntaxMode,
) -> Result<SchemaJson, Box<ManifestError>> {
    match manifest_shex_syntax_mode {
        ManifestShExSyntaxMode::ShExJ => SchemaJson::parse_schema(path).map_err(|e| {
            Box::new(ManifestError::SchemaJsonError {
                error: Box::new(e),
                entry_name: entry_name.to_string(),
            })
        }),
        ManifestShExSyntaxMode::ShExC => {
            let base = path_to_iri(path)?;
            ShExParser::parse_buf(path, Some(base)).map_err(|e| {
                Box::new(ManifestError::ShExCParsingError {
                    error: Box::new(e),
                    entry_name: entry_name.to_string(),
                    shex_path: Box::new(path.to_path_buf()),
                })
            })
        },
    }
}

impl ValidationEntry {
    #[cfg(not(target_family = "wasm"))]
    pub fn traits(&self) -> Vec<String> {
        match &self.trait_ {
            None => Vec::new(),
            Some(traits) => traits.clone(),
        }
    }

    #[cfg(not(target_family = "wasm"))]
    pub fn run(
        &self,
        folder: &Path,
        manifest_shex_syntax_mode: ManifestShExSyntaxMode,
    ) -> Result<(), Box<ManifestError>> {
        let path_absolute = folder.canonicalize().map_err(|err| ManifestError::AbsolutePathError {
            base: folder.to_string_lossy().to_string().into(),
            error: err,
        })?;
        let path_iri = path_to_iri(&path_absolute)?;
        // Append trailing slash so relative IRIs inside the folder resolve correctly
        // per RFC 3986: without it, the last path segment is stripped on resolution.
        let folder_iri_str = format!("{}/", path_iri.as_str());
        let base_str = Some(folder_iri_str.as_str());

        let graph = OxigraphInMemory::parse_data(
            &self.action.data,
            &RDFFormat::Turtle,
            folder,
            base_str,
            &ReaderMode::Strict,
        )
        .map_err(|e| Box::new(ManifestError::SRDFError(e)))?;
        trace!("Data obtained from: {}", self.action.data);

        let path_schema = get_path_schema(&self.action.schema, folder, manifest_shex_syntax_mode);
        trace!("Path schema: {}", path_schema.display());
        let schema = parse_schema(&path_schema, base_str, &self.name, manifest_shex_syntax_mode)?;
        trace!("Schema obtained from: {}", self.action.schema);

        trace!("Entry action: {:?}", self.action);

        // If the test declares an externs schema (`shapeExterns`), load it as a
        // `SchemaExternalResolver` and register it in the `ValidatorConfig`. The
        // compiler now applies the registry automatically; no manual rewrite is needed.
        let mut config = ValidatorConfig::default();
        if let Some(externs_rel) = &self.action.shape_externs {
            let externs_path = folder.join(externs_rel);
            let resolver = SchemaExternalResolver::from_path(&externs_path).map_err(|error| {
                ManifestError::ExternalResolverError {
                    entry_name: self.name.clone(),
                    error,
                }
            })?;
            config = config.with_external_resolver(resolver);
        }

        let mut map_state = MapState::default();
        let registry = SemanticActionsRegistry::default();
        registry.set_map_state(&mut map_state);
        let mut compiler = AST2IR::new(&ResolveMethod::default(), map_state);
        let mut compiled_schema = SchemaIR::new(registry);
        let base_iri = path_to_iri(&path_schema)?;
        trace!("Compiling schema, base: {base_iri}");
        compiler
            .compile(
                &schema,
                &base_iri,
                &Some(base_iri.clone()),
                &mut compiled_schema,
                config.external_resolvers(),
            )
            .map_err(|e| Box::new(ManifestError::SchemaIRError(e)))?;
        let schema = compiled_schema.clone();
        let mut validator = Validator::new(&compiled_schema, &config).map_err(ManifestError::ValidationError)?;
        let expected_type = parse_type(&self.type_)?;
        debug!("Schema compiled...expected type: {:?}", expected_type);
        trace!("Schema: {}", schema);

        let mut failed_status: Vec<ValidationStatus> = Vec::new();
        let mut passed_status: Vec<ValidationStatus> = Vec::new();
        if let Some(map) = &self.action.map {
            let map_path = folder.join(map);
            let str = std::fs::read_to_string(&map_path).map_err(|e| ManifestError::ReadingShapeMap {
                error: e.to_string(),
                entry: self.name.clone(),
                map: map_path.clone(),
            })?;
            let manifest_map =
                serde_json::from_str::<ManifestMap>(&str).map_err(|e| ManifestError::ParsingManifestMap {
                    error: e.to_string(),
                    entry: self.name.clone(),
                })?;
            for entry in manifest_map.entries() {
                let node = parse_node(entry.node(), base_str)?;
                let shape = parse_shape(entry.shape(), base_str)?;
                let result = validator
                    .validate_node_shape(&node, &shape, &graph, &schema, &Some(graph.prefixmap().clone()))
                    .map_err(|e| Box::new(ManifestError::ValidationError(e)))?;

                let partial_status = result.get_info(&node, &shape).unwrap();
                if partial_status.is_conformant() {
                    passed_status.push(partial_status);
                } else {
                    failed_status.push(partial_status);
                }
            }
        }
        if let Some(focus) = &self.action.focus {
            trace!("Focus: {}", focus);
            let node = parse_focus(focus, base_str)?;
            let shape = parse_maybe_shape(&self.action.shape, base_str)?;
            trace!("Focus node: {}, shape: {}", node, shape);

            let result = validator
                .validate_node_shape(&node, &shape, &graph, &schema, &Some(graph.prefixmap().clone()))
                .map_err(|e| Box::new(ManifestError::ValidationError(e)))?;
            let validation_status = result.get_info(&node, &shape).unwrap();
            if validation_status.is_conformant() {
                passed_status.push(validation_status);
            } else {
                failed_status.push(validation_status);
            }
        }
        match (expected_type, failed_status.is_empty()) {
            (Validation, true) => Ok(()),
            (Validation, _) => {
                debug!("Expected OK but failed {}", &self.name);
                Err(Box::new(ManifestError::ExpectedOkButObtained {
                    failed_status,
                    passed_status,
                    entry: self.name.clone(),
                }))
            },
            (Failure, false) => Ok(()),
            (Failure, _) => {
                debug!("Expected Failure but passed {}", &self.name);
                Err(Box::new(ManifestError::ExpectedFailureButObtained {
                    failed_status,
                    passed_status,
                    entry: self.name.clone(),
                }))
            },
        }
    }
}

#[cfg(not(target_family = "wasm"))]
fn parse_maybe_shape(shape: &Option<String>, base: Option<&str>) -> Result<ShapeLabel, Box<ManifestError>> {
    match &shape {
        None => Ok(ShapeLabel::Start),
        Some(str) => {
            let shape = parse_shape(str, base)?;
            Ok(shape)
        },
    }
}

/*
fn parse_maybe_focus(
    maybe_focus: &Option<Focus>,
    entry: &str,
    base: Option<&str>,
) -> Result<Node, ManifestError> {
    match maybe_focus {
        None => Err(ManifestError::NoFocusNode {
            entry: entry.to_string(),
        }),
        Some(focus) => {
            let node = parse_focus(focus, base)?;
            Ok(node)
        }
    }
}
*/

#[cfg(not(target_family = "wasm"))]
fn parse_focus(focus: &Focus, base: Option<&str>) -> Result<Node, Box<ManifestError>> {
    match focus {
        Focus::Single(str) => {
            trace!("Parsing focus node: {str}");
            let node = parse_node(str, base)?;
            trace!("Parsed focus node: {node}");
            Ok(node)
        },
        Focus::Typed(str, str_type) => {
            let datatype = IriS::from_str(str_type.as_str()).map_err(|e| Box::new(ManifestError::IriError(e)))?;
            Ok(Object::Literal(ConcreteLiteral::lit_datatype(str, &IriRef::Iri(datatype))).into())
        },
    }
}

#[cfg(not(target_family = "wasm"))]
fn parse_node(str: &str, base: Option<&str>) -> Result<Node, Box<ManifestError>> {
    Node::parse(str, base).map_err(|e| {
        Box::new(ManifestError::ParsingFocusNode {
            value: str.to_string(),
            error: Box::new(e),
        })
    })
}

#[cfg(not(target_family = "wasm"))]
fn parse_shape(str: &str, base: Option<&str>) -> Result<ShapeLabel, Box<ManifestError>> {
    let node = Node::parse(str, base).map_err(|e| {
        Box::new(ManifestError::ParsingShapeLabel {
            value: str.to_string(),
            error: e.to_string(),
        })
    })?;
    let shape_label = ShapeLabel::from_object(node.as_object()).map_err(|e| ManifestError::ParsingShapeLabel {
        value: str.to_string(),
        error: e.to_string(),
    })?;
    Ok(shape_label)
}

#[cfg(not(target_family = "wasm"))]
fn parse_type(str: &str) -> Result<ValidationType, Box<ManifestError>> {
    match str {
        "sht:ValidationTest" => Ok(Validation),
        "sht:ValidationFailure" => Ok(Failure),
        _ => Err(Box::new(ManifestError::ParsingValidationType {
            value: str.to_string(),
        })),
    }
}

#[cfg(not(target_family = "wasm"))]
fn path_to_iri(path: &Path) -> Result<IriS, Box<ManifestError>> {
    let canonical = path.canonicalize().map_err(|err| {
        Box::new(ManifestError::AbsolutePathError {
            base: path.to_string_lossy().to_string().into(),
            error: err,
        })
    })?;
    let url = Url::from_file_path(canonical).map_err(|_| ManifestError::FromFilePath {
        path: path.to_path_buf(),
    })?;
    let iri = IriS::new_unchecked(url.as_str());
    Ok(iri)
}

#[cfg(not(target_family = "wasm"))]
fn get_path_schema(schema: &String, folder: &Path, manifest_shex_syntax_mode: ManifestShExSyntaxMode) -> PathBuf {
    let new_schema_name = match manifest_shex_syntax_mode {
        ManifestShExSyntaxMode::ShExJ => change_extension(schema.to_string(), ".shex".to_string(), ".json".to_string()),
        ManifestShExSyntaxMode::ShExC => schema.to_string(),
    };
    let json_path = Path::new(&new_schema_name);
    let mut attempt = PathBuf::from(folder);
    attempt.push(json_path);
    attempt
}

#[cfg(not(target_family = "wasm"))]
#[derive(Debug, PartialEq)]
enum ValidationType {
    Validation,
    Failure,
}

#[cfg(not(target_family = "wasm"))]
impl Manifest for ManifestValidation {
    fn len(&self) -> usize {
        self.entry_names.len()
    }

    fn is_empty(&self) -> bool {
        self.entry_names.is_empty()
    }

    fn entry_names(&self) -> Vec<String> {
        self.entry_names.clone() // iter().map(|n| n.clone()).collect()
    }

    fn run_entry(
        &self,
        name: &str,
        base: &Path,
        manifest_shex_syntax_mode: ManifestShExSyntaxMode,
    ) -> Result<(), Box<ManifestError>> {
        match self.map.get(name) {
            None => Err(Box::new(ManifestError::NotFoundEntry { name: name.to_string() })),
            Some(entry) => entry.run(base, manifest_shex_syntax_mode),
        }
    }

    fn has_traits(&self, name: &str) -> Result<Vec<String>, Box<ManifestError>> {
        match self.map.get(name) {
            None => Err(Box::new(ManifestError::NotFoundEntry { name: name.to_string() })),
            Some(entry) => Ok(entry.traits().clone()),
        }
    }
}

#[cfg(all(not(target_family = "wasm"), test))]
mod tests {
    use super::*;
    use std::fs;
    use std::path::Path;

    #[test]
    fn count_validation_entries() {
        let manifest_path = Path::new("shexTest/validation/manifest.jsonld");
        let manifest = {
            let manifest_str = fs::read_to_string(manifest_path).unwrap();
            serde_json::from_str::<ManifestValidation>(&manifest_str).unwrap()
        };
        assert_eq!(manifest.entry_names.len(), 1183);
    }
}
