from __future__ import annotations

from pathlib import Path
from threading import RLock

from rdflib import Graph
from rdflib.query import ResultRow
from rdflib.term import Node


class RDFRepository:
    """Loads the ontology and data files into one local, queryable RDF graph."""

    def __init__(self, root: Path):
        self.root = root
        self.graph = Graph()
        self._lock = RLock()
        self.loaded_files: list[Path] = []
        self.reload()

    def reload(self) -> None:
        paths = [
            self.root / "ontology" / "global-shipment.ttl",
            *sorted((self.root / "ontology").glob("*-extension.ttl")),
            *sorted((self.root / "data").glob("*.ttl")),
        ]

        graph = Graph()
        loaded_files: list[Path] = []
        for path in paths:
            if path.exists() and path not in loaded_files:
                graph.parse(path, format="turtle")
                loaded_files.append(path)

        with self._lock:
            self.graph = graph
            self.loaded_files = loaded_files
            self.base_triple_count = len(graph)

    def add_triples(self, triples: list[tuple[Node, Node, Node]]) -> int:
        with self._lock:
            before = len(self.graph)
            for triple in triples:
                self.graph.add(triple)
            return len(self.graph) - before

    def query_file(self, path: Path) -> dict[str, list[dict[str, str]]]:
        if not path.exists():
            raise FileNotFoundError(path)
        return self.query(path.read_text(encoding="utf-8"))

    def query(self, sparql: str) -> dict[str, list[dict[str, str]]]:
        with self._lock:
            result = self.graph.query(sparql)

        variables = [str(variable) for variable in result.vars or []]
        rows = [self._row_to_dict(row, variables) for row in result]
        return {"rows": rows}

    @staticmethod
    def _row_to_dict(row: ResultRow, variables: list[str]) -> dict[str, str]:
        values: dict[str, str] = {}
        for variable in variables:
            value = row.get(variable)
            values[variable] = "" if value is None else str(value)
        return values

    @property
    def triple_count(self) -> int:
        return len(self.graph)

    @property
    def in_memory_triple_count(self) -> int:
        return self.triple_count - self.base_triple_count

    @property
    def loaded_file_names(self) -> list[str]:
        return [str(path.relative_to(self.root)) for path in self.loaded_files]
