"""롤 N장을 **동시에** 만드는가 — 호출부를 AST 로 본다 (#21 시간, 2026-08-29).

## 왜 AST 인가

`run_multiroll_select` 는 `parallel_rolls=True` 면 제대로 병렬로 만들고,
그 동작은 이미 다른 시험이 잰다. 문제는 **거기까지 값이 오는가**다.

`_run_branch(...)` 호출이 여덟 곳인데 그중 셋에만 인자가 붙어 있었다.
그러니 서비스는 「병렬 기능이 있다」면서 흔한 샷에서는 롤을 **하나씩**
만들고 있었다. 함수 하나를 시험하면 초록인데 조립을 안 하는 부류다 —
그래서 **호출부를 따로** 본다.

## 재는 것

    rc=1 인 자리        → 인자가 **없어야** 한다 (롤이 하나면 병렬은 무의미)
    그 밖의 모든 자리   → 인자가 **있어야** 한다

★목록을 손으로 적지 않는다. 손으로 적으면 새 갈래가 생겼을 때 목록에
 안 들어가고 시험은 그대로 초록이다. `rc` 값으로 **코드에게 물어본다.**
"""
from __future__ import annotations

import ast
import pathlib

SERVICE = (pathlib.Path(__file__).resolve().parents[2]
           / "app" / "services" / "still_recipe_service.py")


def _branch_calls():
    """`_run_branch(...)` 호출을 전부 찾아 (줄, rc, parallel_rolls 유무)."""
    tree = ast.parse(SERVICE.read_text(encoding="utf-8"))
    out = []
    for node in ast.walk(tree):
        if not isinstance(node, ast.Call):
            continue
        name = getattr(node.func, "id", None) or getattr(node.func, "attr", None)
        if name != "_run_branch":
            continue
        kw = {k.arg for k in node.keywords if k.arg}
        rc = None
        for k in node.keywords:
            if k.arg == "rc" and isinstance(k.value, ast.Constant):
                rc = k.value.value
        out.append((node.lineno, rc, "parallel_rolls" in kw))
    return out


def test_the_service_has_branch_calls_at_all():
    """★먼저 **찾긴 찾았는지** 본다.

    이름이 바뀌어 0건이 되면 아래 시험들이 전부 「위반 없음」으로 초록이
    된다 — 못 찾은 것을 없다고 읽는 부류. 그래서 하한을 잠근다.
    """
    calls = _branch_calls()
    assert len(calls) >= 6, (
        f"`_run_branch` 호출을 {len(calls)}건밖에 못 찾았다 — 헬퍼 이름이 "
        "바뀌었으면 이 시험을 먼저 고쳐라. 0건이 조용히 초록이 되면 안 된다")


def test_every_multi_roll_branch_generates_rolls_in_parallel():
    """롤이 둘 이상인 자리는 **전부** 동시 생성이어야 한다."""
    missing = [(ln, rc) for ln, rc, has in _branch_calls()
               if rc != 1 and not has]
    assert not missing, (
        "롤을 하나씩 순차로 만드는 갈래가 남았다 "
        f"(줄, rc): {missing} — `parallel_rolls=True` 를 넘겨라. "
        "생성 내용·후보 순서·지문은 안 바뀐다")


def test_single_roll_branches_are_left_alone():
    """★반대편도 잠근다 — rc=1 에 붙이면 「고쳤다」가 넓어진 것이다.

    롤이 하나면 병렬은 아무 일도 안 한다(`len(missing) > 1` 게이트).
    거기까지 붙이는 것은 지적 범위를 넘겨 고치는 것이고, 나중에 그
    자리의 계약이 바뀌었을 때 무엇이 의도였는지 흐려진다.
    """
    wrong = [ln for ln, rc, has in _branch_calls() if rc == 1 and has]
    assert not wrong, (
        f"롤이 하나인 자리에 `parallel_rolls` 를 붙였다: 줄 {wrong} — "
        "아무 일도 안 하고 의도만 흐린다")


def test_at_least_one_single_roll_branch_exists():
    """위 시험이 **빈 목록으로 초록**이 되지 않게 한다.

    rc=1 갈래가 사라지면 `test_single_roll_branches_are_left_alone` 은
    잴 것이 없어 자동 통과한다. 그때는 그 시험을 지우든 고치든 해야지
    조용히 초록으로 남으면 안 된다.
    """
    singles = [ln for ln, rc, _ in _branch_calls() if rc == 1]
    assert singles, (
        "rc=1 갈래가 하나도 없다 — 위 반대편 시험이 잴 것이 없어졌다. "
        "그 시험을 정리하고 이 시험도 같이 손봐라")
