use crate::{
    Result, Rudof,
    errors::{IriError, ShExError},
    formats::{DataReaderMode, InputSpec, ShExFormat},
    utils::{PrefixDirective, default_prefix_header, get_base_iri},
};
use rudof_iri::{IriS, MimeType};
use rudof_rdf::rdf_core::RDFFormat;
use shex_ast::{
    ResolveMethod, Schema as ShExSchema,
    compact::ShExParser,
    ir::{
        map_action_extension::MapActionExtension, schema_ir::SchemaIR,
        semantic_actions_registry::SemanticActionsRegistry, test_action_extension::TestActionExtension,
    },
    shexr::shexr_parser::ShExRParser,
};
use shex_validation::Validator as ShExValidator;
use sparql_service::RdfData;
use std::{env, io};
// use tracing::trace;
#[cfg(not(target_family = "wasm"))]
use url::Url;

pub fn load_shex_schema(
    rudof: &mut Rudof,
    schema: &InputSpec,
    schema_format: Option<&ShExFormat>,
    base_schema: Option<&str>,
    reader_mode: Option<&DataReaderMode>,
) -> Result<()> {
    let (schema_format, base_schema, reader_mode) = init_defaults(rudof, schema_format, base_schema, reader_mode)?;

    let schema_reader = schema
        .open_read(Some(schema_format.mime_type()), "ShEx Schema")
        .map_err(|error| ShExError::DataSourceSpec {
            message: format!("Failed to open schema source '{}': {error}", schema.source_name()),
        })?;

    match schema_format {
        ShExFormat::ShExC => {
            load_shex_schema_shexc(rudof, schema_reader, &schema.source_name(), base_schema, &reader_mode)?;
        },
        ShExFormat::ShExJ => {
            load_shex_schema_shexj(rudof, schema_reader, &schema.source_name(), base_schema, &reader_mode)?;
        },
        ShExFormat::Turtle
        | ShExFormat::NTriples
        | ShExFormat::RdfXml
        | ShExFormat::TriG
        | ShExFormat::N3
        | ShExFormat::NQuads => {
            load_shex_schema_shexr(
                rudof,
                schema_reader,
                &schema.source_name(),
                schema_format,
                base_schema,
                &reader_mode,
            )?;
        },
        _ => {
            todo!("Implement loading for ShEx format '{}'", schema_format);
        },
    }

    Ok(())
}

fn init_defaults(
    rudof: &mut Rudof,
    schema_format: Option<&ShExFormat>,
    base_schema: Option<&str>,
    reader_mode: Option<&DataReaderMode>,
) -> Result<(ShExFormat, IriS, DataReaderMode)> {
    let base_schema = get_base_iri(rudof, base_schema)?;
    Ok((
        schema_format.copied().unwrap_or_default(),
        base_schema,
        reader_mode.copied().unwrap_or_default(),
    ))
}

fn load_shex_schema_shexc<R: io::Read>(
    rudof: &mut Rudof,
    mut schema_reader: R,
    source_name: &str,
    base_schema: IriS,
    reader_mode: &DataReaderMode,
) -> Result<()> {
    let mut content = String::new();
    schema_reader
        .read_to_string(&mut content)
        .map_err(|error| ShExError::FailedParsingShExSchema {
            error: error.to_string(),
            source_name: source_name.to_string(),
            format: "ShExC".to_string(),
        })?;
    let header = default_prefix_header(rudof, &content, PrefixDirective::Sparql);
    let prefixed_reader = io::Cursor::new(format!("{header}{content}"));
    #[cfg(target_family = "wasm")]
    let source_iri = {
        IriS::from_str(source_name).map_err(|error| IriError::ParseError {
            iri: source_name.to_string(),
            error: error.to_string(),
        })?
    };

    #[cfg(not(target_family = "wasm"))]
    let source_iri = {
        let cwd = env::current_dir().map_err(|e| IriError::PathConversionError {
            path: ".".to_string(),
            error: format!("Error resolving source IRI. Failed to get current directory: {e}"),
        })?;

        let url = Url::from_directory_path(&cwd).map_err(|_| IriError::PathConversionError {
            path: cwd.to_string_lossy().to_string(),
            error: "Error resolving source IRI. Cannot convert current directory to a file URL".to_string(),
        })?;

        IriS::from_str_base(source_name, Some(url.as_str())).map_err(|e| IriError::ParseError {
            iri: source_name.to_string(),
            error: format!("Failed to parse source name as IRI: {e}"),
        })?
    };

    let schema = ShExParser::from_reader(prefixed_reader, Some(base_schema.clone()), &source_iri).map_err(|error| {
        ShExError::FailedParsingShExSchema {
            error: error.to_string(),
            source_name: source_name.to_string(),
            format: "ShExC".to_string(),
        }
    })?;

    compile_shex_schema(rudof, base_schema, schema, reader_mode)?;

    Ok(())
}

fn load_shex_schema_shexj<R: io::Read>(
    rudof: &mut Rudof,
    schema_reader: R,
    source_name: &str,
    base_schema: IriS,
    reader_mode: &DataReaderMode,
) -> Result<()> {
    let schema = ShExSchema::from_reader(schema_reader).map_err(|error| ShExError::FailedParsingShExSchema {
        error: error.to_string(),
        source_name: source_name.to_string(),
        format: "ShExJ".to_string(),
    })?;

    compile_shex_schema(rudof, base_schema, schema, reader_mode)?;

    Ok(())
}

/// Loads a ShEx schema encoded as RDF (ShExR: Turtle, N-Triples, RDF/XML or
/// N-Quads; TriG and N3 are rejected below since reading them isn't
/// implemented at the RDF-parsing layer yet) by parsing the RDF into an
/// in-memory graph and then decoding the `sx:Schema` instance it contains
/// via [`ShExRParser`].
fn load_shex_schema_shexr<R: io::Read>(
    rudof: &mut Rudof,
    mut schema_reader: R,
    source_name: &str,
    schema_format: ShExFormat,
    base_schema: IriS,
    reader_mode: &DataReaderMode,
) -> Result<()> {
    if matches!(schema_format, ShExFormat::TriG | ShExFormat::N3) {
        return Err(ShExError::FailedParsingShExSchema {
            error: "reading RDF in this format is not yet implemented; use turtle, ntriples, rdfxml or nquads \
                    instead"
                .to_string(),
            source_name: source_name.to_string(),
            format: schema_format.to_string(),
        }
        .into());
    }

    let rdf_format: RDFFormat = schema_format.try_into()?;
    let mut rdf_data = RdfData::new();

    let merge_result = if matches!(schema_format, ShExFormat::Turtle) {
        let mut content = String::new();
        schema_reader
            .read_to_string(&mut content)
            .map_err(|error| ShExError::FailedParsingShExSchema {
                error: error.to_string(),
                source_name: source_name.to_string(),
                format: schema_format.to_string(),
            })?;
        let header = default_prefix_header(rudof, &content, PrefixDirective::Turtle);
        let mut prefixed_reader = io::Cursor::new(format!("{header}{content}").into_bytes());
        rdf_data.merge_from_reader(
            &mut prefixed_reader,
            source_name,
            &rdf_format,
            Some(base_schema.as_str()),
            &reader_mode.into(),
        )
    } else {
        rdf_data.merge_from_reader(
            &mut schema_reader,
            source_name,
            &rdf_format,
            Some(base_schema.as_str()),
            &reader_mode.into(),
        )
    };
    merge_result.map_err(|error| ShExError::FailedParsingShExSchema {
        error: error.to_string(),
        source_name: source_name.to_string(),
        format: schema_format.to_string(),
    })?;

    let schema = ShExRParser::new(rdf_data)
        .parse()
        .map_err(|error| ShExError::FailedParsingShExSchema {
            error: error.to_string(),
            source_name: source_name.to_string(),
            format: schema_format.to_string(),
        })?;

    compile_shex_schema(rudof, base_schema, schema, reader_mode)?;

    Ok(())
}

fn compile_shex_schema(rudof: &mut Rudof, base: IriS, schema: ShExSchema, reader_mode: &DataReaderMode) -> Result<()> {
    // Build the registry with all extensions pre-registered so that the Arc<Mutex<MapState>>
    // inside MapActionExtension is shared with every closure compiled from the schema.
    let map_state = rudof.map_state.clone().unwrap_or_default();
    let registry = SemanticActionsRegistry::new().with(vec![
        Box::new(TestActionExtension::new()),
        Box::new(MapActionExtension::new(map_state.clone())),
    ]);
    // trace!("Initialized SemanticActionsRegistry with MapState: {map_state:#?}");

    let mut schema_ir = SchemaIR::new(registry);

    let validator_config = rudof.config.shex_validator();
    schema_ir
        .populate_from_schema_json(
            &schema,
            validator_config.external_resolvers(),
            &ResolveMethod::default(),
            &Some(base.clone()),
        )
        .map_err(|error| ShExError::FailedCompilingShExSchema {
            error: error.to_string(),
        })?;

    match reader_mode {
        DataReaderMode::Strict => {
            let neg_cycles = schema_ir.neg_cycles();
            let has_neg_cycles = !neg_cycles.is_empty();

            if has_neg_cycles {
                Err(ShExError::FailedCompilingShExSchema {
                    error: format!(
                        "Schema contains negative cycles in its dependency graph. Found {} negative cycle(s).",
                        neg_cycles.len()
                    ),
                })?
            }
        },
        DataReaderMode::Lax => {},
    }

    let validator = ShExValidator::new(&schema_ir, rudof.config.shex_validator()).map_err(|_| {
        ShExError::FailedCompilingShExSchema {
            error: "Failed to create ShEx validator.".to_string(),
        }
    })?;

    // schema_ir.set_map_state(&mut map_state);

    rudof.shex_schema = Some(schema);
    rudof.shex_schema_ir = Some(schema_ir);
    rudof.shex_validator = Some(validator);
    Ok(())
}
