"""옷 합성·상태 변형 참조가 **실제로 실린다** — `created_at` 이 text 여도 (2026-09-20).

실측(컨트리로드 2판): `image_asset.created_at` 칸은 text 인데 합성 순위 함수가
`ca.created_at.timestamp()` 를 불러 매번 AttributeError 가 났고, 넓은 except 가
경고 한 줄만 남긴 채 **합성·상태 변형 참조 전부**를 버렸다. 09-19 본 주행 239샷이
옷 합성 없이 기본 참조만 받았다(찰리 54샷 「의상 합성 0」의 진짜 이유).

잠그는 것 — 프로덕션 `build_scene_ref_image_map` 이 text 시각 행을 받고:
- 합성 키 `composite:{인물}:{아웃룩}` 이 실린다 · 지금 기본 참조로 그린 합성이 이긴다
- 상태 변형 키 `state_variant:{인물}:{상태}` 가 실린다
"""
from __future__ import annotations

from unittest.mock import MagicMock

PID = "SAMPLE_P"
# 실제 키 정규식은 UUID 모양(소문자 16진·하이픈)만 받는다 — 표본 id 도 그 모양으로
C1 = "c1c1c1c1-0000-4000-8000-000000000001"
O1 = "0e0e0e0e-0000-4000-8000-000000000002"
BASE_OLD = "ba5e0000-0000-4000-8000-00000000000a"
BASE_NEW = "ba5e0000-0000-4000-8000-00000000000b"


def _db(rows_by_call):
    """query().filter().all() 이 부를 때마다 정해 둔 행 목록을 차례로 돌려준다."""
    calls = iter(rows_by_call)

    def _query(*a, **k):
        q = MagicMock()
        for m in ("filter", "filter_by", "join", "order_by", "options"):
            getattr(q, m).return_value = q
        q.all.side_effect = lambda: next(calls)
        return q

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


def test_composites_and_state_variants_load_with_text_created_at(tmp_path):
    from app.models.project import ImageAsset
    from app.services.scene_reference_service import SceneReferenceService

    def _png(name, data):
        p = tmp_path / name
        p.write_bytes(data)
        return str(p)

    # 같은 (인물, 아웃룩)에 합성 둘 — 옛 기본으로 그린 것(tier 2)과 지금 기본으로 그린 것(tier 0).
    # created_at 은 **text** 다(실제 DB 와 같은 모양, 두 표기 섞임).
    old = ImageAsset(id="comp-old", project_id=PID, asset_type="reference",
                     entity_id=C1, file_path=_png("old.png", b"OLD-COMP"),
                     prompt_used=f"[composite:{C1}:{O1}:{BASE_OLD}] 옷",
                     created_at="2026-09-19 03:07:52.354078+00")
    new = ImageAsset(id="comp-new", project_id=PID, asset_type="reference",
                     entity_id=C1, file_path=_png("new.png", b"NEW-COMP"),
                     prompt_used=f"[composite:{C1}:{O1}:{BASE_NEW}] 옷",
                     created_at="2026-09-19T15:40:00.000000+00:00")
    base = ImageAsset(id=BASE_NEW, project_id=PID, asset_type="reference",
                      entity_id=C1, file_path=_png("base.png", b"BASE"),
                      prompt_used="Full body reference", is_primary=1,
                      created_at="2026-09-19T15:00:00+00:00")
    sv = ImageAsset(id="sv-1", project_id=PID, asset_type="reference",
                    entity_id=C1, file_path=_png("sv.png", b"SV-DEAD"),
                    prompt_used=f"[state_variant:{C1}:dead] 인물",
                    created_at="2026-09-19 03:14:44.106831+00")
    # 부르는 순서: 기본 참조 id(out_asset_id_map 을 줄 때) → 아웃룩 단독 → 합성
    #  → 현재 기본 → 상태 변형
    db = _db([[(C1, BASE_NEW)], [], [old, new], [base], [sv]])
    svc = SceneReferenceService.__new__(SceneReferenceService)
    svc._db = db
    svc._project_id = PID
    out_ids: dict = {}
    got = svc.build_scene_ref_image_map(
        {C1: b"BASE"},
        {C1: {"entity_type": "character"}, O1: {"entity_type": "outlook"}},
        out_asset_id_map=out_ids)
    assert got[f"composite:{C1}:{O1}"] == b"NEW-COMP"      # 지금 기본으로 그린 합성이 이긴다
    assert out_ids[f"composite:{C1}:{O1}"] == "comp-new"
    assert got[f"state_variant:{C1}:dead"] == b"SV-DEAD"   # 상태 변형도 실린다


def test_same_tier_composites_are_ordered_by_parsed_time_not_text(tmp_path):
    """같은 순위(둘 다 옛 무표식)끼리는 **시각 순**으로 — 글자 순이 아니다 (Codex 비차단).

    두 표기가 섞이면 글자 비교는 틀린다: "2026-09-19 23:00…"(공백)과
    "2026-09-19T01:00…"(T)을 글자로 견주면 ' ' < 'T' 라 **23시가 더 옛것**으로 읽힌다.
    """
    from app.models.project import ImageAsset
    from app.services.scene_reference_service import SceneReferenceService

    def _png(name, data):
        p = tmp_path / name
        p.write_bytes(data)
        return str(p)

    later_space = ImageAsset(
        id="comp-late", project_id=PID, asset_type="reference", entity_id=C1,
        file_path=_png("late.png", b"LATE"),
        prompt_used=f"[composite:{C1}:{O1}] 옷",
        created_at="2026-09-19 23:00:00.000001+00")
    earlier_t = ImageAsset(
        id="comp-early", project_id=PID, asset_type="reference", entity_id=C1,
        file_path=_png("early.png", b"EARLY"),
        prompt_used=f"[composite:{C1}:{O1}] 옷",
        created_at="2026-09-19T01:00:00+00:00")
    base = ImageAsset(id=BASE_NEW, project_id=PID, asset_type="reference",
                      entity_id=C1, file_path=_png("base.png", b"BASE"),
                      prompt_used="Full body reference", is_primary=1,
                      created_at="2026-09-19T00:00:00+00:00")
    db = _db([[(C1, BASE_NEW)], [], [earlier_t, later_space], [base], []])
    svc = SceneReferenceService.__new__(SceneReferenceService)
    svc._db = db
    svc._project_id = PID
    got = svc.build_scene_ref_image_map(
        {C1: b"BASE"},
        {C1: {"entity_type": "character"}, O1: {"entity_type": "outlook"}},
        out_asset_id_map={})
    assert got[f"composite:{C1}:{O1}"] == b"LATE"
