"""Tests for knowledge ontology models -- NodeKind, RelationshipType, INVERSE_MAP."""

from __future__ import annotations

import pytest

from pycharter.wiki.ontology.models import (
    INVERSE_MAP,
    ConceptDefinition,
    NodeKind,
    RelationshipType,
    resolve_inverse,
)


class TestNodeKind:
    """Validate NodeKind enum values and membership."""

    EXPECTED_KINDS = {
        "entity",
        "process",
        "event",
        "attribute",
        "rule",
        "system",
        "concept",
        "metric",
    }

    def test_has_all_expected_members(self):
        actual = {k.value for k in NodeKind}
        assert actual == self.EXPECTED_KINDS

    def test_member_count(self):
        assert len(NodeKind) == 8

    @pytest.mark.parametrize("kind", ["entity", "process", "event", "metric"])
    def test_string_coercion(self, kind: str):
        nk = NodeKind(kind)
        assert nk.value == kind
        assert nk == kind


class TestRelationshipType:
    """Validate expanded RelationshipType enum."""

    ORIGINAL_TYPES = {"references", "derived_from", "same_as", "related_to"}

    KNOWLEDGE_TYPES = {
        "is_a",
        "has",
        "depends_on",
        "precedes",
        "causes",
        "implements",
        "governs",
        "describes",
        "equivalent_to",
    }

    def test_has_all_original_types(self):
        actual = {r.value for r in RelationshipType}
        assert self.ORIGINAL_TYPES.issubset(actual)

    def test_has_all_knowledge_types(self):
        actual = {r.value for r in RelationshipType}
        assert self.KNOWLEDGE_TYPES.issubset(actual)

    def test_member_count(self):
        assert len(RelationshipType) == 13

    @pytest.mark.parametrize(
        "value",
        ["references", "derived_from", "is_a", "depends_on", "causes", "equivalent_to"],
    )
    def test_value_access(self, value: str):
        rt = RelationshipType(value)
        assert rt.value == value
        assert rt == value


class TestInverseMap:
    """Validate INVERSE_MAP completeness and correctness."""

    def test_every_relationship_type_has_inverse(self):
        for rt in RelationshipType:
            assert rt.value in INVERSE_MAP, f"Missing inverse for {rt.value}"

    def test_symmetric_types_map_to_self(self):
        symmetric = {"equivalent_to", "related_to", "same_as"}
        for name in symmetric:
            assert INVERSE_MAP[name] == name or INVERSE_MAP[name] == name

    def test_non_symmetric_types_have_different_inverse(self):
        non_symmetric = {
            "is_a",
            "has",
            "depends_on",
            "derived_from",
            "precedes",
            "causes",
            "implements",
            "governs",
            "describes",
            "references",
        }
        for name in non_symmetric:
            assert INVERSE_MAP[name] != name, f"{name} should not be its own inverse"

    @pytest.mark.parametrize(
        "fwd, expected_inv",
        [
            ("is_a", "has_subtype"),
            ("has", "part_of"),
            ("depends_on", "depended_on_by"),
            ("derived_from", "derives"),
            ("precedes", "follows"),
            ("causes", "caused_by"),
            ("implements", "implemented_by"),
            ("governs", "governed_by"),
            ("describes", "described_by"),
            ("references", "referenced_by"),
        ],
    )
    def test_specific_inverses(self, fwd: str, expected_inv: str):
        assert INVERSE_MAP[fwd] == expected_inv


class TestResolveInverse:
    """Validate resolve_inverse() function."""

    def test_known_type(self):
        assert resolve_inverse("is_a") == "has_subtype"

    def test_symmetric_type(self):
        assert resolve_inverse("equivalent_to") == "equivalent_to"

    def test_unknown_type_returns_itself(self):
        assert resolve_inverse("unknown_type") == "unknown_type"


class TestConceptDefinitionWithNodeKind:
    """Validate ConceptDefinition node_kind field."""

    def test_default_is_none(self):
        cd = ConceptDefinition(id="Foo", label="Foo")
        assert cd.node_kind is None

    def test_set_node_kind(self):
        cd = ConceptDefinition(id="Foo", label="Foo", node_kind="entity")
        assert cd.node_kind == "entity"

    def test_to_dict_includes_node_kind(self):
        cd = ConceptDefinition(id="Foo", label="Foo", node_kind="metric")
        d = cd.to_dict()
        assert d["node_kind"] == "metric"

    def test_to_dict_omits_node_kind_when_none(self):
        cd = ConceptDefinition(id="Foo", label="Foo")
        d = cd.to_dict()
        assert "node_kind" not in d
