"""엔티티 시각적 의존성 그래프 & 토폴로지 정렬 테스트."""

import pytest

from app.modules.entity_dependency import (
    VISUAL_REFERENCE_RULES,
    build_visual_dependency_graph,
    topological_sort_entities,
)


# ── helpers ──────────────────────────────────────────────────────────

def _ent(eid: str, etype: str) -> dict:
    return {"id": eid, "entity_type": etype, "name": eid}


def _rel(rid: str, family: str) -> dict:
    return {"id": rid, "relation_family": family}


def _part(relation_id: str, canon_id: str) -> dict:
    return {"relation_id": relation_id, "canon_id": canon_id}


# ── test: empty inputs ──────────────────────────────────────────────

def test_empty_entities_empty_batches():
    deps = build_visual_dependency_graph([], [], [])
    batches = topological_sort_entities([], deps)
    assert batches == []


# ── test: no relations → single batch with all entities ─────────────

def test_no_relations_single_batch():
    entities = [_ent("A", "character"), _ent("B", "location"), _ent("C", "prop")]
    deps = build_visual_dependency_graph(entities, [], [])
    batches = topological_sort_entities(entities, deps)

    assert len(batches) == 1
    assert set(batches[0]) == {"A", "B", "C"}


# ── test: character ↔ character identity → correct ordering ─────────

def test_character_character_identity_dependency():
    entities = [_ent("parent", "character"), _ent("child", "character")]
    relations = [_rel("r1", "identity")]
    participants = [_part("r1", "parent"), _part("r1", "child")]

    deps = build_visual_dependency_graph(entities, relations, participants)

    # "parent" listed first → "child" depends on "parent"
    assert "parent" in deps["child"]
    assert "child" not in deps["parent"]

    batches = topological_sort_entities(entities, deps)
    assert len(batches) == 2
    assert batches[0] == ["parent"]
    assert batches[1] == ["child"]


# ── test: character ↔ character transformation ──────────────────────

def test_character_character_transformation():
    entities = [_ent("young", "character"), _ent("old", "character")]
    relations = [_rel("r1", "transformation")]
    participants = [_part("r1", "young"), _part("r1", "old")]

    deps = build_visual_dependency_graph(entities, relations, participants)
    assert "young" in deps["old"]

    batches = topological_sort_entities(entities, deps)
    assert len(batches) == 2
    assert "young" in batches[0]
    assert "old" in batches[1]


# ── test: character ↔ prop possession → correct ordering ────────────

def test_character_prop_possession_dependency():
    entities = [_ent("hero", "character"), _ent("sword", "prop")]
    relations = [_rel("r1", "possession")]
    participants = [_part("r1", "hero"), _part("r1", "sword")]

    deps = build_visual_dependency_graph(entities, relations, participants)

    # "hero" listed first → "sword" depends on "hero"
    assert "hero" in deps["sword"]

    batches = topological_sort_entities(entities, deps)
    assert len(batches) == 2
    assert "hero" in batches[0]
    assert "sword" in batches[1]


# ── test: location ↔ location transformation → correct ordering ────

def test_location_location_transformation():
    entities = [_ent("base_day", "location"), _ent("base_night", "location")]
    relations = [_rel("r1", "transformation")]
    participants = [_part("r1", "base_day"), _part("r1", "base_night")]

    deps = build_visual_dependency_graph(entities, relations, participants)
    assert "base_day" in deps["base_night"]

    batches = topological_sort_entities(entities, deps)
    assert len(batches) == 2
    assert "base_day" in batches[0]
    assert "base_night" in batches[1]


# ── test: location ↔ location identity ──────────────────────────────

def test_location_location_identity():
    entities = [_ent("loc_a", "location"), _ent("loc_b", "location")]
    relations = [_rel("r1", "identity")]
    participants = [_part("r1", "loc_a"), _part("r1", "loc_b")]

    deps = build_visual_dependency_graph(entities, relations, participants)
    assert "loc_a" in deps["loc_b"]


# ── test: character ↔ location is NOT a visual dependency ───────────

def test_character_location_ignored():
    entities = [_ent("hero", "character"), _ent("home", "location")]
    relations = [_rel("r1", "containment")]
    participants = [_part("r1", "hero"), _part("r1", "home")]

    deps = build_visual_dependency_graph(entities, relations, participants)

    # containment between character and location should create NO dependency
    assert len(deps["hero"]) == 0
    assert len(deps["home"]) == 0

    batches = topological_sort_entities(entities, deps)
    assert len(batches) == 1
    assert set(batches[0]) == {"hero", "home"}


# ── test: character ↔ character with non-visual relation ignored ────

def test_non_visual_relation_family_ignored():
    entities = [_ent("hero", "character"), _ent("villain", "character")]
    # "containment" is not in VISUAL_REFERENCE_RULES for (character, character)
    relations = [_rel("r1", "containment")]
    participants = [_part("r1", "hero"), _part("r1", "villain")]

    deps = build_visual_dependency_graph(entities, relations, participants)
    assert len(deps["hero"]) == 0
    assert len(deps["villain"]) == 0


# ── test: circular dependency handled ───────────────────────────────

def test_circular_dependency_handled():
    """Two entities that depend on each other should not cause infinite loop."""
    entities = [_ent("A", "character"), _ent("B", "character")]

    # Manually create circular deps
    deps = {"A": {"B"}, "B": {"A"}}

    batches = topological_sort_entities(entities, deps)

    # Should produce at least one batch with all remaining entities
    all_ids = set()
    for batch in batches:
        all_ids.update(batch)
    assert all_ids == {"A", "B"}


# ── test: topological sort produces valid batches ───────────────────

def test_topological_sort_valid_ordering():
    """Complex dependency chain: A → B → C, A → D (independent of B/C)."""
    entities = [_ent("A", "character"), _ent("B", "character"),
                _ent("C", "character"), _ent("D", "prop")]

    # A depends on nothing; B depends on A; C depends on B; D depends on A
    deps = {
        "A": set(),
        "B": {"A"},
        "C": {"B"},
        "D": {"A"},
    }

    batches = topological_sort_entities(entities, deps)

    # Validate ordering: each entity should appear after all its deps
    position = {}
    for batch_idx, batch in enumerate(batches):
        for eid in batch:
            position[eid] = batch_idx

    assert position["A"] < position["B"]
    assert position["B"] < position["C"]
    assert position["A"] < position["D"]

    # B and D can be in the same batch (both only depend on A)
    assert position["B"] == position["D"]


# ── test: single-participant relations are skipped ──────────────────

def test_single_participant_relation_skipped():
    entities = [_ent("solo", "character")]
    relations = [_rel("r1", "identity")]
    participants = [_part("r1", "solo")]  # only 1 participant

    deps = build_visual_dependency_graph(entities, relations, participants)
    assert len(deps["solo"]) == 0


# ── test: participants referencing unknown entities are skipped ──────

def test_unknown_entity_in_participant_skipped():
    entities = [_ent("known", "character")]
    relations = [_rel("r1", "identity")]
    participants = [_part("r1", "known"), _part("r1", "ghost")]

    deps = build_visual_dependency_graph(entities, relations, participants)
    assert len(deps["known"]) == 0


# ── test: multiple independent pairs → single batch ─────────────────

def test_multiple_independent_entities_same_batch():
    entities = [
        _ent("char1", "character"),
        _ent("char2", "character"),
        _ent("loc1", "location"),
        _ent("prop1", "prop"),
    ]
    # No relations at all
    deps = build_visual_dependency_graph(entities, [], [])
    batches = topological_sort_entities(entities, deps)

    assert len(batches) == 1
    assert set(batches[0]) == {"char1", "char2", "loc1", "prop1"}


# ── test: VISUAL_REFERENCE_RULES completeness ──────────────────────

def test_visual_reference_rules_structure():
    """Verify the rules dict has the expected keys and values."""
    assert ("character", "character") in VISUAL_REFERENCE_RULES
    assert ("character", "prop") in VISUAL_REFERENCE_RULES
    assert ("prop", "character") in VISUAL_REFERENCE_RULES
    assert ("location", "location") in VISUAL_REFERENCE_RULES

    # character ↔ location should NOT be in the rules
    assert ("character", "location") not in VISUAL_REFERENCE_RULES
    assert ("location", "character") not in VISUAL_REFERENCE_RULES
