"""
Vocabulary loader -- load a concept scheme from YAML or JSON-LD
and optionally ingest it into the concepts database table.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

import yaml  # type: ignore[import-untyped]

from pycharter.wiki.ontology.models import ConceptDefinition, ConceptScheme
from pycharter.wiki.rdf.context import concept_uri, set_base_uri
from pycharter.wiki.shared.errors import OntologyError

# ── YAML loading ─────────────────────────────────────────────────────────────


def _parse_concept_yaml(raw: dict[str, Any]) -> ConceptDefinition:
    """Parse a single concept entry from YAML."""
    return ConceptDefinition(
        id=raw.get("id", ""),
        label=raw.get("label", ""),
        definition=raw.get("definition"),
        broader=raw.get("broader"),
        tags=raw.get("tags", []),
        alt_labels=raw.get("alt_labels", []),
        notation=raw.get("notation"),
        examples=raw.get("examples", []),
        exact_match=raw.get("exact_match"),
        deprecated=bool(raw.get("deprecated", False)),
        replaced_by=raw.get("replaced_by"),
        node_kind=raw.get("kind"),
    )


def _load_yaml(path: Path) -> ConceptScheme:
    """Load a concept scheme from a YAML file."""
    try:
        with open(path, "r", encoding="utf-8") as f:
            data = yaml.safe_load(f)
    except yaml.YAMLError as e:
        raise OntologyError(f"Invalid YAML: {e}", path=str(path)) from e

    if not isinstance(data, dict):
        raise OntologyError(
            f"Concepts file must contain a dictionary, got {type(data).__name__}",
            path=str(path),
        )

    scheme_data = data.get("scheme", {})
    base_uri = scheme_data.get("base_uri")
    if base_uri:
        set_base_uri(base_uri)

    scheme = ConceptScheme(
        id=scheme_data.get("id", ""),
        title=scheme_data.get("title", ""),
        version=scheme_data.get("version", "0.0.0"),
        description=scheme_data.get("description", ""),
        base_uri=base_uri,
    )

    for raw in data.get("concepts", []):
        if isinstance(raw, dict):
            scheme.concepts.append(_parse_concept_yaml(raw))

    return scheme


def _load_jsonld(path: Path) -> ConceptScheme:
    """Load a concept scheme from a JSON-LD file."""
    from pycharter.wiki.rdf.jsonld_parser import parse_concepts_jsonld

    try:
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
    except json.JSONDecodeError as e:
        raise OntologyError(f"Invalid JSON: {e}", path=str(path)) from e

    return parse_concepts_jsonld(data)


# ── Public API ───────────────────────────────────────────────────────────────


def load_vocabulary(path: str | Path) -> ConceptScheme:
    """
    Load a concept scheme from YAML or JSON-LD.

    Dispatches on file extension: ``.yaml`` / ``.yml`` or ``.jsonld`` / ``.json``.
    """
    p = Path(path)
    if not p.exists():
        raise OntologyError(f"Vocabulary file not found: {path}", path=str(path))

    suffix = p.suffix.lower()
    if suffix in (".yaml", ".yml"):
        return _load_yaml(p)
    if suffix in (".jsonld", ".json"):
        return _load_jsonld(p)
    raise OntologyError(
        f"Unsupported vocabulary format: {suffix}. "
        "Supported: .yaml, .yml, .jsonld, .json",
        path=str(path),
    )


def ingest_vocabulary(
    scheme: ConceptScheme,
    session: Any,
    *,
    author: str = "seed",
) -> dict[str, int]:
    """
    Upsert concepts from a ConceptScheme into the concepts table.

    Two-pass approach: first pass creates/updates all concepts,
    second pass wires up parent_id references.

    Returns:
        Dict with ``created``, ``updated``, and ``total`` counts.
    """
    from pycharter.db.models.wiki.concept import ConceptModel

    created = 0
    updated = 0

    concept_map: dict[str, Any] = {}

    for c in scheme.concepts:
        uri = concept_uri(c.id)
        existing = session.query(ConceptModel).filter(ConceptModel.name == c.id).first()
        if existing:
            existing.description = c.definition
            existing.uri = uri
            existing.notation = c.notation
            existing.deprecated = c.deprecated
            existing.node_kind = c.node_kind
            existing.tier = "core" if not c.broader else "domain"
            existing.updated_by = author
            concept_map[c.id] = existing
            updated += 1
        else:
            model = ConceptModel(
                name=c.id,
                description=c.definition,
                uri=uri,
                notation=c.notation,
                deprecated=c.deprecated,
                node_kind=c.node_kind,
                tier="core" if not c.broader else "domain",
                created_by=author,
            )
            session.add(model)
            session.flush()
            concept_map[c.id] = model
            created += 1

    # Second pass: wire parent references
    for c in scheme.concepts:
        if c.broader and c.broader in concept_map:
            concept_map[c.id].parent_id = concept_map[c.broader].id

    session.commit()
    return {"created": created, "updated": updated, "total": created + updated}
