"""인물의 **기본 신원 참조**를 고를 때 파생본(합성·의상·상태 변형)을 거른다.

실측(컨트리로드 2판 2026-09-20 사용자 지적): 찰리는 대표 참조가 4개였다 —
기본 1장 + 옷 합성 3장. 합성도 `asset_type='reference'` 에 `is_primary=1` 이라
(합성은 합성끼리 대표를 다툰다 — `auto_set_primary`), 기본을 고르는 조회
`order_by(created_at.desc()).first()` 가 **코트 합성본**을 기본으로 집었다.
그래서 의상 배정이 `O00`(옷 없음)인 씬에서 찰리가 코트를 입고 나왔고,
우비 씬에도 코트가 붙었다. 사용자 육안 지적 여러 건의 공통 뿌리다.

이 시험이 잠그는 것:
1. **규칙** — 무엇이 기본이고 무엇이 파생인가 (순수 함수)
2. **SQL 문안** — 조건이 그 표식들을 실제로 이름 댄다
3. **배선** — 기본을 고르는 **세 자리**가 그 조건을 정말 건다

★한계: 3은 조건이 조회에 걸렸다는 것까지만 본다. 조건을 **집행**하는 것은
 PostgreSQL 이고, 여기 가짜 DB 는 SQL 을 실행하지 않는다.
"""
from __future__ import annotations

from unittest.mock import MagicMock

import pytest

PID = "SAMPLE_P"
C1 = "c1c1c1c1-0000-4000-8000-000000000001"
O1 = "0e0e0e0e-0000-4000-8000-000000000002"


# ── 1. 규칙 ────────────────────────────────────────────────────────────────

@pytest.mark.parametrize("prompt", [
    None, "", "Full body reference of the character",
    "Set in a 2069 post-apocalyptic era. Photorealistic product photo",
    "CRITICAL — This is a CORRECTION of the attached reference image",
])
def test_a_plain_reference_is_the_base_identity(prompt):
    from app.services.scene_reference_service import is_base_identity_ref
    assert is_base_identity_ref(prompt) is True


@pytest.mark.parametrize("prompt", [
    f"[composite:{C1}:{O1}] 찰리+변장용외투와모자",
    f"[composite:{C1}:{O1}:base-uuid] 찰리+우비",
    f"[composite_old:{C1}:{O1}] 물러난 판",
    f"[composite_old2:{C1}:{O1}] 물러난 판",
    f"[outfit:{O1}] 의상 단독",
    f"[outlook_id:{O1}] 옛 표기",
    f"[state_variant:{C1}:dead] 찰리",
    f"[state_variant_old:{C1}:unconscious] 찰리",
    f"[state_variant_oldsrc:{C1}:dead] 찰리",
    f"[state_variant_retry:{C1}:dead] 찰리",
])
def test_every_derived_marker_is_not_the_base_identity(prompt):
    """★물러난 판(`_old`·`_oldsrc`·`_retry`)도 같은 어간이라 함께 걸린다 —
    표식을 하나씩 열거하면 새 접미사가 생길 때 조용히 새어 들어온다."""
    from app.services.scene_reference_service import is_base_identity_ref
    assert is_base_identity_ref(prompt) is False


# ── 2. SQL 문안 ────────────────────────────────────────────────────────────

def _compiled_sql() -> str:
    from app.services.scene_reference_service import base_identity_ref_filter
    return str(base_identity_ref_filter().compile(
        compile_kwargs={"literal_binds": True}))


def test_the_sql_condition_names_every_marker():
    from app.services.scene_reference_service import DERIVED_REF_PREFIXES
    sql = _compiled_sql()
    for pre in DERIVED_REF_PREFIXES:
        # `_` 는 LIKE 이스케이프를 거치므로 그 모양으로 찾는다
        assert pre.replace("_", "/_") in sql, f"{pre} 가 조건에 없다: {sql}"
    # 표식이 없는 옛 자산(NULL)은 기본으로 남아야 한다
    assert "IS NULL" in sql.upper()


def test_the_underscore_in_a_marker_is_a_literal_not_a_wildcard():
    """★`autoescape` 가 없으면 `[state_variant` 가 `[stateXvariant` 까지 잡아
    **파이썬 판단과 갈린다** (Codex NON-BLOCK 1). SQL 은 이스케이프하고,
    파이썬은 원래 글자 그대로 본다 — 두 판단이 같아야 한다."""
    from app.services.scene_reference_service import is_base_identity_ref
    sql = _compiled_sql()
    assert "ESCAPE" in sql.upper()
    assert "/_variant" in sql and "/_id" in sql
    # 밑줄 자리에 다른 글자가 온 것은 파생본이 아니다 — 양쪽 다 「기본」
    assert is_base_identity_ref("[stateXvariant:c1:dead] 인물") is True
    assert is_base_identity_ref("[outlookXid:o1] 옛 표기") is True


def test_leading_space_is_not_trimmed_on_either_side():
    """앞 공백 정책도 **둘이 같아야** 한다 — SQL 은 다듬지 않으므로 파이썬도
    다듬지 않는다(실측 참조 138장 중 머리 공백이 있는 것 0장)."""
    from app.services.scene_reference_service import is_base_identity_ref
    assert is_base_identity_ref(" [composite:c1:o1] 찰리") is True
    sql = _compiled_sql()
    assert "TRIM" not in sql.upper() and "LTRIM" not in sql.upper()


# ── 3. 배선 — 기본을 고르는 세 자리가 그 조건을 건다 ───────────────────────

def _asset(tmp_path, name, data, **kw):
    from app.models.project import ImageAsset
    p = tmp_path / name
    p.write_bytes(data)
    return ImageAsset(project_id=PID, asset_type="reference",
                      file_path=str(p), **kw)


def _capturing_db(result_rows):
    """filter() 에 넘어온 조건을 모아 두는 가짜 DB. SQL 은 실행하지 않는다."""
    captured: list = []

    def _query(*a, **k):
        q = MagicMock()

        def _filter(*conds, **kk):
            captured.extend(conds)
            return q

        q.filter.side_effect = _filter
        for m in ("filter_by", "join", "order_by", "options"):
            getattr(q, m).return_value = q
        q.all.return_value = list(result_rows)
        q.first.return_value = result_rows[0] if result_rows else None
        return q

    db = MagicMock()
    db.query.side_effect = _query
    return db, captured


def _has_base_filter(captured) -> bool:
    from app.services.scene_reference_service import DERIVED_REF_PREFIXES
    for c in captured:
        try:
            sql = str(c.compile(compile_kwargs={"literal_binds": True}))
        except Exception:  # noqa: BLE001 — 조건이 아닌 인자
            continue
        # SQL 은 표식의 `_` 를 이스케이프한다 — 그 모양으로 찾는다
        if all(pre.replace("_", "/_") in sql for pre in DERIVED_REF_PREFIXES):
            return True
    return False


def test_loading_base_reference_bytes_asks_for_base_only(tmp_path):
    from app.services.scene_reference_service import SceneReferenceService
    base = _asset(tmp_path, "base.png", b"BASE", id="base-1",
                  entity_id=C1, is_primary=1, prompt_used="Full body")
    db, captured = _capturing_db([base])
    svc = SceneReferenceService.__new__(SceneReferenceService)
    svc._db, svc._project_id = db, PID
    got = svc.load_entity_reference_images([{"id": C1}])
    assert got == {C1: b"BASE"}
    assert _has_base_filter(captured), "기본 bytes 조회에 조건이 안 걸렸다"


def test_visible_entity_reference_map_asks_for_base_only(tmp_path):
    from app.services.scene_reference_service import SceneReferenceService
    base = _asset(tmp_path, "base2.png", b"BASE2", id="base-2",
                  entity_id=C1, is_primary=1, prompt_used=None)
    db, captured = _capturing_db([base])
    svc = SceneReferenceService.__new__(SceneReferenceService)
    svc._db, svc._project_id = db, PID
    got = svc.get_reference_image_map([{"id": C1}])
    assert got == {C1: b"BASE2"}
    assert _has_base_filter(captured), "visible entity 조회에 조건이 안 걸렸다"


def test_scene_ref_map_records_the_base_asset_id_not_a_composite(tmp_path):
    """`out_asset_id_map` 의 기본 키가 합성 id 로 채워지면 계보가 거짓이 된다."""
    from app.services.scene_reference_service import SceneReferenceService
    db, captured = _capturing_db([(C1, "base-3")])
    svc = SceneReferenceService.__new__(SceneReferenceService)
    svc._db, svc._project_id = db, PID
    out: dict = {}
    svc.build_scene_ref_image_map(
        {C1: b"BASE3"}, {C1: {"entity_type": "character"}},
        out_asset_id_map=out)
    assert out.get(C1) == "base-3"
    assert _has_base_filter(captured), "기본 asset_id 조회에 조건이 안 걸렸다"
