from __future__ import annotations

from pathlib import Path

from rdflib import Dataset, Graph, Literal, RDF, URIRef

import cf_ontology.ontology_manager as ontology_manager_module
from cf_ontology import OntologyManager
from cf_ontology.dataset_views import iter_graphs
from cf_ontology.namespaces import CF_BASE
from cf_ontology.quad_store import QuadStore


def _graph_ids(dataset) -> set[str]:
    return {str(graph.identifier) for graph in iter_graphs(dataset)}


def _simple_document(node_id: str, *, label: str) -> dict:
    return {
        "@context": {
            "ex": "https://example.org/",
            "schema": "https://schema.org/",
            "cf": CF_BASE,
            "skos": "http://www.w3.org/2004/02/skos/core#",
        },
        "@graph": [
            {
                "@id": node_id,
                "@type": "cf:ProcessingPipeline",
                "skos:prefLabel": label,
                "schema:name": label,
            }
        ],
    }


def _pipeline_nquads(label: str) -> str:
    return "\n".join(
        [
            (
                "<https://example.org/pipelines/demo> "
                f"<{RDF.type}> <{CF_BASE}ProcessingPipeline> "
                "<https://example.org/source-graph> ."
            ),
            (
                "<https://example.org/pipelines/demo> "
                f"<https://schema.org/name> \"{label}\" "
                "<https://example.org/source-graph> ."
            ),
        ]
    )


def test_quad_store_load_dataset_returns_named_graph_view(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)
    store.ingest_document(
        _simple_document("https://example.org/pipeline/a", label="alpha"),
        graph_id="g1",
        graph_type="custom",
        package="tests",
        source_path=None,
    )
    store.ingest_document(
        _simple_document("https://example.org/pipeline/b", label="beta"),
        graph_id="g2",
        graph_type="custom",
        package="tests",
        source_path=None,
    )

    dataset = store.load_dataset(["g1", "g2"])
    graph = store.load_graph(["g1", "g2"])

    assert _graph_ids(dataset) == {"g1", "g2"}
    assert (
        URIRef("https://example.org/pipeline/a"),
        URIRef("https://schema.org/name"),
        Literal("alpha"),
    ) in graph
    assert (
        URIRef("https://example.org/pipeline/b"),
        URIRef("https://schema.org/name"),
        Literal("beta"),
    ) in graph


def test_ontology_manager_active_dataset_keeps_current_pipeline_revision_only() -> None:
    om = OntologyManager(load_resources=False)
    doc1 = _simple_document("https://example.org/pipeline/demo", label="rev1")
    doc2 = _simple_document("https://example.org/pipeline/demo", label="rev2")

    rev1 = om.commit_pipeline("demo-pipeline", doc1, user="tester", message="rev1")
    rev2 = om.commit_pipeline("demo-pipeline", doc2, user="tester", message="rev2")

    active_graph_ids = _graph_ids(om.get_active_dataset())
    assert f"pipe:demo-pipeline@{rev2}" in active_graph_ids
    assert f"pipe:demo-pipeline@{rev1}" not in active_graph_ids

    rev1_dataset = om.get_pipeline_revision_dataset("demo-pipeline", rev=rev1)
    assert _graph_ids(rev1_dataset) == {f"pipe:demo-pipeline@{rev1}"}


def test_quad_store_ingest_dataset_regraphs_default_and_named_input_quads(
    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)

    dataset = Dataset()
    dataset.add(
        (
            URIRef("https://example.org/pipeline/default"),
            RDF.type,
            URIRef(f"{CF_BASE}ProcessingPipeline"),
        )
    )
    dataset.graph(URIRef("https://example.org/source-graph")).add(
        (
            URIRef("https://example.org/pipeline/named"),
            URIRef("https://schema.org/name"),
            Literal("named"),
        )
    )

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

    stored_dataset = store.load_dataset(["canonical-dataset-graph"])
    stored_graph = store.load_graph(["canonical-dataset-graph"])

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


def test_commit_pipeline_from_nquads_regraphs_under_canonical_revision_id() -> None:
    om = OntologyManager(load_resources=False)

    rev = om.commit_pipeline("demo-pipeline", _pipeline_nquads("rev1"), user="tester")

    revision_dataset = om.get_pipeline_revision_dataset("demo-pipeline", rev=rev)
    revision_graph = om.get_pipeline_revision_graph("demo-pipeline", rev=rev)

    assert _graph_ids(revision_dataset) == {f"pipe:demo-pipeline@{rev}"}
    assert "https://example.org/source-graph" not in _graph_ids(revision_dataset)
    assert (
        URIRef("https://example.org/pipelines/demo"),
        RDF.type,
        URIRef(f"{CF_BASE}ProcessingPipeline"),
    ) in revision_graph


def test_validate_pipeline_document_uses_dataset_views_not_document_exports(
    monkeypatch,
) -> None:
    om = OntologyManager(load_resources=False)
    om._ontology_store.ingest_document(
        {
            "@context": {
                "ex": "https://example.org/",
                "schema": "https://schema.org/",
            },
            "@graph": [
                {
                    "@id": "https://example.org/base",
                    "schema:name": "base",
                }
            ],
        },
        graph_id="base",
        graph_type="custom",
        package="tests",
        source_path=None,
    )

    def _boom(*_args, **_kwargs):
        raise AssertionError("legacy document export path must not be used during validation")

    for store in (om._ontology_store, om._steps_store, om._pipelines_store):
        monkeypatch.setattr(store, "export_document", _boom)
    monkeypatch.setattr(
        ontology_manager_module,
        "load_rdf_document",
        lambda *_args, **_kwargs: (_ for _ in ()).throw(
            AssertionError("raw N-Quads validation should not route through document loading")
        ),
    )

    def _fake_validate(data_graph, shacl_graph, **_kwargs):
        assert isinstance(data_graph, Graph)
        assert isinstance(shacl_graph, Graph)
        assert (
            URIRef("https://example.org/base"),
            URIRef("https://schema.org/name"),
            Literal("base"),
        ) in data_graph
        assert (
            URIRef("https://example.org/pipelines/demo"),
            RDF.type,
            URIRef(f"{CF_BASE}ProcessingPipeline"),
        ) in data_graph
        return True, Graph(), "ok"

    monkeypatch.setattr(ontology_manager_module, "shacl_validate", _fake_validate)

    result = om.validate_pipeline_document(_pipeline_nquads("runtime"))

    assert result["conforms"] is True
    assert result["report_text"] == "ok"
