"""Progressive Disclosure 레이어 기능 테스트

TC13 ~ TC20: search() layer 파라미터 및 get_by_ids() API 검증.
"""

import json
import sys
import textwrap
from pathlib import Path

import pytest

sys.path.insert(0, "/home/jay/workspace")

from utils.memory_indexer import MemoryIndexer  # noqa: E402

# ---------------------------------------------------------------------------
# 공통 헬퍼
# ---------------------------------------------------------------------------

DIARY_CONTENT = textwrap.dedent("""\
    ---
    date: "2026-04-05"
    session: 1
    team_id: "dev3"
    task_id: "task-9999"
    tags: [auto-compact, pre-compact]
    auto_generated: true
    ---
    ## 수행한 작업
    - 메모리 인덱서 TDD RED phase 작성
    - SQLite FTS5 테이블 설계 검토

    ## 피드백 및 수정 지시
    - 한국어 검색 성능 확인 필요
""")

MEMORY_CONTENT = textwrap.dedent("""\
    ---
    name: 위임 규칙 종합
    description: 파일 기반 위임, 논리적 팀, 한정승인 등
    type: feedback
    team_id: "dev3"
    ---
    ### 파일 기반 위임 (절대 규칙)
    - 지시 내용 → 파일 작성 → `--task-file`로 전달.
    - 100자 이내만 `--task` 허용.

    ### 논리적 팀
    - 마케팅/컨설팅/출판/디자인 라우팅 규칙 준수.
""")


def make_diary_file(directory: Path, filename: str = "2026-04-05-session-01.md") -> Path:
    """임시 디렉토리에 diary 파일 생성."""
    f = directory / filename
    f.write_text(DIARY_CONTENT, encoding="utf-8")
    return f


def make_memory_file(directory: Path, filename: str = "delegation_rules.md") -> Path:
    """임시 디렉토리에 memory 파일 생성."""
    f = directory / filename
    f.write_text(MEMORY_CONTENT, encoding="utf-8")
    return f


# ---------------------------------------------------------------------------
# TC13: Layer index 반환 필드 검증
# ---------------------------------------------------------------------------


def test_TC13_layer_index_fields(tmp_path):
    """layer='index' 검색 결과에는 id, title, type, score만 포함되어야 한다."""
    db_path = str(tmp_path / "test.db")
    mem_file = make_memory_file(tmp_path)

    indexer = MemoryIndexer(db_path=db_path)
    try:
        indexer.index_file(str(mem_file))
        results = indexer.search("위임 규칙", layer="index")
    finally:
        indexer.close()

    assert isinstance(results, list), "search()가 리스트를 반환하지 않습니다."
    assert len(results) > 0, "layer='index' 검색 결과가 비어있습니다."

    required_fields = {"id", "title", "type", "score"}
    forbidden_fields = {"file_path", "team_id", "tags", "snippet"}

    for item in results:
        for field in required_fields:
            assert field in item, f"layer='index' 결과에 필수 필드 '{field}'가 없습니다: {list(item.keys())}"
        for field in forbidden_fields:
            assert (
                field not in item
            ), f"layer='index' 결과에 포함되어서는 안 될 필드 '{field}'가 있습니다: {list(item.keys())}"


# ---------------------------------------------------------------------------
# TC14: Layer summary 반환 필드 검증
# ---------------------------------------------------------------------------


def test_TC14_layer_summary_fields(tmp_path):
    """layer='summary' 결과에는 id, title, type, score, snippet이 있어야 하고
    snippet은 50자 이하여야 한다. file_path, team_id, tags는 없어야 한다."""
    db_path = str(tmp_path / "test.db")
    mem_file = make_memory_file(tmp_path)

    indexer = MemoryIndexer(db_path=db_path)
    try:
        indexer.index_file(str(mem_file))
        results = indexer.search("위임 규칙", layer="summary")
    finally:
        indexer.close()

    assert isinstance(results, list), "search()가 리스트를 반환하지 않습니다."
    assert len(results) > 0, "layer='summary' 검색 결과가 비어있습니다."

    required_fields = {"id", "title", "type", "score", "snippet"}
    forbidden_fields = {"file_path", "team_id", "tags"}

    for item in results:
        for field in required_fields:
            assert field in item, f"layer='summary' 결과에 필수 필드 '{field}'가 없습니다: {list(item.keys())}"
        for field in forbidden_fields:
            assert (
                field not in item
            ), f"layer='summary' 결과에 포함되어서는 안 될 필드 '{field}'가 있습니다: {list(item.keys())}"
        snippet = item.get("snippet", "")
        assert len(snippet) <= 50, f"layer='summary' snippet이 50자를 초과합니다: {len(snippet)}자 → {snippet!r}"


# ---------------------------------------------------------------------------
# TC15: Layer full 기존 동작 호환
# ---------------------------------------------------------------------------


def test_TC15_layer_full_compatibility(tmp_path):
    """layer='full' 결과에는 file_path, title, type, team_id, tags, snippet, score가
    모두 있어야 하고, layer 미지정 결과와 동일해야 한다."""
    db_path = str(tmp_path / "test.db")
    mem_file = make_memory_file(tmp_path)

    indexer = MemoryIndexer(db_path=db_path)
    try:
        indexer.index_file(str(mem_file))
        results_full = indexer.search("위임 규칙", layer="full")
        results_default = indexer.search("위임 규칙")
    finally:
        indexer.close()

    assert isinstance(results_full, list), "layer='full' search()가 리스트를 반환하지 않습니다."
    assert len(results_full) > 0, "layer='full' 검색 결과가 비어있습니다."

    required_fields = {"file_path", "title", "type", "team_id", "tags", "snippet", "score"}
    for item in results_full:
        for field in required_fields:
            assert field in item, f"layer='full' 결과에 필수 필드 '{field}'가 없습니다: {list(item.keys())}"

    # layer 미지정과 결과가 동일해야 함 (JSON 직렬화로 비교)
    assert json.dumps(results_full, ensure_ascii=False, sort_keys=True) == json.dumps(
        results_default, ensure_ascii=False, sort_keys=True
    ), "layer='full' 결과와 layer 미지정 결과가 다릅니다."


# ---------------------------------------------------------------------------
# TC16: Layer 유효성 검사
# ---------------------------------------------------------------------------


def test_TC16_invalid_layer_raises(tmp_path):
    """layer='invalid' 전달 시 ValueError가 발생해야 한다."""
    db_path = str(tmp_path / "test.db")

    indexer = MemoryIndexer(db_path=db_path)
    try:
        with pytest.raises(ValueError):
            indexer.search("위임 규칙", layer="invalid")
    finally:
        indexer.close()


# ---------------------------------------------------------------------------
# TC17: get_by_ids 기본 동작
# ---------------------------------------------------------------------------


def test_TC17_get_by_ids_basic(tmp_path):
    """파일 2개를 인덱싱한 뒤 get_by_ids()로 조회하면 content 필드가 있어야 한다."""
    db_path = str(tmp_path / "test.db")
    diary_file = make_diary_file(tmp_path)
    mem_file = make_memory_file(tmp_path)

    indexer = MemoryIndexer(db_path=db_path)
    try:
        indexer.index_file(str(diary_file))
        indexer.index_file(str(mem_file))

        # ID 수집: layer="index"로 검색 후 "id" 필드 활용
        diary_results = indexer.search("메모리 인덱서", layer="index")
        mem_results = indexer.search("위임 규칙", layer="index")

        assert len(diary_results) > 0, "diary 파일 검색 결과가 없습니다."
        assert len(mem_results) > 0, "memory 파일 검색 결과가 없습니다."

        id1 = diary_results[0]["id"]
        id2 = mem_results[0]["id"]

        fetched = indexer.get_by_ids([id1, id2])
    finally:
        indexer.close()

    assert isinstance(fetched, list), "get_by_ids()가 리스트를 반환하지 않습니다."
    assert len(fetched) == 2, f"get_by_ids([id1, id2]) 결과 건수가 2여야 하는데 {len(fetched)}입니다."
    for item in fetched:
        assert "content" in item, f"get_by_ids() 결과에 'content' 필드가 없습니다: {list(item.keys())}"


# ---------------------------------------------------------------------------
# TC18: get_by_ids 빈 리스트
# ---------------------------------------------------------------------------


def test_TC18_get_by_ids_empty(tmp_path):
    """get_by_ids([])는 빈 리스트를 반환해야 한다."""
    db_path = str(tmp_path / "test.db")

    indexer = MemoryIndexer(db_path=db_path)
    try:
        result = indexer.get_by_ids([])
    finally:
        indexer.close()

    assert isinstance(result, list), "get_by_ids([])가 리스트를 반환하지 않습니다."
    assert result == [], f"get_by_ids([])가 빈 리스트를 반환하지 않았습니다: {result}"


# ---------------------------------------------------------------------------
# TC19: get_by_ids 존재하지 않는 ID
# ---------------------------------------------------------------------------


def test_TC19_get_by_ids_nonexistent(tmp_path):
    """get_by_ids([99999])는 해당 ID가 없으면 빈 리스트를 반환해야 한다."""
    db_path = str(tmp_path / "test.db")

    indexer = MemoryIndexer(db_path=db_path)
    try:
        result = indexer.get_by_ids([99999])
    finally:
        indexer.close()

    assert isinstance(result, list), "get_by_ids([99999])가 리스트를 반환하지 않습니다."
    assert result == [], f"존재하지 않는 ID 조회 시 빈 리스트를 반환해야 하는데 {result}를 반환했습니다."


# ---------------------------------------------------------------------------
# TC20: 토큰 절감 측정
# ---------------------------------------------------------------------------


def test_TC20_token_saving_by_layer(tmp_path):
    """같은 검색어로 layer='index' < 'summary' < 'full' 순으로 바이트 크기가
    증가해야 하며, full 대비 index의 크기가 50% 이하여야 한다."""
    db_path = str(tmp_path / "test.db")
    diary_file = make_diary_file(tmp_path)
    mem_file = make_memory_file(tmp_path)

    indexer = MemoryIndexer(db_path=db_path)
    try:
        indexer.index_file(str(diary_file))
        indexer.index_file(str(mem_file))

        results_index = indexer.search("위임", layer="index")
        results_summary = indexer.search("위임", layer="summary")
        results_full = indexer.search("위임", layer="full")
    finally:
        indexer.close()

    def byte_size(obj) -> int:
        return len(json.dumps(obj, ensure_ascii=False).encode("utf-8"))

    size_index = byte_size(results_index)
    size_summary = byte_size(results_summary)
    size_full = byte_size(results_full)

    assert size_index < size_summary, f"index({size_index}B) 크기가 summary({size_summary}B) 이상입니다."
    assert size_summary < size_full, f"summary({size_summary}B) 크기가 full({size_full}B) 이상입니다."

    ratio = size_index / size_full
    assert ratio <= 0.5, (
        f"full 대비 index 크기 비율이 50%를 초과합니다: {ratio:.1%} " f"(index={size_index}B, full={size_full}B)"
    )
