"""api/v1/prompts.py admin UI 정렬 단위 테스트 (Quick win #7 follow-up G2).

list_prompts (itertools.groupby + reverse) 와 list_modules (defaultdict +
Python sort) 가 (module, name) 묶음 안에서 numeric DESC 적용되는지 회귀
가드. 시나리오 핵심: ``9.20260301`` vs ``10.20260301`` lexical 이면 ``9...``
가 더 큰 것처럼 잘못 정렬되는데, ``_version_sort_key`` 사용 시 정확히
numeric DESC.
"""
from __future__ import annotations

from typing import Any, Iterable, List, Optional


class _StubResult:
    def __init__(self, rows: List[Any]):
        self._rows = rows

    def fetchall(self):
        return self._rows


class _StubDB:
    """row tuple list 만 반환하는 최소 stub — execute 호출 1회 가정."""

    def __init__(self, rows: List[Any]):
        self._rows = rows
        self.queries: List[str] = []

    def execute(self, sql, params=None):
        # query string capture (text() 객체 → text 추출용)
        try:
            self.queries.append(str(sql.text))
        except AttributeError:
            self.queries.append(str(sql))
        return _StubResult(self._rows)


def _make_list_prompts_row(
    module: str, name: str, version: str, is_active: bool = True,
) -> tuple:
    """list_prompts SELECT 컬럼 순서 (id, module, name, version, is_active,
    file_path, created_at, created_by, content_length)."""
    return (
        f"{module}_{name}_{version}",       # id
        module,                              # module
        name,                                # name
        version,                             # version
        is_active,                           # is_active
        None,                                # file_path
        "2026-01-01",                        # created_at
        "u1",                                # created_by
        100,                                 # content_length
    )


def _make_list_modules_row(module: str, version: str, is_active: bool = True) -> tuple:
    """list_modules SELECT 컬럼 (module, version, is_active)."""
    return (module, version, is_active)


def test_list_prompts_numeric_sort_within_group():
    """같은 (module, name) 안에서 version 이 numeric DESC 정렬되어야 한다.

    ``9.20260301`` vs ``10.20260301`` — lexical 이면 ``9...`` 가 먼저, numeric
    이면 ``10...`` 이 먼저. itertools.groupby 가 이 group 내 reverse 적용.
    """
    from app.api.v1.prompts import list_prompts

    rows = [
        _make_list_prompts_row("scene_extractor", "system", "9.20260301"),
        _make_list_prompts_row("scene_extractor", "system", "10.20260301"),
        _make_list_prompts_row("scene_extractor", "system", "12.20260301"),
        _make_list_prompts_row("scene_extractor", "system", "9.20260302"),
    ]
    db = _StubDB(rows)
    out = list_prompts(module=None, active_only=False, db=db, current_user=object())

    # 모두 같은 (module, name) 이므로 numeric DESC.
    versions = [r["version"] for r in out]
    assert versions == ["12.20260301", "10.20260301", "9.20260302", "9.20260301"], (
        f"expected numeric DESC, got: {versions}"
    )


def test_list_prompts_groups_independent():
    """다른 (module, name) 묶음끼리는 module/name 알파벳 순, 묶음 안에서만 reverse."""
    from app.api.v1.prompts import list_prompts

    rows = [
        _make_list_prompts_row("aaa", "n1", "1.x"),
        _make_list_prompts_row("aaa", "n1", "10.x"),
        _make_list_prompts_row("bbb", "n2", "9.x"),
        _make_list_prompts_row("bbb", "n2", "100.x"),
    ]
    db = _StubDB(rows)
    out = list_prompts(module=None, active_only=False, db=db, current_user=object())

    # 묶음 순서: aaa < bbb (module asc), 각 묶음 안에서 numeric DESC.
    assert [(r["module"], r["version"]) for r in out] == [
        ("aaa", "10.x"),
        ("aaa", "1.x"),
        ("bbb", "100.x"),
        ("bbb", "9.x"),
    ]


def test_list_modules_latest_version_numeric_with_secondary_lexical_witness():
    """list_modules latest_version 이 primary numeric DESC + secondary lexical witness.

    이 테스트는 두 가지를 동시에 명시:
      1. **버그 가드**: scene 모듈의 ``9.x`` < ``10.x`` < ``12.x`` numeric DESC.
      2. **lexical drift witness**: entity 의 ``9.99`` vs ``9.100`` 에서 secondary
         lexical 비교로 ``9.99`` 가 latest. 본 프로젝트 12-digit timestamp 컨벤션
         에서는 무관하지만 비표준 secondary 형식 시 lexical drift 재발 가능 —
         G3 docstring 과 짝.

    **이 테스트가 fail 한다면**: ``_version_sort_key`` 가 secondary numeric cast 도입.
    assert 를 그냥 수정하지 말고 _version_sort_key 변경 의도 + secondary 형식 컨벤션
    검토 후 함께 갱신.
    """
    from app.api.v1.prompts import list_modules

    rows = [
        _make_list_modules_row("scene", "9.20260301"),
        _make_list_modules_row("scene", "12.20260301"),
        _make_list_modules_row("scene", "10.20260301"),
        _make_list_modules_row("entity", "9.99"),
        _make_list_modules_row("entity", "9.100"),  # secondary lexical edge — 12-digit 위반
    ]
    db = _StubDB(rows)
    out = list_modules(db=db, current_user=object())

    by_module = {r["module"]: r for r in out}
    # Primary numeric 정확.
    assert by_module["scene"]["latest_version"] == "12.20260301"
    # WITNESS: secondary lexical drift — 12-digit timestamp 위반 형식에서만 발생.
    # _version_sort_key 가 secondary numeric cast 추가되면 "9.100" 으로 변경 필요.
    assert by_module["entity"]["latest_version"] == "9.99", (
        "lexical drift witness — see G3 docstring. If this fails, secondary "
        "numeric cast was added; update both _version_sort_key and this assertion."
    )


def test_list_modules_total_and_active_count():
    """latest_version 외에 total + active_count 도 정확."""
    from app.api.v1.prompts import list_modules

    rows = [
        _make_list_modules_row("m1", "1.x", is_active=True),
        _make_list_modules_row("m1", "2.x", is_active=True),
        _make_list_modules_row("m1", "3.x", is_active=False),
    ]
    db = _StubDB(rows)
    out = list_modules(db=db, current_user=object())
    assert len(out) == 1
    assert out[0]["module"] == "m1"
    assert out[0]["total"] == 3
    assert out[0]["active_count"] == 2
    assert out[0]["latest_version"] == "3.x"


def test_list_prompts_empty_db_returns_empty_list():
    from app.api.v1.prompts import list_prompts

    db = _StubDB([])
    out = list_prompts(module=None, active_only=False, db=db, current_user=object())
    assert out == []


def test_list_modules_empty_db_returns_empty_list():
    from app.api.v1.prompts import list_modules

    db = _StubDB([])
    out = list_modules(db=db, current_user=object())
    assert out == []
