from __future__ import annotations

from pathlib import Path

from rdflib import Dataset, Literal, OWL, RDF, URIRef
from rdflib.collection import Collection

from cf_ontology import dataset_from_document, ingest_rdf_documents
from cf_ontology.namespaces import CF_BASE
from cf_ontology.quad_store import QuadStore
from cf_ontology.rdf_document import load_rdf_document
from cf_ontology.semantics_paths import resolve_semantics_db_path


_STEP_IRI = "https://example.org/steps/DemoStep"
_STEP_PACKAGE_IRI = "https://example.org/packages/DemoPackage"


def _project_root() -> Path:
    return Path(__file__).resolve().parents[1]


def _statement_count(dataset: Dataset) -> int:
    return sum(len(graph) for graph in dataset.graphs())


def _write_nquads(path: Path, document: dict) -> Path:
    dataset = dataset_from_document(document)
    payload = dataset.serialize(format="nquads")
    if isinstance(payload, bytes):
        payload = payload.decode("utf-8")
    path.write_text(payload, encoding="utf-8")
    return path


def _synthetic_step_document() -> dict:
    return {
        "@context": {
            "cf": CF_BASE,
            "ex": "https://example.org/",
        },
        "@graph": [
            {
                "@id": _STEP_PACKAGE_IRI,
                "@type": "cf:StepPackage",
                "cf:packageId": "demo.pkg",
                "cf:packageVersion": "1.2.3",
            },
            {
                "@id": _STEP_IRI,
                "@type": "cf:ProcessingStep",
                "cf:belongsToStepPackage": {"@id": _STEP_PACKAGE_IRI},
            },
        ],
    }


def should_parse_packaged_nquads_when_loading_each_packaged_resource() -> None:
    ontology_root = _project_root() / "src" / "cf_ontology" / "ontology"
    nq_paths = sorted(
        path
        for section in ("core", "vocab", "shapes")
        for path in (ontology_root / section).glob("*.nq")
    )

    failures: list[str] = []
    for path in nq_paths:
        dataset = Dataset()
        dataset.parse(str(path), format="nquads")
        if _statement_count(dataset) == 0:
            failures.append(str(path))

    assert nq_paths
    assert not failures, f"Packaged N-Quads files were empty after parsing: {failures}"


def should_preserve_statement_count_when_round_tripping_nquads_through_load_rdf_document(
    tmp_path: Path,
) -> None:
    document = {
        "@context": {
            "cf": CF_BASE,
            "ex": "https://example.org/",
            "schema": "https://schema.org/",
        },
        "@graph": [
            {
                "@id": "https://example.org/pipelines/demo",
                "@type": "cf:ProcessingPipeline",
                "schema:name": "demo",
            },
            {
                "@id": "https://example.org/pipelines/demo/steps",
                "@type": "cf:StepsHeader",
                "cf:stepsPath": "./steps.nq",
            },
        ],
    }
    original_dataset = dataset_from_document(document)
    path = _write_nquads(tmp_path / "document.nq", document)

    reloaded_document = load_rdf_document(path, label="roundtrip document")
    reloaded_dataset = dataset_from_document(reloaded_document)

    assert _statement_count(reloaded_dataset) == _statement_count(original_dataset)


def test_node_definition_union_members_are_readable_and_parse_correctly() -> None:
    classes_path = _project_root() / "src" / "cf_ontology" / "ontology" / "core" / "classes.nq"
    dataset = Dataset()
    dataset.parse(str(classes_path), format="nquads")

    head = dataset.value(URIRef(f"{CF_BASE}NodeDefinition"), OWL.unionOf)
    assert head is not None

    members = list(Collection(dataset.default_context, head))
    assert members == [
        URIRef(f"{CF_BASE}ProcessingPipeline"),
        URIRef(f"{CF_BASE}ProcessingStep"),
    ]


def should_store_retrievable_step_data_when_ingesting_step_nquads_document(
    tmp_path: Path,
) -> None:
    semantics_dir = tmp_path / "semantics"
    path = _write_nquads(tmp_path / "steps.nq", _synthetic_step_document())

    ingest_rdf_documents(
        [path],
        semantics_dir=semantics_dir,
        graph_type="step",
        package="tests",
    )

    store = QuadStore(
        db_path=resolve_semantics_db_path(semantics_dir, kind="steps"),
        semantics_dir=semantics_dir,
        read_only=True,
    )
    graph_ids = [entry.graph_id for entry in store.list_graphs()]
    graph = store.load_graph(graph_ids)

    assert graph_ids
    assert (
        URIRef(_STEP_IRI),
        RDF.type,
        URIRef(f"{CF_BASE}ProcessingStep"),
    ) in graph


def should_regraph_ingested_nquads_under_the_explicit_target_graph_id(
    tmp_path: Path,
) -> None:
    semantics_dir = tmp_path / "semantics"
    db_path = semantics_dir / "cf-test.duckdb"
    store = QuadStore(db_path=db_path, semantics_dir=semantics_dir)
    store.init_schema(create_pipeline_tables=False)

    nquads_text = "\n".join(
        [
            f"<https://example.org/pipeline/default> <{RDF.type}> <{CF_BASE}ProcessingPipeline> .",
            (
                "<https://example.org/pipeline/named> "
                f"<https://schema.org/name> \"named\" "
                "<https://example.org/source-graph> ."
            ),
        ]
    )

    store.ingest_nquads(
        nquads_text,
        graph_id="canonical-graph",
        graph_type="pipeline",
        package="tests",
        source_path=None,
    )

    dataset = store.load_dataset(["canonical-graph"])
    graph_ids = {str(graph.identifier) for graph in dataset.graphs() if len(graph)}
    graph = store.load_graph(["canonical-graph"])

    assert graph_ids == {"canonical-graph"}
    assert (
        URIRef("https://example.org/pipeline/default"),
        RDF.type,
        URIRef(f"{CF_BASE}ProcessingPipeline"),
    ) in graph
    assert (
        URIRef("https://example.org/pipeline/named"),
        URIRef("https://schema.org/name"),
        Literal("named"),
    ) in graph
