"""prompt_loader 흡수 (problems.md #14) — 4 caller 의 직접 file read 제거 가드.

본 회귀 테스트는 다음을 검증한다.

1. **prompt_sanitizer / image_validator / pdf_validator / prompt_service** 가
   ``app.modules.prompt_loader.load_prompt`` 를 호출 경로로 사용한다 (직접
   ``Path.read_text`` / hardcoded ``v1`` 경로 사용 금지).
2. ``_load_prompt(filename)`` 호환 시그니처는 보존되어 caller 변경 0.
3. ``.md`` 확장자 자동 stripping — caller 의 기존 ``"sanitize_system.md"`` 같은
   형태가 그대로 동작.
4. 4 caller 가 prompt_loader 를 통해 DB 우선 + numeric version 정렬 + version
   pack drift detection (#6) + local jsonschema validation (#13) 의 통합 정책을
   상속.
"""
from __future__ import annotations

from unittest.mock import patch

import pytest


# ---------------------------------------------------------------------------
# 1. 직접 file read 패턴 제거 회귀 가드 (정적 검사)
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
    "module_path,banned_patterns",
    [
        # prompt_sanitizer.py
        (
            "app/modules/prompt_sanitizer.py",
            ["PROMPTS_DIR", "(PROMPTS_DIR / filename)", '"v1"'],
        ),
        # image_validator.py
        (
            "app/modules/image_validator.py",
            ["PROMPTS_DIR", "(PROMPTS_DIR / filename)", '"v1"'],
        ),
        # pdf_validator.py
        (
            "app/modules/pdf_validator.py",
            ["PROMPTS_DIR", "(PROMPTS_DIR / filename)", '"v1"'],
        ),
    ],
)
def test_no_hardcoded_v1_path_in_prompt_callers(module_path, banned_patterns):
    """4 caller 가 ``v1`` hardcoded / PROMPTS_DIR / 직접 read 패턴을 더 이상 갖지 않음.

    회귀 가드 — prompt_loader 흡수가 풀려 다시 직접 read 로 돌아가면 catch.
    """
    from pathlib import Path

    backend_root = Path(__file__).resolve().parent.parent.parent
    src = (backend_root / module_path).read_text(encoding="utf-8")
    for pat in banned_patterns:
        assert pat not in src, (
            f"{module_path} 에 hardcoded prompt-path 패턴 '{pat}' 잔존 — "
            "prompt_loader.load_prompt 로 흡수되어야 함 (problems.md #14)"
        )


def test_prompt_service_translate_uses_load_prompt():
    """``prompt_service.translate_if_korean`` 가 load_prompt 사용."""
    from pathlib import Path

    backend_root = Path(__file__).resolve().parent.parent.parent
    src = (backend_root / "app/services/prompt_service.py").read_text(encoding="utf-8")
    assert "load_prompt" in src
    # 직접 디렉토리 iterate + read 패턴 잔존하지 않음.
    assert "[d.name for d in prompt_dir.iterdir()" not in src
    assert "template_path.read_text" not in src


# ---------------------------------------------------------------------------
# 2. _load_prompt 시그니처 호환 + .md 확장자 strip
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
    "module_attr,expected_module,expected_stem,filename",
    [
        ("app.modules.prompt_sanitizer", "prompt_sanitizer", "sanitize_system", "sanitize_system.md"),
        ("app.modules.prompt_sanitizer", "prompt_sanitizer", "sanitize_user", "sanitize_user.md"),
        ("app.modules.image_validator", "image_validation", "reference_validation", "reference_validation.md"),
        ("app.modules.image_validator", "image_validation", "scene_validation", "scene_validation.md"),
        ("app.modules.image_validator", "image_validation", "pdf_validation", "pdf_validation.md"),
        ("app.modules.pdf_validator", "pdf_validation", "validate", "validate.md"),
    ],
)
def test_load_prompt_dispatches_to_prompt_loader(
    module_attr, expected_module, expected_stem, filename,
):
    """``_load_prompt`` 가 load_prompt(module=, stem=) 으로 dispatch."""
    import importlib

    target_module = importlib.import_module(module_attr)
    with patch("app.modules.prompt_loader.load_prompt") as mocked:
        mocked.return_value = "stub content"
        result = target_module._load_prompt(filename)
    assert result == "stub content"
    mocked.assert_called_once_with(expected_module, expected_stem)


def test_load_prompt_preserves_no_extension_argument():
    """확장자 없는 인자도 그대로 dispatch — defensive double-strip 회피."""
    from app.modules import prompt_sanitizer

    with patch("app.modules.prompt_loader.load_prompt") as mocked:
        mocked.return_value = "ok"
        prompt_sanitizer._load_prompt("sanitize_system")
    mocked.assert_called_once_with("prompt_sanitizer", "sanitize_system")


# ---------------------------------------------------------------------------
# 3. translate_prompt 흡수 검증
# ---------------------------------------------------------------------------


def test_translate_uses_load_prompt_with_format_kwargs():
    """prompt_service.translate_if_korean 의 format kwargs 통과 확인."""
    from app.services import prompt_service

    captured = {}

    def fake_load_prompt(module, name, **kwargs):
        captured["module"] = module
        captured["name"] = name
        captured["kwargs"] = kwargs
        return f"M={module} N={name} ROLES={kwargs.get('ref_roles_text')}"

    # call_text 도 mock — 실제 LLM 호출 차단.
    with patch("app.modules.prompt_loader.load_prompt", side_effect=fake_load_prompt), \
         patch("app.modules.llm.llm_client.call_text") as mock_call_text:
        mock_call_text.return_value = "translated 영문만"  # contains no Korean → returned as-is

        # 한국어 포함 — translate path 진입 trigger.
        result = prompt_service.translate_if_korean(
            cleaned="씬 한국어",
            ref_roles_text="ROLE-A",
            ref_instructions_text="INST-B",
            style_context="STYLE-C",
        )

    # load_prompt 가 정확한 module/stem + 4 kwargs 전달.
    assert captured["module"] == "scene_image"
    assert captured["name"] == "translate_prompt"
    assert captured["kwargs"]["ref_roles_text"] == "ROLE-A"
    assert captured["kwargs"]["ref_instructions"] == "INST-B"
    assert captured["kwargs"]["style_context"] == "STYLE-C"
    assert captured["kwargs"]["t2i_prompt"] == "씬 한국어"
    # call_text 호출됨 (translation flow 진입).
    assert mock_call_text.called


def test_translate_skips_when_template_missing():
    """load_prompt 가 FileNotFoundError 면 원본 반환 + 에러 로그."""
    from app.services import prompt_service

    with patch(
        "app.modules.prompt_loader.load_prompt",
        side_effect=FileNotFoundError("no template"),
    ):
        result = prompt_service.translate_if_korean(
            cleaned="한국어",
            ref_roles_text="x",
            ref_instructions_text="y",
            style_context="z",
        )
    assert result == "한국어"  # 원본 보존


def test_translate_short_circuits_for_english_only():
    """한국어 없으면 load_prompt 호출 자체 발생 안 함 (regression — 기존 동작 보존)."""
    from app.services import prompt_service

    with patch("app.modules.prompt_loader.load_prompt") as mocked:
        result = prompt_service.translate_if_korean(
            cleaned="english only",
            ref_roles_text="x",
            ref_instructions_text="y",
            style_context="z",
        )
    assert result == "english only"
    mocked.assert_not_called()


# ---------------------------------------------------------------------------
# 4. effective source 통합 — 4 module 도 get_effective_source 적용 가능
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
    "module,stem",
    [
        ("prompt_sanitizer", "sanitize_system"),
        ("image_validation", "reference_validation"),
        ("pdf_validation", "validate"),
        ("scene_image", "translate_prompt"),
    ],
)
def test_effective_source_covers_absorbed_modules(module, stem):
    """get_effective_source 가 4 흡수 module 의 file 후보를 정확히 보고."""
    from app.modules.prompt_loader import get_effective_source

    out = get_effective_source(module, stem, db=None)
    assert out["module"] == module
    assert out["name"] == stem
    # file 후보 존재 (실 prompts 디렉토리 기준).
    assert out["candidates"]["file"] is not None, (
        f"{module}/{stem} 의 file 후보가 비어있음 — prompts/_base/{module}/<v>/{stem}.md "
        "또는 prompt_loader 흡수 누락"
    )
    # module_pack 정보 노출.
    assert "module_pack" in out
    assert out["module_pack"]["latest"] is not None
