"""상태 변형의 입력 그림은 **그 상태가 나오는 샷에서 입은 옷**을 따른다 (2026-09-20).

실측(컨트리로드 2판): 「가장 최근 합성」에서 만들어, 찰리(로봇)의 의식 없음·죽음이
외투 차림이 됐다 — 그 상태가 나오는 샷 5개에서 찰리는 옷이 없다(O00·배정 없음).
상태 변형은 합성보다 먼저 붙으므로 그 샷에 틀린 옷이 실린다.

잠그는 것:
- 다수결 규칙: 옷 없음(O00) 다수 → 기본 그림 · 한 옷 다수 → 그 옷 · 동수 → None ·
  배정 없음은 표를 던지지 않는다
- 입력 자산: 기본 그림 / 그 옷의 합성 중 **지금 기본 그림으로 그린 것**(더 새 옛판보다 먼저)
- 이미 있는 변형의 입력이 규칙과 다르면 다시 만든다 · 모르면(기록 없음) 안 산다
"""
from __future__ import annotations

import json
from unittest.mock import MagicMock

from app.core.state_variant_source import (
    BASE,
    existing_source_differs,
    pick_state_variant_source,
    source_asset_for,
)

O00 = "o000-uuid"


def test_bare_or_unassigned_majority_uses_the_base():
    # 찰리 실측 모양: O00 둘 + 배정 없음 둘
    assert pick_state_variant_source([O00, O00, None, None], {O00}) == BASE


def test_one_outfit_majority_uses_that_outfit():
    assert pick_state_variant_source(["o01", "o01", None], {O00}) == "o01"


def test_tie_or_nothing_falls_back_to_the_old_rule():
    assert pick_state_variant_source(["o01", "o02"], {O00}) is None
    assert pick_state_variant_source([], {O00}) is None


def test_unassigned_shots_do_not_vote():
    """★배정 없음은 표가 아니다 — 모르는 것을 「옷 없음」으로 세면 명시된 옷을 이긴다.
    실측(현우 중상): 배정 없음 3 · O02 2 → O02 여야 한다."""
    assert pick_state_variant_source([None, None, None, "o02", "o02"], {O00}) == "o02"
    assert pick_state_variant_source([None, None], {O00}) is None


def _fake_db(base_rows, comp_rows):
    calls = iter([base_rows, comp_rows])

    def _query(*a, **k):
        q = MagicMock()
        for m in ("filter", "filter_by", "order_by"):
            getattr(q, m).return_value = q
        rows = next(calls)
        q.first.return_value = rows[0] if rows else None
        q.all.return_value = list(rows)
        return q

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


def test_source_asset_prefers_the_composite_drawn_from_the_current_base():
    from app.models.project import ImageAsset

    base = ImageAsset(id="BASE-NOW", prompt_used="Full body")
    stale_but_newer = ImageAsset(
        id="comp-stale", prompt_used="[composite:c1:o01:BASE-OLD] x",
        created_at="2026-09-20T01:00:00+00:00")
    current = ImageAsset(
        id="comp-now", prompt_used="[composite:c1:o01:BASE-NOW] x",
        created_at="2026-09-19 03:00:00.1+00")
    got = source_asset_for(_fake_db([base], [stale_but_newer, current]),
                           "P", "c1", "o01")
    assert got.id == "comp-now"


def test_source_asset_for_base_is_the_base():
    from app.models.project import ImageAsset

    base = ImageAsset(id="BASE-NOW", prompt_used="Full body")
    assert source_asset_for(_fake_db([base], []), "P", "c1", BASE).id == "BASE-NOW"


def test_existing_variant_is_rebuilt_only_when_its_input_differs():
    assert existing_source_differs(json.dumps(["comp-O07"]), "BASE-NOW") is True
    assert existing_source_differs(json.dumps(["BASE-NOW"]), "BASE-NOW") is False
    # 규칙이 못 골랐거나 옛 자산에 입력 기록이 없으면 다시 사지 않는다
    assert existing_source_differs(json.dumps(["comp-O07"]), None) is False
    assert existing_source_differs(None, "BASE-NOW") is False
    assert existing_source_differs("[]", "BASE-NOW") is False
