"""
test_qc_verify.py - qc_verify.py 단위 테스트 (아르고스 작성)

테스트 항목:
1. data_integrity - 정상 케이스 (task-31.1: completed + .done.clear 파일)
2. data_integrity - 비정상 케이스 (task-4.4: completed but no .done file)
3. file_check - 존재하는 파일 → PASS
4. file_check - 없는 파일 → FAIL
5. api_health - --skip api_health 시 SKIP
6. test_runner - --skip test_runner 시 FAIL (MANDATORY)
7. CLI 출력 JSON 형식 검증
8. overall 판정 로직: FAIL 항목 있으면 overall=FAIL
9. critical_gap verifier 테스트
10. MANDATORY blocklist 테스트
11. Skip 로그 추적 테스트
12. .done 파일에 skip 정보 기록 테스트
"""

import json
import os
import subprocess
import sys
import tempfile
from datetime import datetime
from pathlib import Path

import pytest

# verifiers 모듈 경로 설정
_QC_DIR = Path(__file__).parent
if str(_QC_DIR) not in sys.path:
    sys.path.insert(0, str(_QC_DIR))

from qc_verify import _determine_overall, _summarize, build_result, run_check
from verifiers import api_health, critical_gap, data_integrity, file_check, test_runner

_QC_VERIFY_PATH = str(_QC_DIR / "qc_verify.py")


# ---------------------------------------------------------------------------
# 1. data_integrity - 정상 케이스
# ---------------------------------------------------------------------------


class TestDataIntegrityNormal:
    """task-31.1: completed 상태이고 .done.clear 파일이 실제로 존재 → PASS"""

    def test_completed_with_done_file_pass(self):
        result = data_integrity.verify("task-31.1")
        assert result["status"] == "PASS", f"task-31.1은 completed + .done.clear 존재 → PASS 기대, got: {result}"

    def test_result_has_required_keys(self):
        result = data_integrity.verify("task-31.1")
        assert "status" in result
        assert "details" in result
        assert isinstance(result["details"], list)

    def test_details_contain_status_info(self):
        result = data_integrity.verify("task-31.1")
        details_str = "\n".join(result["details"])
        assert "task-31.1" in details_str
        assert "completed" in details_str or "status=" in details_str


# ---------------------------------------------------------------------------
# 2. data_integrity - 비정상 케이스
# ---------------------------------------------------------------------------


class TestDataIntegrityAbnormal:
    """task-4.4: completed 상태지만 .done 파일 없음 → FAIL"""

    def test_completed_without_done_file_fail(self):
        result = data_integrity.verify("task-4.4")
        assert result["status"] == "FAIL", f"task-4.4는 completed이지만 .done 파일 없음 → FAIL 기대, got: {result}"

    def test_fail_details_mention_no_done_file(self):
        result = data_integrity.verify("task-4.4")
        details_str = "\n".join(result["details"])
        # FAIL 메시지에 .done 관련 내용 포함 확인
        assert "FAIL" in details_str or "NOT FOUND" in details_str

    def test_nonexistent_task_fail(self):
        """존재하지 않는 task_id → FAIL"""
        result = data_integrity.verify("task-99999.9")
        assert result["status"] == "FAIL"

    def test_empty_task_id_skip(self):
        """빈 task_id → SKIP"""
        result = data_integrity.verify("")
        assert result["status"] == "SKIP"


# ---------------------------------------------------------------------------
# 3. file_check - 존재하는 파일
# ---------------------------------------------------------------------------


class TestFileCheckExistingFile:
    """실제 존재하는 파일 경로 → PASS"""

    def test_existing_file_pass(self):
        result = file_check.verify(task_id="", file_paths=[_QC_VERIFY_PATH])
        assert result["status"] == "PASS", f"존재하는 파일 {_QC_VERIFY_PATH} → PASS 기대, got: {result}"

    def test_existing_file_details_show_ok(self):
        result = file_check.verify(task_id="", file_paths=[_QC_VERIFY_PATH])
        details_str = "\n".join(result["details"])
        assert "OK" in details_str

    def test_no_files_no_task_skip(self):
        """파일도 없고 task_id도 없으면 SKIP"""
        result = file_check.verify(task_id="", file_paths=[])
        assert result["status"] == "SKIP"


# ---------------------------------------------------------------------------
# 4. file_check - 없는 파일
# ---------------------------------------------------------------------------


class TestFileCheckMissingFile:
    """존재하지 않는 파일 경로 → FAIL"""

    def test_missing_file_fail(self):
        result = file_check.verify(task_id="", file_paths=["/tmp/nonexistent_argos_test_file.py"])
        assert result["status"] == "FAIL", f"없는 파일 경로 → FAIL 기대, got: {result}"

    def test_missing_file_details_show_missing(self):
        result = file_check.verify(task_id="", file_paths=["/tmp/nonexistent_argos_test_file.py"])
        details_str = "\n".join(result["details"])
        assert "MISSING" in details_str


# ---------------------------------------------------------------------------
# 5. api_health - skip 처리
# ---------------------------------------------------------------------------


class TestApiHealthSkip:
    """--skip api_health 플래그 시 SKIP 반환"""

    def test_api_health_skip_via_run_check(self):
        result = run_check(
            name="api_health",
            skip_list=["api_health"],
            task_id="task-4.4",
            api_base="",
            api_endpoints=[],
            test_dir="",
            file_paths=[],
        )
        assert result["status"] == "SKIP"

    def test_api_health_no_base_url_skip(self):
        """base_url 없으면 SKIP (직접 verify 호출)"""
        result = api_health.verify("", ["/api/status"])
        assert result["status"] == "SKIP"

    def test_api_health_no_endpoints_skip(self):
        """endpoints 없으면 SKIP"""
        result = api_health.verify("http://localhost:8000", [])
        assert result["status"] == "SKIP"


# ---------------------------------------------------------------------------
# 6. test_runner - skip 처리
# ---------------------------------------------------------------------------


class TestTestRunnerSkip:
    """--skip test_runner 플래그 시 MANUAL_SKIP 반환 (MANDATORY 아님, 하지만 경고)"""

    def test_test_runner_skip_via_run_check(self):
        result = run_check(
            name="test_runner",
            skip_list=["test_runner"],
            task_id="task-4.4",
            api_base="",
            api_endpoints=[],
            test_dir="",
            file_paths=[],
        )
        # test_runner는 더 이상 MANDATORY가 아님 → MANUAL_SKIP (경고 상태)
        assert result["status"] == "MANUAL_SKIP"

    def test_test_runner_no_dir_skip(self):
        """test_dir 없으면 SKIP (직접 verify 호출)"""
        result = test_runner.verify("")
        assert result["status"] == "SKIP"


# ---------------------------------------------------------------------------
# 7. CLI 출력 JSON 형식 검증
# ---------------------------------------------------------------------------


class TestCLIJsonOutput:
    """qc_verify.py --task-id task-4.4 --skip api_health,test_runner 실행 시 유효한 JSON 출력"""

    def test_cli_outputs_valid_json(self):
        proc = subprocess.run(
            [
                "python3",
                _QC_VERIFY_PATH,
                "--task-id",
                "task-4.4",
                "--skip",
                "api_health,test_runner",
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )
        stdout = proc.stdout.strip()
        assert stdout, f"stdout 비어있음. stderr: {proc.stderr[:300]}"
        try:
            parsed = json.loads(stdout)
        except json.JSONDecodeError as e:
            pytest.fail(f"JSON 파싱 실패: {e}\nstdout: {stdout[:200]}")
        assert isinstance(parsed, dict)

    def test_cli_json_has_required_fields(self):
        proc = subprocess.run(
            [
                "python3",
                _QC_VERIFY_PATH,
                "--task-id",
                "task-4.4",
                "--skip",
                "api_health,test_runner",
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )
        parsed = json.loads(proc.stdout.strip())
        for field in ("task_id", "overall", "checks", "summary", "verified_at"):
            assert field in parsed, f"필드 '{field}' 누락"

    def test_cli_task_id_matches(self):
        proc = subprocess.run(
            [
                "python3",
                _QC_VERIFY_PATH,
                "--task-id",
                "task-4.4",
                "--skip",
                "api_health,test_runner",
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )
        parsed = json.loads(proc.stdout.strip())
        assert parsed["task_id"] == "task-4.4"


# ---------------------------------------------------------------------------
# 8. overall 판정 로직
# ---------------------------------------------------------------------------


class TestOverallDetermination:
    """FAIL 항목이 있으면 overall=FAIL"""

    def test_fail_in_checks_overall_fail(self):
        checks = {
            "api_health": {"status": "SKIP", "details": []},
            "file_check": {"status": "FAIL", "details": ["MISSING: /tmp/x"]},
            "data_integrity": {"status": "PASS", "details": []},
            "test_runner": {"status": "SKIP", "details": []},
        }
        assert _determine_overall(checks, []) == "FAIL"

    def test_all_pass_overall_pass(self):
        checks = {
            "api_health": {"status": "SKIP", "details": []},
            "file_check": {"status": "PASS", "details": []},
            "data_integrity": {"status": "PASS", "details": []},
            "test_runner": {"status": "SKIP", "details": []},
        }
        assert _determine_overall(checks, []) == "PASS"

    def test_warn_only_overall_warn(self):
        checks = {
            "data_integrity": {"status": "WARN", "details": []},
            "file_check": {"status": "PASS", "details": []},
        }
        assert _determine_overall(checks, []) == "WARN"

    def test_build_result_overall_fail_when_has_fail(self):
        checks = {
            "file_check": {"status": "FAIL", "details": ["x"]},
            "data_integrity": {"status": "PASS", "details": []},
        }
        result = build_result("task-test", checks)
        assert result["overall"] == "FAIL"

    def test_cli_exit_code_nonzero_on_fail(self):
        """FAIL인 경우 CLI 종료코드 1"""
        proc = subprocess.run(
            [
                "python3",
                _QC_VERIFY_PATH,
                "--task-id",
                "task-4.4",
                "--skip",
                "api_health,test_runner",
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )
        parsed = json.loads(proc.stdout.strip())
        if parsed["overall"] == "FAIL":
            assert proc.returncode == 1, f"FAIL 시 exit code 1 기대, got {proc.returncode}"


# ---------------------------------------------------------------------------
# 9. critical_gap verifier 테스트
# ---------------------------------------------------------------------------


class TestCriticalGap:
    """critical_gap.verify() - CRITICAL 이슈 미수정 탐지"""

    def _write_report(self, tmp_dir: str, task_id: str, content: str) -> str:
        """임시 디렉토리에 보고서 파일 생성 후 경로 반환."""
        path = os.path.join(tmp_dir, f"{task_id}.md")
        with open(path, "w", encoding="utf-8") as f:
            f.write(content)
        return path

    def test_critical_unresolved_fail(self):
        """CRITICAL 이슈 있고 수정 미확인 → FAIL"""
        content = "## 이슈\n- CRITICAL: DB 연결 실패\n## 기타\n아무 수정 없음\n"
        with tempfile.TemporaryDirectory() as tmp_dir:
            path = self._write_report(tmp_dir, "task-test-fail", content)
            result = critical_gap.verify("task-test-fail", report_path=path)
        assert result["status"] == "FAIL", f"CRITICAL 미수정 → FAIL 기대, got: {result}"
        details_str = "\n".join(result["details"])
        assert "UNRESOLVED" in details_str

    def test_critical_resolved_pass(self):
        """CRITICAL 이슈 있고 수정 확인 → PASS"""
        content = "## 이슈\n" "- CRITICAL: API 응답 오류\n" "## 수정 결과\n" "- 수정 완료: 오류 처리 로직 추가\n"
        with tempfile.TemporaryDirectory() as tmp_dir:
            path = self._write_report(tmp_dir, "task-test-pass", content)
            result = critical_gap.verify("task-test-pass", report_path=path)
        assert result["status"] == "PASS", f"CRITICAL 수정 확인 → PASS 기대, got: {result}"
        details_str = "\n".join(result["details"])
        assert "RESOLVED" in details_str

    def test_no_critical_pass(self):
        """CRITICAL 이슈 없음 → PASS"""
        content = "## 완료 보고서\n정상적으로 구현 완료.\n- 마이너 개선 사항만 있음\n"
        with tempfile.TemporaryDirectory() as tmp_dir:
            path = self._write_report(tmp_dir, "task-test-no-critical", content)
            result = critical_gap.verify("task-test-no-critical", report_path=path)
        assert result["status"] == "PASS", f"CRITICAL 없음 → PASS 기대, got: {result}"

    def test_report_not_found_skip(self):
        """보고서 파일 없음 → SKIP"""
        result = critical_gap.verify("task-nonexistent-99999")
        assert result["status"] == "SKIP", f"파일 없음 → SKIP 기대, got: {result}"
        details_str = "\n".join(result["details"])
        assert "not found" in details_str.lower() or "Report not found" in details_str

    def test_empty_report_pass(self):
        """빈 보고서 → PASS"""
        with tempfile.TemporaryDirectory() as tmp_dir:
            path = self._write_report(tmp_dir, "task-test-empty", "")
            result = critical_gap.verify("task-test-empty", report_path=path)
        assert result["status"] == "PASS", f"빈 보고서 → PASS 기대, got: {result}"

    def test_critical_korean_keyword_blocker(self):
        """'블로커' 키워드 탐지 → FAIL"""
        content = "## 이슈\n- 블로커: 빌드 실패\n## 마무리\n별도 수정 없음\n"
        with tempfile.TemporaryDirectory() as tmp_dir:
            path = self._write_report(tmp_dir, "task-test-blocker", content)
            result = critical_gap.verify("task-test-blocker", report_path=path)
        assert result["status"] == "FAIL"

    def test_critical_korean_keyword_serious(self):
        """'심각' 키워드 탐지 → FAIL"""
        content = "## 문제\n심각한 메모리 누수 발견\n"
        with tempfile.TemporaryDirectory() as tmp_dir:
            path = self._write_report(tmp_dir, "task-test-serious", content)
            result = critical_gap.verify("task-test-serious", report_path=path)
        assert result["status"] == "FAIL"

    def test_critical_resolved_by_english_keyword(self):
        """'fixed' 영어 키워드로 수정 확인 → PASS"""
        content = (
            "## Issues\n" "- CRITICAL: race condition detected\n" "## Resolution\n" "- fixed by adding mutex lock\n"
        )
        with tempfile.TemporaryDirectory() as tmp_dir:
            path = self._write_report(tmp_dir, "task-test-fixed", content)
            result = critical_gap.verify("task-test-fixed", report_path=path)
        assert result["status"] == "PASS"

    def test_result_has_required_keys(self):
        """결과 dict에 status, details 키 존재"""
        result = critical_gap.verify("task-nonexistent-99999")
        assert "status" in result
        assert "details" in result
        assert isinstance(result["details"], list)

    def test_critical_gap_in_run_check(self):
        """run_check()에서 critical_gap 케이스 정상 호출"""
        result = run_check(
            name="critical_gap",
            skip_list=[],
            task_id="task-nonexistent-99999",
            api_base="",
            api_endpoints=[],
            test_dir="",
            file_paths=[],
        )
        # 보고서 없으면 SKIP
        assert result["status"] == "SKIP"

    def test_critical_gap_skip_via_skip_list(self):
        """--skip critical_gap 플래그 시 SKIP 반환"""
        result = run_check(
            name="critical_gap",
            skip_list=["critical_gap"],
            task_id="task-4.4",
            api_base="",
            api_endpoints=[],
            test_dir="",
            file_paths=[],
        )
        assert result["status"] == "SKIP"

    def test_critical_gap_in_all_checks(self):
        """ALL_CHECKS 목록에 'critical_gap'이 포함되어 있어야 함"""
        from qc_verify import ALL_CHECKS

        assert "critical_gap" in ALL_CHECKS


# ---------------------------------------------------------------------------
# 10. MANDATORY blocklist 테스트
# ---------------------------------------------------------------------------


class TestMandatoryBlocklist:
    """MANDATORY 체크는 --skip으로 우회 불가"""

    def test_mandatory_check_cannot_be_skipped(self):
        """test_runner는 더 이상 MANDATORY가 아님 → skip 시도 시 MANUAL_SKIP (경고)"""
        result = run_check(
            name="test_runner",
            skip_list=["test_runner"],
            task_id="task-test",
            api_base="",
            api_endpoints=[],
            test_dir="",
            file_paths=[],
        )
        assert result["status"] == "MANUAL_SKIP"
        # MANUAL_SKIP details에 경고 메시지 포함
        assert any("MANUAL_SKIP" in d or "경고" in d for d in result["details"])

    def test_data_integrity_mandatory(self):
        """data_integrity도 MANDATORY"""
        result = run_check(
            name="data_integrity",
            skip_list=["data_integrity"],
            task_id="task-test",
            api_base="",
            api_endpoints=[],
            test_dir="",
            file_paths=[],
        )
        assert result["status"] == "FAIL"

    def test_file_check_mandatory(self):
        """file_check도 MANDATORY"""
        result = run_check(
            name="file_check",
            skip_list=["file_check"],
            task_id="task-test",
            api_base="",
            api_endpoints=[],
            test_dir="",
            file_paths=[],
        )
        assert result["status"] == "FAIL"

    def test_non_mandatory_still_skippable(self):
        """api_health는 MANDATORY 아님 → skip 가능"""
        result = run_check(
            name="api_health",
            skip_list=["api_health"],
            task_id="task-test",
            api_base="",
            api_endpoints=[],
            test_dir="",
            file_paths=[],
        )
        assert result["status"] == "SKIP"

    def test_style_check_still_skippable(self):
        """style_check는 MANDATORY 아님 → skip 가능"""
        result = run_check(
            name="style_check",
            skip_list=["style_check"],
            task_id="task-test",
            api_base="",
            api_endpoints=[],
            test_dir="",
            file_paths=[],
        )
        assert result["status"] == "SKIP"

    def test_mandatory_checks_constant_exists(self):
        """MANDATORY_CHECKS 상수가 존재하고 data_integrity, file_check, spec_compliance 포함 (test_runner 제외)"""
        from qc_verify import MANDATORY_CHECKS

        assert isinstance(MANDATORY_CHECKS, set)
        assert "data_integrity" in MANDATORY_CHECKS
        assert "file_check" in MANDATORY_CHECKS
        assert "spec_compliance" in MANDATORY_CHECKS
        assert "test_runner" not in MANDATORY_CHECKS


# ---------------------------------------------------------------------------
# 11. Skip 로그 추적 테스트
# ---------------------------------------------------------------------------


class TestSkipLogTracking:
    """--skip 사용 시 JSONL 로그 기록"""

    def test_skip_creates_log_entry(self):
        """skip 사용 시 로그 파일에 기록"""
        log_path = "/home/jay/workspace/memory/logs/qc-skip-log.jsonl"
        before_count = 0
        if os.path.exists(log_path):
            with open(log_path) as f:
                before_count = sum(1 for _ in f)

        proc = subprocess.run(
            [
                "python3",
                _QC_VERIFY_PATH,
                "--task-id",
                "test-skip-log",
                "--skip",
                "pyright_check,style_check",
                "--team",
                "dev1-team",
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )

        assert os.path.exists(log_path), "Skip 로그 파일이 생성되어야 함"
        with open(log_path) as f:
            lines = f.readlines()
        assert len(lines) > before_count, "새 로그 엔트리가 추가되어야 함"

        last_entry = json.loads(lines[-1])
        assert last_entry["task_id"] == "test-skip-log"
        assert "pyright_check" in last_entry["skipped_checks"]
        assert last_entry["team"] == "dev1-team"

    def test_skip_log_contains_blocked_mandatory(self):
        """MANDATORY skip 시도 시 blocked_mandatory 필드에 기록 (data_integrity 사용)"""
        log_path = "/home/jay/workspace/memory/logs/qc-skip-log.jsonl"

        proc = subprocess.run(
            [
                "python3",
                _QC_VERIFY_PATH,
                "--task-id",
                "test-mandatory-log",
                "--skip",
                "data_integrity,style_check",
                "--team",
                "dev1-team",
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )

        with open(log_path) as f:
            lines = f.readlines()
        last_entry = json.loads(lines[-1])
        # data_integrity는 여전히 MANDATORY → blocked_mandatory에 기록
        assert "data_integrity" in last_entry["blocked_mandatory"]
        # test_runner는 MANDATORY가 아니므로 blocked_mandatory에 없음
        assert "test_runner" not in last_entry["blocked_mandatory"]

    def test_no_skip_no_log(self):
        """skip 없으면 로그 추가 안 됨"""
        log_path = "/home/jay/workspace/memory/logs/qc-skip-log.jsonl"
        before_count = 0
        if os.path.exists(log_path):
            with open(log_path) as f:
                before_count = sum(1 for _ in f)

        proc = subprocess.run(
            [
                "python3",
                _QC_VERIFY_PATH,
                "--task-id",
                "test-no-skip",
                "--skip",
                "",
                "--team",
                "dev1-team",
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )

        if os.path.exists(log_path):
            with open(log_path) as f:
                after_count = sum(1 for _ in f)
            assert after_count == before_count, "skip 없으면 로그가 추가되면 안 됨"


# ---------------------------------------------------------------------------
# 12. .done 파일에 skip 정보 기록 테스트
# ---------------------------------------------------------------------------


class TestDoneFileSkipInfo:
    """.done 파일에 skipped_checks 정보 포함"""

    def test_done_file_contains_skipped_checks(self):
        """--gate 모드에서 .done에 skipped_checks 필드 포함"""
        task_id = f"test-done-skip-{int(datetime.now().timestamp())}"
        done_path = f"/home/jay/workspace/memory/events/{task_id}.done"

        try:
            proc = subprocess.run(
                [
                    "python3",
                    _QC_VERIFY_PATH,
                    "--task-id",
                    task_id,
                    "--skip",
                    "pyright_check,style_check",
                    "--gate",
                    "--team",
                    "dev1-team",
                ],
                capture_output=True,
                text=True,
                timeout=30,
            )

            if os.path.exists(done_path):
                with open(done_path) as f:
                    done_data = json.load(f)
                assert "skipped_checks" in done_data, ".done에 skipped_checks 필드 필요"
                assert "pyright_check" in done_data["skipped_checks"]
                assert "style_check" in done_data["skipped_checks"]
        finally:
            if os.path.exists(done_path):
                os.remove(done_path)

    def test_done_file_no_skip_empty_list(self):
        """skip 없으면 skipped_checks는 빈 리스트"""
        task_id = f"test-done-noskip-{int(datetime.now().timestamp())}"
        done_path = f"/home/jay/workspace/memory/events/{task_id}.done"

        try:
            proc = subprocess.run(
                [
                    "python3",
                    _QC_VERIFY_PATH,
                    "--task-id",
                    task_id,
                    "--gate",
                    "--team",
                    "dev1-team",
                ],
                capture_output=True,
                text=True,
                timeout=30,
            )

            if os.path.exists(done_path):
                with open(done_path) as f:
                    done_data = json.load(f)
                assert done_data.get("skipped_checks") == [] or "skipped_checks" in done_data
        finally:
            if os.path.exists(done_path):
                os.remove(done_path)
