"""Patch B-min — PromptSanitizer 2-layer defense 단위 테스트.

Layer 1: user prompt 의 SEMANTIC CONSTRAINTS 섹션 (sanitize LLM 지시).
Layer 2: final sanitized_prompt 의 SEMANTIC OVERRIDE block (T2I 모델 지시).

시나리오 의존 0: synthetic identifier (C91 / dead) 만 사용.
"""
from app.modules.prompt_sanitizer import PromptSanitizer


def test_sanitize_two_layer_defense(monkeypatch):
    """user prompt + final prompt 둘 다 검증 + 중복 append 차단."""
    captured_kwargs: dict = {}

    def fake_call_structured(**kwargs):
        captured_kwargs.update(kwargs)
        return {
            "sanitized_prompt": "safe rewritten prompt body",
            "changes": "removed unsafe terms",
            "strategy": "film_previs",
        }

    monkeypatch.setattr(
        "app.modules.llm.llm_client.call_structured",
        fake_call_structured,
    )

    sanitizer = PromptSanitizer()
    constraints = {
        "semantic_mode": "immobilized",
        "entity_ids": ["C91"],
        "source_states": {"C91": "dead"},
        "preserve_pose": True,
        "preserve_subject_state": True,
        "forbid_state_polarity_rewrite": True,
        "forbid_unharmed_rewrite": True,
        "forbid_active_reaction": True,
        "override_strategy_prefix": True,
        "evidence": [],
    }

    result = sanitizer.sanitize(
        original_prompt="unsafe original",
        block_reason="SAFETY",
        block_categories=["violence"],
        attempt=1,
        semantic_constraints=constraints,
    )

    # Layer 1 — user prompt 에 SEMANTIC CONSTRAINTS 섹션 + entity/state line
    user_prompt = captured_kwargs["user_prompt"]
    assert "SEMANTIC CONSTRAINTS" in user_prompt
    assert "C91" in user_prompt
    assert "dead" in user_prompt
    assert "forbid_state_polarity_rewrite: True" in user_prompt

    # Layer 2 — final sanitized_prompt 에 SEMANTIC OVERRIDE block append
    sanitized = result["sanitized_prompt"]
    assert "SEMANTIC OVERRIDE:" in sanitized
    assert "C91 (dead):" in sanitized

    # block 위치 = strategy prefix 뒤 (final position)
    prefix_marker = "pre-visualization concept art"               # film_previs prefix substring
    assert sanitized.index(prefix_marker) < sanitized.index("SEMANTIC OVERRIDE:")

    # 중복 append 차단 검증 (retry 입력 시뮬레이션)
    result2 = sanitizer.sanitize(
        original_prompt=sanitized,                                # 이전 retry output 을 다음 입력으로
        block_reason="SAFETY",
        block_categories=["violence"],
        attempt=2,
        semantic_constraints=constraints,
    )
    assert result2["sanitized_prompt"].count("SEMANTIC OVERRIDE:") == 1


def test_sanitize_no_constraints_unchanged():
    """semantic_constraints=None → 기존 동작 보존 (override block 미append)."""
    from app.modules.prompt_sanitizer import _apply_semantic_override
    sample = "any prompt body"
    assert _apply_semantic_override(sample, None) == sample
    assert _apply_semantic_override(sample, {"override_strategy_prefix": False}) == sample


def test_apply_semantic_override_dedups_existing_marker():
    """Retry 입력 prompt 에 이미 OVERRIDE block 존재 시 중복 append 차단.

    scene_image_pipeline.py:261 의 retry 루프 (`current_prompt =
    sanitize_result.get("sanitized_prompt", current_prompt)`) 가 다음
    sanitize() 의 input 으로 동일 prompt (block 포함) 를 줄 때 — 새 retry
    가 두 번째 block 을 append 하면 안 됨.
    """
    from app.modules.prompt_sanitizer import _apply_semantic_override
    constraints = {
        "semantic_mode": "immobilized",
        "entity_ids": ["C91"],
        "source_states": {"C91": "dead"},
        "override_strategy_prefix": True,
    }
    once = _apply_semantic_override("body text", constraints)
    twice = _apply_semantic_override(once, constraints)
    assert twice.count("SEMANTIC OVERRIDE:") == 1
    assert twice.startswith("body text")


def test_pose_locked_layer1_omits_false_forbid_flags():
    """IMPORTANT 2 fix — pose_locked mode 의 user prompt 에 forbid_*: False 가
    출력되면 Layer 2 의 _POSE_LOCKED_BLOCK ('active reaction 금지') 와 충돌해
    LLM 에 mixed signal. False flag 는 user prompt 에서 제외 검증.
    """
    from app.modules.prompt_sanitizer import _render_semantic_constraints_block
    constraints = {
        "semantic_mode": "pose_locked",
        "entity_ids": ["C92"],
        "source_states": {},
        "preserve_pose": True,
        "preserve_subject_state": False,
        "forbid_state_polarity_rewrite": False,
        "forbid_unharmed_rewrite": False,
        "forbid_active_reaction": False,
        "override_strategy_prefix": True,
        "evidence": [],
    }
    rendered = _render_semantic_constraints_block(constraints)
    # preserve_pose=True 는 노출, False flags 는 모두 제외.
    assert "preserve_pose: True" in rendered
    assert "preserve_subject_state" not in rendered
    assert "forbid_state_polarity_rewrite" not in rendered
    assert "forbid_unharmed_rewrite" not in rendered
    assert "forbid_active_reaction" not in rendered
    # semantic_mode 는 항상 노출.
    assert "semantic_mode: pose_locked" in rendered


def test_immobilized_layer1_includes_all_true_forbid_flags():
    """immobilized mode (모든 forbid_* True) — 변경 후에도 5 flags 모두 노출."""
    from app.modules.prompt_sanitizer import _render_semantic_constraints_block
    constraints = {
        "semantic_mode": "immobilized",
        "entity_ids": ["C91"],
        "source_states": {"C91": "dead"},
        "preserve_pose": True,
        "preserve_subject_state": True,
        "forbid_state_polarity_rewrite": True,
        "forbid_unharmed_rewrite": True,
        "forbid_active_reaction": True,
        "override_strategy_prefix": True,
        "evidence": [],
    }
    rendered = _render_semantic_constraints_block(constraints)
    for key in (
        "preserve_pose", "preserve_subject_state",
        "forbid_state_polarity_rewrite", "forbid_unharmed_rewrite",
        "forbid_active_reaction",
    ):
        assert f"{key}: True" in rendered, f"missing {key} in immobilized layer1"
