"""task-2410: 3문서 API 테스트.

dashboard.routes_get 의 handle_get_three_docs_list / handle_get_three_docs_doc
핸들러가 type 화이트리스트, path traversal, 정상 조회 시나리오를
올바르게 처리하는지 검증한다.
"""

from __future__ import annotations

import sys
from pathlib import Path

import pytest

WORKSPACE_ROOT = Path("/home/jay/workspace")
if str(WORKSPACE_ROOT) not in sys.path:
    sys.path.insert(0, str(WORKSPACE_ROOT))

from dashboard.routes_get import (  # noqa: E402
    handle_get_three_docs_doc,
    handle_get_three_docs_list,
)


class _FakeLoader:
    def reload_all(self) -> None:
        return None


@pytest.fixture()
def loader() -> _FakeLoader:
    return _FakeLoader()


# ---- list endpoint ----------------------------------------------------------


def test_list_system_returns_array(loader: _FakeLoader) -> None:
    code, data = handle_get_three_docs_list("/api/three-docs?type=system", loader)
    assert code == 200
    assert isinstance(data, list)
    assert len(data) > 0
    item = data[0]
    assert {"id", "type", "has_plan", "has_context_notes", "has_checklist", "title", "updated_at"} <= set(item)
    assert item["type"] == "system"


def test_list_project_returns_array(loader: _FakeLoader) -> None:
    code, data = handle_get_three_docs_list("/api/three-docs?type=project", loader)
    assert code == 200
    assert isinstance(data, list)
    # plans 디렉토리에 최소 1개 프로젝트 존재
    assert len(data) >= 1
    assert all(item["type"] == "project" for item in data)


def test_list_task_returns_array_sorted(loader: _FakeLoader) -> None:
    code, data = handle_get_three_docs_list("/api/three-docs?type=task", loader)
    assert code == 200
    assert isinstance(data, list)
    assert len(data) >= 1
    assert all(item["type"] == "task" for item in data)
    # updated_at 내림차순 정렬 검증
    for prev, curr in zip(data, data[1:]):
        assert prev["updated_at"] >= curr["updated_at"]


def test_list_invalid_type_returns_400(loader: _FakeLoader) -> None:
    code, data = handle_get_three_docs_list("/api/three-docs?type=evil", loader)
    assert code == 400
    assert "error" in data


def test_list_missing_type_returns_400(loader: _FakeLoader) -> None:
    code, _ = handle_get_three_docs_list("/api/three-docs", loader)
    assert code == 400


# ---- doc endpoint -----------------------------------------------------------


def test_doc_path_traversal_blocked(loader: _FakeLoader) -> None:
    code, _ = handle_get_three_docs_doc("/api/three-docs/system/..%2F..%2Fetc/plan", loader)
    assert code == 400


def test_doc_topic_with_slash_blocked(loader: _FakeLoader) -> None:
    code, _ = handle_get_three_docs_doc("/api/three-docs/project/ins%2Furo/plan", loader)
    assert code == 400


def test_doc_invalid_type_blocked(loader: _FakeLoader) -> None:
    code, _ = handle_get_three_docs_doc("/api/three-docs/evil/topic/plan", loader)
    assert code == 400


def test_doc_invalid_doc_blocked(loader: _FakeLoader) -> None:
    code, _ = handle_get_three_docs_doc("/api/three-docs/system/3docs-schema/secret", loader)
    assert code == 400


def test_doc_missing_file_returns_404(loader: _FakeLoader) -> None:
    # 단독 .md 시스템 주제는 plan만 있음 → checklist 요청은 404
    code, data = handle_get_three_docs_doc("/api/three-docs/system/anu-guide/checklist", loader)
    assert code == 404
    assert "expected_path" in data


def test_doc_valid_project_plan_returns_200(loader: _FakeLoader) -> None:
    # anu-guide-system 프로젝트는 plan/context/checklist 3 파일 모두 존재
    code, data = handle_get_three_docs_doc(
        "/api/three-docs/project/anu-guide-system/plan", loader
    )
    assert code == 200
    assert "content" in data
    assert "size" in data
    assert "mtime" in data
    assert data["doc"] == "plan"
    assert data["topic"] == "anu-guide-system"
    assert data["type"] == "project"


# ---- task-2414: 404 body 필드 보강 회귀 테스트 ---------------------------------


def test_404_response_includes_expected_path_for_missing_context_notes(
    loader: _FakeLoader, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """task topic에 plan.md만 있을 때 context-notes 요청 → 404 + expected_path 키 포함."""
    import dashboard.routes_get as rg

    # 임시 task 디렉토리 + plan.md만 생성
    topic_dir = tmp_path / "my-task-topic"
    topic_dir.mkdir()
    (topic_dir / "plan.md").write_text("# My Task Plan\n", encoding="utf-8")

    # _three_docs_get_base_dir 이 tmp_path 를 반환하도록 monkeypatch
    orig_get_base = rg._three_docs_get_base_dir

    def _fake_base(doc_type: str):
        if doc_type == "task":
            return tmp_path
        return orig_get_base(doc_type)

    monkeypatch.setattr(rg, "_three_docs_get_base_dir", _fake_base)

    code, data = handle_get_three_docs_doc(
        "/api/three-docs/task/my-task-topic/context-notes", loader
    )
    assert code == 404
    assert "expected_path" in data
    assert data["expected_path"].endswith("context-notes.md")


def test_404_response_includes_expected_path_for_missing_checklist(
    loader: _FakeLoader, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """task topic에 plan.md만 있을 때 checklist 요청 → 404 + expected_path 키 포함."""
    import dashboard.routes_get as rg

    topic_dir = tmp_path / "my-task-topic"
    topic_dir.mkdir()
    (topic_dir / "plan.md").write_text("# My Task Plan\n", encoding="utf-8")

    orig_get_base = rg._three_docs_get_base_dir

    def _fake_base(doc_type: str):
        if doc_type == "task":
            return tmp_path
        return orig_get_base(doc_type)

    monkeypatch.setattr(rg, "_three_docs_get_base_dir", _fake_base)

    code, data = handle_get_three_docs_doc(
        "/api/three-docs/task/my-task-topic/checklist", loader
    )
    assert code == 404
    assert "expected_path" in data
    assert data["expected_path"].endswith("checklist.md")


def test_system_single_md_returns_404_for_non_plan_docs(
    loader: _FakeLoader, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """system 타입 단독 .md 파일: context-notes/checklist → 404, plan → 200."""
    import dashboard.routes_get as rg

    # 단독 .md 파일만 생성 (서브디렉토리 없음)
    (tmp_path / "foo.md").write_text("# Foo Doc\n", encoding="utf-8")

    orig_get_base = rg._three_docs_get_base_dir

    def _fake_base(doc_type: str):
        if doc_type == "system":
            return tmp_path
        return orig_get_base(doc_type)

    monkeypatch.setattr(rg, "_three_docs_get_base_dir", _fake_base)

    # context-notes → 404 + expected_path
    code, data = handle_get_three_docs_doc("/api/three-docs/system/foo/context-notes", loader)
    assert code == 404
    assert "expected_path" in data

    # checklist → 404 + expected_path
    code, data = handle_get_three_docs_doc("/api/three-docs/system/foo/checklist", loader)
    assert code == 404
    assert "expected_path" in data

    # plan → 200 + content
    code, data = handle_get_three_docs_doc("/api/three-docs/system/foo/plan", loader)
    assert code == 200
    assert "content" in data


def test_400_response_has_error_key_for_invalid_doc_name(loader: _FakeLoader) -> None:
    """doc 이름이 올바르지 않을 때 → 400 + error 키 + 'Invalid doc' 포함."""
    code, data = handle_get_three_docs_doc(
        "/api/three-docs/system/3docs-schema/invalid-doc", loader
    )
    assert code == 400
    assert "error" in data
    assert "Invalid doc" in data["error"]
