"""entity_review_v4 모듈 + EntityReviewStep 테스트."""
import json
from unittest.mock import MagicMock, patch


import pytest
# ── review_entities 단위 테스트 ──


def test_review_entities_calls_call_structured_with_step():
    """call_structured가 step='entity_review'로 호출되는지 검증."""
    from app.modules.pipeline.entity_reviewer import review_entities

    with patch("app.modules.pipeline.entity_reviewer.call_structured") as mock_cs:
        mock_cs.return_value = {
            "missing_entities": [],
            "unnecessary_entities": [],
            "overall_assessment": "양호",
        }

        entities = {
            "characters": [{"name": "A", "scene_appearances": ["S1", "S2"]}],
            "locations": [{"name": "B", "scene_appearances": ["S3"]}],
            "props": [],
        }
        result = review_entities(entities, "시나리오 전문 텍스트")

        mock_cs.assert_called_once()
        _, kwargs = mock_cs.call_args
        assert kwargs["step"] == "entity_review"
        assert kwargs["schema_name"] == "entity_review"
        assert result["overall_assessment"] == "양호"


def test_review_entities_loads_external_prompt():
    """외부 프롬프트 파일(system.md, review_schema.json)을 로드하는지 검증."""
    from app.modules.pipeline.entity_reviewer import review_entities

    with patch("app.modules.pipeline.entity_reviewer.call_structured") as mock_cs, \
         patch("app.modules.pipeline.entity_reviewer.load_prompt") as mock_lp, \
         patch("app.modules.pipeline.entity_reviewer.load_schema") as mock_ls:

        mock_lp.return_value = "system prompt text"
        mock_ls.return_value = {"type": "object", "properties": {}}
        mock_cs.return_value = {
            "missing_entities": [],
            "unnecessary_entities": [],
            "overall_assessment": "ok",
        }

        review_entities(
            {"characters": [], "locations": [], "props": []},
            "전문",
        )

        mock_lp.assert_called_once_with("entity_review_v4", "system")
        mock_ls.assert_called_once_with("entity_review_v4", "review_schema")


def test_review_entities_formats_entities():
    """엔티티 목록이 user_prompt에 올바르게 포맷되는지 검증."""
    from app.modules.pipeline.entity_reviewer import review_entities

    with patch("app.modules.pipeline.entity_reviewer.call_structured") as mock_cs, \
         patch("app.modules.pipeline.entity_reviewer.load_prompt") as mock_lp, \
         patch("app.modules.pipeline.entity_reviewer.load_schema") as mock_ls:

        mock_lp.return_value = "sys"
        mock_ls.return_value = {"type": "object"}
        mock_cs.return_value = {
            "missing_entities": [],
            "unnecessary_entities": [],
            "overall_assessment": "ok",
        }

        entities = {
            "characters": [
                {"name": "영수", "scene_appearances": ["S1", "S2", "S3"]},
                {"name": "민지", "scene_appearances": ["S1"]},
            ],
            "locations": [],
            "props": [],
        }
        review_entities(entities, "전문 텍스트")

        _, kwargs = mock_cs.call_args
        user_prompt = kwargs["user_prompt"]
        assert "영수(3씬)" in user_prompt
        assert "민지(1씬)" in user_prompt


# ── EntityReviewStep 통합 테스트 ──


@pytest.mark.skip(reason="W3-3 cluster B drift — v3/v2 계약이 현재(v4/shot-more) 구현과 어긋남. docs/review-codex-1/11-fix-plan.md §8.2 참조. 복원/재작성은 Wave 5 이후 재평가.")
def test_entity_review_step_filters_unnecessary():
    """EntityReviewStep이 불필요 항목을 필터링하는지 검증."""
    import tempfile
    import os

    tmp = tempfile.mkdtemp()
    ep_id = "ep-test-001"
    proj_id = "proj-test-001"

    prev_data = {
        "characters": [
            {"name": "영수", "scene_appearances": ["S1", "S2"]},
            {"name": "행인1", "scene_appearances": ["S3"]},
        ],
        "locations": [
            {"name": "학교", "scene_appearances": ["S1", "S2"]},
        ],
        "props": [
            {"name": "가방", "scene_appearances": ["S1"]},
            {"name": "연필", "scene_appearances": ["S2"]},
        ],
    }

    # entity_extract 체크포인트 생성
    cp_dir = os.path.join(
        tmp, proj_id, "checkpoints", "episodes", ep_id, "entity_extract"
    )
    os.makedirs(cp_dir, exist_ok=True)
    manifest = {"data": prev_data, "status": "completed"}
    with open(os.path.join(cp_dir, "manifest.json"), "w") as f:
        json.dump(manifest, f)

    mock_review_result = {
        "missing_entities": [],
        "unnecessary_entities": [
            {"name": "행인1", "reason": "1회성 등장"},
            {"name": "연필", "reason": "일반적 소품"},
        ],
        "overall_assessment": "대체로 양호하나 일부 불필요 항목 존재",
    }

    with patch("app.core.steps.entity_steps.StepRunner.__init__", return_value=None), \
         patch("app.core.config.settings") as mock_settings, \
         patch("app.modules.pipeline.entity_reviewer.review_entities", return_value=mock_review_result):

        mock_settings.projects_dir = tmp

        from app.core.steps.entity_steps import EntityReviewStep

        step = EntityReviewStep.__new__(EntityReviewStep)
        step.step_id = "entity_review"
        step.project_id = proj_id
        step.episode_id = ep_id
        step.db = MagicMock()
        step.project_config = {}
        step.opik_context = {}
        step.run_id = "test-run"
        step._cp_dir = os.path.join(
            tmp, proj_id, "checkpoints", "episodes", ep_id, "entity_review"
        )

        # _load_cleaned_text를 직접 패치하여 DB 임포트 회피
        step._load_cleaned_text = lambda: "시나리오 전문 텍스트"
        step.build_opik_metadata = lambda *a, **kw: {}

        result = step._execute()

    data = result["data"]
    char_names = [e["name"] for e in data["characters"]]
    prop_names = [e["name"] for e in data["props"]]

    # 행인1은 필터링됨
    assert "영수" in char_names
    assert "행인1" not in char_names
    # 연필은 필터링됨
    assert "가방" in prop_names
    assert "연필" not in prop_names
    # 학교는 유지
    assert len(data["locations"]) == 1
    assert data["locations"][0]["name"] == "학교"
    # 리뷰 결과 포함
    assert "review" in data
    assert data["review"]["overall_assessment"] == "대체로 양호하나 일부 불필요 항목 존재"
