"""
test_spec_compliance.py - spec_compliance verifier 단위 테스트

테스트 항목:
1. task 파일에 체크리스트 있고, 보고서에 모든 키워드 포함 → PASS
2. task 파일에 체크리스트 있고, 보고서에 일부 키워드만 → WARN
3. task 파일 없음 → SKIP
4. 보고서 없음 → SKIP
5. 체크리스트 항목 0개 → PASS
6. - [x] 항목은 이미 완료로 간주 (검증 대상 아님)
"""

import sys
from pathlib import Path

import pytest

# qc 디렉토리를 sys.path에 추가
_QC_DIR = Path(__file__).parent.parent
if str(_QC_DIR) not in sys.path:
    sys.path.insert(0, str(_QC_DIR))

_VERIFIERS_DIR = _QC_DIR / "verifiers"
if str(_VERIFIERS_DIR) not in sys.path:
    sys.path.insert(0, str(_VERIFIERS_DIR))

# spec_compliance 모듈 임포트 시도
try:
    from verifiers import spec_compliance

    _MODULE_AVAILABLE = True
except ImportError:
    try:
        import spec_compliance  # type: ignore

        _MODULE_AVAILABLE = True
    except ImportError:
        _MODULE_AVAILABLE = False

pytestmark = pytest.mark.skipif(
    not _MODULE_AVAILABLE,
    reason="spec_compliance 모듈이 아직 구현되지 않음 — 구현 후 테스트 활성화",
)


# ---------------------------------------------------------------------------
# 헬퍼: 임시 task/report 파일 생성
# ---------------------------------------------------------------------------


def _make_task_file(tmp_path: Path, task_id: str, content: str) -> Path:
    """임시 tasks 디렉토리에 task 파일을 생성하고 경로 반환."""
    tasks_dir = tmp_path / "tasks"
    tasks_dir.mkdir(parents=True, exist_ok=True)
    task_file = tasks_dir / f"{task_id}.md"
    task_file.write_text(content, encoding="utf-8")
    return task_file


def _make_report_file(tmp_path: Path, task_id: str, content: str) -> Path:
    """임시 reports 디렉토리에 보고서 파일을 생성하고 경로 반환."""
    reports_dir = tmp_path / "reports"
    reports_dir.mkdir(parents=True, exist_ok=True)
    report_file = reports_dir / f"{task_id}.md"
    report_file.write_text(content, encoding="utf-8")
    return report_file


# ---------------------------------------------------------------------------
# 1. 체크리스트 있고 보고서에 모든 키워드 포함 → PASS
# ---------------------------------------------------------------------------


class TestAllChecklistItemsCovered:
    """task에 - [ ] 항목이 있고, 보고서에 해당 키워드가 모두 포함되면 PASS."""

    def test_all_covered_status_pass(self, tmp_path):
        task_content = "# 작업\n\n- [ ] API 엔드포인트 구현\n- [ ] 테스트 작성\n"
        report_content = "# 보고서\n\nAPI 엔드포인트 구현 완료. 테스트 작성 완료.\n"

        task_file = _make_task_file(tmp_path, "task-test-pass", task_content)
        report_file = _make_report_file(tmp_path, "task-test-pass", report_content)

        result = spec_compliance.verify(
            "task-test-pass",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert result["status"] == "PASS", f"모든 항목 커버 시 PASS 기대, got: {result}"

    def test_all_covered_has_required_keys(self, tmp_path):
        task_content = "- [ ] 데이터베이스 연결 설정\n"
        report_content = "데이터베이스 연결 설정 완료.\n"

        _make_task_file(tmp_path, "task-keys", task_content)
        _make_report_file(tmp_path, "task-keys", report_content)

        result = spec_compliance.verify(
            "task-keys",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert "status" in result, "결과에 'status' 키가 없음"
        assert "details" in result, "결과에 'details' 키가 없음"
        assert isinstance(result["details"], list), "'details'는 list여야 함"

    def test_all_covered_details_not_empty(self, tmp_path):
        task_content = "- [ ] 파일 검증 구현\n"
        report_content = "파일 검증 구현을 완료했습니다.\n"

        _make_task_file(tmp_path, "task-details", task_content)
        _make_report_file(tmp_path, "task-details", report_content)

        result = spec_compliance.verify(
            "task-details",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert len(result["details"]) > 0, "PASS여도 details에 최소 1개 항목 필요"

    def test_multiple_checklist_items_all_covered(self, tmp_path):
        task_content = "# 작업 지시서\n\n" "- [ ] 스키마 정의\n" "- [ ] 마이그레이션 실행\n" "- [ ] 단위 테스트 추가\n"
        report_content = "## 완료 사항\n" "스키마 정의 완료.\n" "마이그레이션 실행 완료.\n" "단위 테스트 추가 완료.\n"

        _make_task_file(tmp_path, "task-multi", task_content)
        _make_report_file(tmp_path, "task-multi", report_content)

        result = spec_compliance.verify(
            "task-multi",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert result["status"] == "PASS", f"3개 항목 모두 커버 → PASS 기대, got: {result}"


# ---------------------------------------------------------------------------
# 2. 체크리스트 있고 보고서에 일부 키워드만 → WARN
# ---------------------------------------------------------------------------


class TestSomeChecklistItemsUncovered:
    """일부 체크리스트 항목의 키워드가 보고서에 없으면 WARN."""

    def test_partial_covered_status_warn(self, tmp_path):
        task_content = "- [ ] 캐시 레이어 구현\n" "- [ ] 성능 최적화 적용\n"
        # 첫 번째 항목 키워드만 보고서에 있음
        report_content = "캐시 레이어 구현 완료.\n"

        _make_task_file(tmp_path, "task-warn", task_content)
        _make_report_file(tmp_path, "task-warn", report_content)

        result = spec_compliance.verify(
            "task-warn",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert result["status"] == "WARN", f"일부 미커버 → WARN 기대, got: {result}"

    def test_partial_covered_details_mention_uncovered(self, tmp_path):
        task_content = "- [ ] 로깅 시스템 구축\n" "- [ ] 알림 발송 기능\n"
        report_content = "로깅 시스템 구축 완료.\n"

        _make_task_file(tmp_path, "task-warn-detail", task_content)
        _make_report_file(tmp_path, "task-warn-detail", report_content)

        result = spec_compliance.verify(
            "task-warn-detail",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        details_str = "\n".join(result["details"])
        # 미커버 항목이 details에 언급되어야 함
        assert (
            "알림" in details_str
            or "발송" in details_str
            or "uncovered" in details_str.lower()
            or "미커버" in details_str
        ), f"미커버 항목이 details에 언급되지 않음: {details_str}"

    def test_all_uncovered_status_warn(self, tmp_path):
        task_content = "- [ ] 복잡한 리팩토링 작업\n"
        report_content = "작업을 완료했습니다.\n"  # 키워드 매칭 안 됨

        _make_task_file(tmp_path, "task-all-uncovered", task_content)
        _make_report_file(tmp_path, "task-all-uncovered", report_content)

        result = spec_compliance.verify(
            "task-all-uncovered",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert result["status"] == "WARN", f"전체 미커버 → WARN 기대, got: {result}"


# ---------------------------------------------------------------------------
# 3. task 파일 없음 → SKIP
# ---------------------------------------------------------------------------


class TestTaskFileMissing:
    """task 파일이 존재하지 않으면 SKIP."""

    def test_missing_task_file_status_skip(self, tmp_path):
        # task 파일 생성하지 않음
        reports_dir = tmp_path / "reports"
        reports_dir.mkdir(parents=True, exist_ok=True)

        result = spec_compliance.verify(
            "task-nonexistent",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(reports_dir),
        )
        assert result["status"] == "SKIP", f"task 파일 없으면 SKIP 기대, got: {result}"

    def test_missing_task_file_details_mention_reason(self, tmp_path):
        result = spec_compliance.verify(
            "task-no-file",
            tasks_dir=str(tmp_path / "nonexistent_tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert len(result["details"]) > 0, "SKIP이어도 details에 이유 필요"


# ---------------------------------------------------------------------------
# 4. 보고서 없음 → SKIP
# ---------------------------------------------------------------------------


class TestReportFileMissing:
    """보고서 파일이 존재하지 않으면 SKIP."""

    def test_missing_report_file_status_skip(self, tmp_path):
        task_content = "- [ ] 기능 구현\n"
        _make_task_file(tmp_path, "task-no-report", task_content)
        # 보고서 파일 생성하지 않음

        result = spec_compliance.verify(
            "task-no-report",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert result["status"] == "SKIP", f"보고서 없으면 SKIP 기대, got: {result}"

    def test_missing_report_details_mention_reason(self, tmp_path):
        task_content = "- [ ] 기능 구현\n"
        _make_task_file(tmp_path, "task-no-report2", task_content)

        result = spec_compliance.verify(
            "task-no-report2",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "nonexistent_reports"),
        )
        assert len(result["details"]) > 0, "SKIP이어도 details에 이유 필요"


# ---------------------------------------------------------------------------
# 5. 체크리스트 항목 0개 → PASS
# ---------------------------------------------------------------------------


class TestNoChecklistItems:
    """체크리스트 항목이 없으면 (0개) PASS."""

    def test_no_checklist_status_pass(self, tmp_path):
        task_content = "# 작업 지시서\n\n단순 작업입니다.\n"
        report_content = "# 완료 보고서\n\n작업 완료.\n"

        _make_task_file(tmp_path, "task-no-checklist", task_content)
        _make_report_file(tmp_path, "task-no-checklist", report_content)

        result = spec_compliance.verify(
            "task-no-checklist",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert result["status"] == "PASS", f"체크리스트 0개 → PASS 기대, got: {result}"

    def test_only_completed_items_treated_as_no_checklist(self, tmp_path):
        """- [x] 항목만 있으면 미완료 항목 0개이므로 PASS."""
        task_content = "# 작업\n\n- [x] 이미 완료된 항목\n- [x] 또 다른 완료 항목\n"
        report_content = "# 보고서\n\n모든 작업 완료.\n"

        _make_task_file(tmp_path, "task-only-done", task_content)
        _make_report_file(tmp_path, "task-only-done", report_content)

        result = spec_compliance.verify(
            "task-only-done",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert result["status"] == "PASS", f"- [x] 항목만 있으면 PASS 기대, got: {result}"


# ---------------------------------------------------------------------------
# 6. - [x] 항목은 이미 완료로 간주 (검증 대상 아님)
# ---------------------------------------------------------------------------


class TestCompletedItemsIgnored:
    """- [x] 항목은 검증 대상에서 제외되어야 한다."""

    def test_completed_items_not_verified(self, tmp_path):
        task_content = "- [x] 완료된 작업 항목\n" "- [ ] 미완료 작업 항목\n"
        # 보고서에 미완료 항목 키워드만 있음 (완료된 항목 키워드는 없어도 됨)
        report_content = "미완료 작업 항목 처리 완료.\n"

        _make_task_file(tmp_path, "task-mixed", task_content)
        _make_report_file(tmp_path, "task-mixed", report_content)

        result = spec_compliance.verify(
            "task-mixed",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        # [x] 항목은 무시되고 [ ] 항목만 검증 → 모두 커버되면 PASS
        assert result["status"] == "PASS", f"- [x] 항목은 무시되고 - [ ] 항목만 검증해야 함, got: {result}"

    def test_completed_items_not_in_uncovered_list(self, tmp_path):
        task_content = "- [x] 완료된 특별한 작업\n" "- [ ] 진행중인 신규 기능\n"
        # 보고서에 미완료 항목도 없음 → WARN
        report_content = "완료된 특별한 작업 내용.\n"  # [x] 항목 키워드만 있음

        _make_task_file(tmp_path, "task-mixed-warn", task_content)
        _make_report_file(tmp_path, "task-mixed-warn", report_content)

        result = spec_compliance.verify(
            "task-mixed-warn",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        details_str = "\n".join(result["details"])
        # 완료된 항목("완료된 특별한 작업")은 미커버 목록에 없어야 함
        assert (
            "완료된 특별한 작업" not in details_str or result["status"] == "PASS"
        ), f"- [x] 항목이 미커버 목록에 나타나면 안 됨: {details_str}"


# ---------------------------------------------------------------------------
# 7. 반환 형식 공통 규약 검증
# ---------------------------------------------------------------------------


class TestReturnFormatContract:
    """verify() 반환 딕셔너리가 항상 규약된 형식을 따르는지 확인."""

    def test_status_is_valid_enum(self, tmp_path):
        task_content = "- [ ] 기능 구현\n"
        report_content = "기능 구현 완료.\n"

        _make_task_file(tmp_path, "task-format", task_content)
        _make_report_file(tmp_path, "task-format", report_content)

        result = spec_compliance.verify(
            "task-format",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert result["status"] in {
            "PASS",
            "FAIL",
            "WARN",
            "SKIP",
        }, f"status가 유효한 enum 값이 아님: {result['status']}"

    def test_details_is_list_of_strings(self, tmp_path):
        task_content = "- [ ] 기능 구현\n"
        report_content = "기능 구현 완료.\n"

        _make_task_file(tmp_path, "task-format2", task_content)
        _make_report_file(tmp_path, "task-format2", report_content)

        result = spec_compliance.verify(
            "task-format2",
            tasks_dir=str(tmp_path / "tasks"),
            reports_dir=str(tmp_path / "reports"),
        )
        assert isinstance(result["details"], list), "details가 list가 아님"
        for item in result["details"]:
            assert isinstance(item, str), f"details 항목이 str이 아님: {item!r}"

    def test_has_both_required_keys(self, tmp_path):
        result = spec_compliance.verify(
            "task-no-file",
            tasks_dir=str(tmp_path / "no_tasks"),
            reports_dir=str(tmp_path / "no_reports"),
        )
        assert "status" in result
        assert "details" in result
