"""Tests for the relation registry (Phase A)."""

import pytest

from shared.sx.parser import parse
from shared.sx.relations import (
    RelationError,
    _RELATION_REGISTRY,
    clear_registry,
    evaluate_defrelation,
    get_relation,
    load_relation_registry,
    relations_from,
    relations_to,
    all_relations,
)
from shared.sx.types import RelationDef


@pytest.fixture(autouse=True)
def _clean_registry():
    """Clear registry before each test."""
    clear_registry()


# ---------------------------------------------------------------------------
# defrelation parsing
# ---------------------------------------------------------------------------

class TestDefrelation:
    def test_basic_defrelation(self):
        tree = parse('''
            (defrelation :page->market
              :from "page"
              :to "market"
              :cardinality :one-to-many
              :inverse :market->page
              :nav :submenu
              :nav-icon "fa fa-shopping-bag"
              :nav-label "markets")
        ''')
        result = evaluate_defrelation(tree)
        assert isinstance(result, RelationDef)
        assert result.name == "page->market"
        assert result.from_type == "page"
        assert result.to_type == "market"
        assert result.cardinality == "one-to-many"
        assert result.inverse == "market->page"
        assert result.nav == "submenu"
        assert result.nav_icon == "fa fa-shopping-bag"
        assert result.nav_label == "markets"

    def test_defrelation_registered(self):
        tree = parse('''
            (defrelation :a->b
              :from "a" :to "b" :cardinality :one-to-one :nav :hidden)
        ''')
        evaluate_defrelation(tree)
        assert get_relation("a->b") is not None
        assert get_relation("a->b").cardinality == "one-to-one"

    def test_defrelation_one_to_one(self):
        tree = parse('''
            (defrelation :page->menu_node
              :from "page" :to "menu_node"
              :cardinality :one-to-one :nav :hidden)
        ''')
        result = evaluate_defrelation(tree)
        assert result.cardinality == "one-to-one"
        assert result.inverse is None
        assert result.nav == "hidden"

    def test_defrelation_many_to_many(self):
        tree = parse('''
            (defrelation :post->entry
              :from "post" :to "calendar_entry"
              :cardinality :many-to-many
              :inverse :entry->post
              :nav :inline
              :nav-icon "fa fa-file-alt"
              :nav-label "events")
        ''')
        result = evaluate_defrelation(tree)
        assert result.cardinality == "many-to-many"

    def test_default_nav_is_hidden(self):
        tree = parse('''
            (defrelation :x->y
              :from "x" :to "y" :cardinality :one-to-many)
        ''')
        result = evaluate_defrelation(tree)
        assert result.nav == "hidden"

    def test_invalid_cardinality_raises(self):
        tree = parse('''
            (defrelation :bad
              :from "a" :to "b" :cardinality :wrong)
        ''')
        with pytest.raises(RelationError, match="invalid cardinality"):
            evaluate_defrelation(tree)

    def test_invalid_nav_raises(self):
        tree = parse('''
            (defrelation :bad
              :from "a" :to "b" :cardinality :one-to-one :nav :bogus)
        ''')
        with pytest.raises(RelationError, match="invalid nav"):
            evaluate_defrelation(tree)

    def test_missing_from_raises(self):
        tree = parse('''
            (defrelation :bad :to "b" :cardinality :one-to-one)
        ''')
        with pytest.raises(RelationError, match="missing required :from"):
            evaluate_defrelation(tree)

    def test_missing_to_raises(self):
        tree = parse('''
            (defrelation :bad :from "a" :cardinality :one-to-one)
        ''')
        with pytest.raises(RelationError, match="missing required :to"):
            evaluate_defrelation(tree)

    def test_missing_cardinality_raises(self):
        tree = parse('''
            (defrelation :bad :from "a" :to "b")
        ''')
        with pytest.raises(RelationError, match="missing required :cardinality"):
            evaluate_defrelation(tree)

    def test_name_must_be_keyword(self):
        tree = parse('(defrelation "not-keyword" :from "a" :to "b" :cardinality :one-to-one)')
        with pytest.raises(RelationError, match="must be a keyword"):
            evaluate_defrelation(tree)


# ---------------------------------------------------------------------------
# Registry queries
# ---------------------------------------------------------------------------

class TestRegistry:
    def _load_sample(self):
        tree = parse('''
            (begin
              (defrelation :page->market
                :from "page" :to "market" :cardinality :one-to-many
                :nav :submenu :nav-icon "fa fa-shopping-bag" :nav-label "markets")
              (defrelation :page->calendar
                :from "page" :to "calendar" :cardinality :one-to-many
                :nav :submenu :nav-icon "fa fa-calendar" :nav-label "calendars")
              (defrelation :post->entry
                :from "post" :to "calendar_entry" :cardinality :many-to-many
                :nav :inline)
              (defrelation :page->menu_node
                :from "page" :to "menu_node" :cardinality :one-to-one
                :nav :hidden))
        ''')
        evaluate_defrelation(tree)

    def test_get_relation(self):
        self._load_sample()
        rel = get_relation("page->market")
        assert rel is not None
        assert rel.to_type == "market"

    def test_get_relation_not_found(self):
        assert get_relation("nonexistent") is None

    def test_relations_from_page(self):
        self._load_sample()
        rels = relations_from("page")
        names = {r.name for r in rels}
        assert names == {"page->market", "page->calendar", "page->menu_node"}

    def test_relations_from_post(self):
        self._load_sample()
        rels = relations_from("post")
        assert len(rels) == 1
        assert rels[0].name == "post->entry"

    def test_relations_from_empty(self):
        self._load_sample()
        assert relations_from("nonexistent") == []

    def test_relations_to_market(self):
        self._load_sample()
        rels = relations_to("market")
        assert len(rels) == 1
        assert rels[0].name == "page->market"

    def test_relations_to_calendar_entry(self):
        self._load_sample()
        rels = relations_to("calendar_entry")
        assert len(rels) == 1
        assert rels[0].name == "post->entry"

    def test_all_relations(self):
        self._load_sample()
        assert len(all_relations()) == 4

    def test_clear_registry(self):
        self._load_sample()
        assert len(all_relations()) == 4
        clear_registry()
        assert len(all_relations()) == 0


# ---------------------------------------------------------------------------
# load_relation_registry() — built-in definitions
# ---------------------------------------------------------------------------

class TestLoadBuiltins:
    def test_loads_builtin_relations(self):
        load_relation_registry()
        assert get_relation("page->market") is not None
        assert get_relation("page->calendar") is not None
        assert get_relation("post->calendar_entry") is not None
        assert get_relation("page->menu_node") is not None

    def test_builtin_page_market(self):
        load_relation_registry()
        rel = get_relation("page->market")
        assert rel.from_type == "page"
        assert rel.to_type == "market"
        assert rel.cardinality == "one-to-many"
        assert rel.inverse == "market->page"
        assert rel.nav == "submenu"
        assert rel.nav_icon == "fa fa-shopping-bag"

    def test_builtin_post_entry(self):
        load_relation_registry()
        rel = get_relation("post->calendar_entry")
        assert rel.cardinality == "many-to-many"
        assert rel.nav == "inline"

    def test_builtin_page_menu_node(self):
        load_relation_registry()
        rel = get_relation("page->menu_node")
        assert rel.cardinality == "one-to-one"
        assert rel.nav == "hidden"

    def test_frozen_dataclass(self):
        load_relation_registry()
        rel = get_relation("page->market")
        with pytest.raises(AttributeError):
            rel.name = "changed"
