"""사람이 손으로 올린 대표를 **걷기가 내리지 않는다** (2026-09-20).

## 무엇이 났나

사용자가 손으로 올린 최종본이 걷기 한 번에 사라졌다. 걷기가 새 이미지를
**한 장도 안 사도** 캐시된 산출을 다시 영속하고, 그때
`auto_set_primary` 가 같은 still 의 형제를 **전부** 내렸다.
실측: 오전에 올린 8장이 저녁 걷기에 덮였고(구매 0), 이 화의 사람 대표는
지금 16장이다.

## 계약

  · 같은 still 에 **사람이 올린 대표**가 있으면 새 산출은 대표가 안 된다
  · 새 산출은 **저장은 된다** — 지우지 않으므로 사용자가 보고 고를 수 있다
  · 사람이 올린 자산 **자신**이 들어오면 그대로 대표가 된다
  · 레버 `image_protect_manual_primary` 를 끄면 종전 동작
"""
from __future__ import annotations

import pytest

from app.services.image_service_helpers import (
    MANUAL_UPLOAD_PROMPT,
    auto_set_primary,
    is_manual_upload,
)


class _Img:
    def __init__(self, id_, *, still_id="S1", prompt="generated",
                 is_primary=0, entity_id=None):
        self.id = id_
        self.still_id = still_id
        self.entity_id = entity_id
        self.prompt_used = prompt
        self.is_primary = is_primary


class _Q:
    def __init__(self, rows):
        self._rows = rows

    def filter(self, *a):
        return self

    def all(self):
        return self._rows


class _DB:
    def __init__(self, rows):
        self._rows = rows

    def query(self, model):
        return _Q(self._rows)


def test_a_human_primary_survives_a_pipeline_save():
    human = _Img("HUMAN", prompt=MANUAL_UPLOAD_PROMPT, is_primary=1)
    fresh = _Img("FRESH")

    auto_set_primary(_DB([human]), "P", fresh)

    assert human.is_primary == 1, "사람이 올린 대표가 내려갔다"
    assert fresh.is_primary == 0, "새 산출이 대표를 가로챘다"


def test_the_fresh_asset_is_still_kept():
    """대표가 안 될 뿐 **자산은 남는다** — 지우지 않는다."""
    human = _Img("HUMAN", prompt=MANUAL_UPLOAD_PROMPT, is_primary=1)
    fresh = _Img("FRESH")

    auto_set_primary(_DB([human]), "P", fresh)

    # 이 함수는 삭제를 하지 않는다 — 호출자가 add 한 자산이 그대로다
    assert fresh.id == "FRESH" and fresh.still_id == "S1"


def test_a_new_human_upload_still_takes_over():
    """사람이 **다시 올리면** 그것이 대표가 된다 — 사람이 바꾸려는 것이다."""
    old_human = _Img("OLD", prompt=MANUAL_UPLOAD_PROMPT, is_primary=1)
    new_human = _Img("NEW", prompt=MANUAL_UPLOAD_PROMPT)

    auto_set_primary(_DB([old_human]), "P", new_human)

    assert new_human.is_primary == 1
    assert old_human.is_primary == 0


def test_without_a_human_primary_nothing_changes():
    """사람 대표가 없으면 **종전 그대로** — 새 산출이 대표가 된다."""
    old = _Img("OLD", is_primary=1)
    fresh = _Img("FRESH")

    auto_set_primary(_DB([old]), "P", fresh)

    assert fresh.is_primary == 1
    assert old.is_primary == 0


def test_a_non_primary_human_asset_does_not_block():
    """사람이 올렸지만 **대표가 아닌** 자산은 막지 않는다."""
    human_old = _Img("HUMAN", prompt=MANUAL_UPLOAD_PROMPT, is_primary=0)
    fresh = _Img("FRESH")

    auto_set_primary(_DB([human_old]), "P", fresh)

    assert fresh.is_primary == 1


def test_the_lever_restores_the_old_behaviour(monkeypatch):
    from app.core import config

    monkeypatch.setattr(config.settings, "image_protect_manual_primary", False)
    human = _Img("HUMAN", prompt=MANUAL_UPLOAD_PROMPT, is_primary=1)
    fresh = _Img("FRESH")

    auto_set_primary(_DB([human]), "P", fresh)

    assert fresh.is_primary == 1, "레버를 꺼도 보호가 걸렸다"
    assert human.is_primary == 0


def test_upload_path_and_guard_share_one_constant():
    """표식을 **두 곳에 적지 않는다** — 한쪽만 고쳐지는 부류를 막는다."""
    import inspect
    import pathlib

    from app.services import image_upload_service as up

    src = pathlib.Path(inspect.getfile(up)).read_text(encoding="utf-8")
    assert "MANUAL_UPLOAD_PROMPT" in src, "업로드 경로가 상수를 안 쓴다"
    assert 'prompt_used="uploaded"' not in src, "표식이 두 곳에 적혀 있다"


def test_is_manual_upload_reads_the_marker():
    assert is_manual_upload(_Img("A", prompt=MANUAL_UPLOAD_PROMPT))
    assert not is_manual_upload(_Img("B", prompt="Create ONE FINAL…"))


# ── 재개·CP 연결 (2026-09-20 Codex BLOCK 1) ─────────────────────────

def test_jit_compares_against_the_pipeline_output_not_the_human_upload():
    """★사람 대표를 지킨 **뒤에** 생긴 결함.

    JIT 무변경 판정이 DB primary 의 bytes 와 이번 산출을 견준다. 대표가
    사람 업로드이면 **언제나 다르므로** 무변경 방문마다
      · `jit_regen_count` 가 오르고(거짓 재생성 집계)
      · 같은 후보가 파일·행으로 다시 쌓인다.

    그래서 비교 대상은 「지난번에 **파이프라인이** 보관한 산출」이어야 한다.
    """
    import inspect
    import pathlib

    from app.services import still_recipe_service as mod

    src = pathlib.Path(inspect.getfile(mod)).read_text(encoding="utf-8")
    i = src.find("_unchanged = (")
    assert i > 0, "전제 확인"
    head = src[max(0, i - 1600):i]
    assert "is_manual_upload(_prior)" in head, (
        "사람 대표를 비교 대상에서 갈라내지 않는다")
    assert "MANUAL_UPLOAD_PROMPT" in head, (
        "지난번 파이프라인 산출을 고르는 조회가 없다")
    seg = src[i:i + 400]
    assert "_prior_path" in seg
    # 재사용 갈래의 계보도 실제 그림을 가리켜야 한다
    j = src.find("primary_asset_by_tag[tag] = _cmp_prior.id")
    assert j > 0, "재사용 갈래가 사람 대표를 prev 계보로 물린다"


def test_checkpoint_primary_matches_the_db_primary():
    """CP 의 대표 칸이 **실제 대표**를 말한다.

    사람 대표가 있으면 새 산출은 `is_primary=0` 으로 저장된다. 그것을
    `primary_id` 로 적으면 **DB 대표=사람 · CP 대표=생성본**으로 갈린다.
    이번 방문이 만든 것은 `produced_id` 로 따로 남긴다.
    """
    import inspect
    import pathlib

    from app.services import still_recipe_service as mod

    src = pathlib.Path(inspect.getfile(mod)).read_text(encoding="utf-8")
    i = src.find('"primary_id": ')
    assert i > 0, "전제 확인"
    seg = src[max(0, i - 900):i + 400]
    assert "is_primary" in seg, "새 자산이 대표인지 안 본다"
    assert '"produced_id"' in seg, (
        "이번 방문이 만든 것을 따로 안 남긴다 — 대표와 섞인다")
