"""
test_memory_check.py

utils/memory_check.py — Memory Check Confirmation Number System 단위 테스트
작성자: 아르고스 (개발1팀 테스트 전문가)

테스트 항목:
- parse_star_items(): MEMORY.md에서 ★ 포함 라인 추출
- find_feedback_files(): feedback_*.md 패턴 파일 목록 반환
- match_feedback_to_task(): task 키워드 기반 feedback 파일 매칭
- get_next_mc_id(): 로그 기반 다음 MC-XXXX ID 생성
- issue_mc(): MC 발급 + 로그 파일 기록
- get_unchecked_tasks(): running 태스크 중 MC 미발급 목록 반환

격리: pytest tmp_path fixture로 파일시스템 완전 격리
"""

import json
import sys
from pathlib import Path

import pytest

# ---------------------------------------------------------------------------
# sys.path 설정 — workspace 루트를 포함시켜 utils 패키지를 임포트 가능하게 함
# ---------------------------------------------------------------------------

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from utils.memory_check import (  # noqa: E402
    find_feedback_files,
    get_next_mc_id,
    get_unchecked_tasks,
    issue_mc,
    match_feedback_to_task,
    parse_star_items,
)


# ===========================================================================
# 1. parse_star_items
# ===========================================================================


class TestParseStarItems:
    """parse_star_items(memory_path) 동작 검증"""

    def test_parse_star_items_normal(self, tmp_path):
        """★ 포함 라인 3개가 있는 MEMORY.md → 3개 반환"""
        memory_md = tmp_path / "MEMORY.md"
        memory_md.write_text(
            "# 팀 메모리\n"
            "- 일반 항목\n"
            "★ [팀 라우팅 가이드](feedback_design_team_routing_v2.md)\n"
            "★ [배포 체크리스트](deploy_checklist.md)\n"
            "★ 코드 리뷰 필수 확인 사항\n"
            "## 기타\n"
            "- 참고 링크\n",
            encoding="utf-8",
        )

        result = parse_star_items(memory_md)

        assert isinstance(result, list)
        assert len(result) == 3

    def test_parse_star_items_empty_file(self, tmp_path):
        """빈 파일 → 빈 리스트 반환"""
        memory_md = tmp_path / "MEMORY.md"
        memory_md.write_text("", encoding="utf-8")

        result = parse_star_items(memory_md)

        assert result == []

    def test_parse_star_items_no_file(self, tmp_path):
        """파일이 존재하지 않으면 빈 리스트 반환"""
        non_existent = tmp_path / "MEMORY.md"

        result = parse_star_items(non_existent)

        assert result == []

    def test_parse_star_items_markdown_stripped(self, tmp_path):
        """마크다운 서식(링크, 볼드 등)이 제거된 텍스트로 반환"""
        memory_md = tmp_path / "MEMORY.md"
        memory_md.write_text(
            "★ [팀 라우팅 가이드](feedback_design_team_routing_v2.md)\n"
            "★ **코드 리뷰** 필수 확인\n",
            encoding="utf-8",
        )

        result = parse_star_items(memory_md)

        assert len(result) == 2
        # 링크 마크다운 제거 확인: "[텍스트](url)" 형식 없어야 함
        for item in result:
            assert "](" not in item, f"마크다운 링크 서식이 남아 있음: {item!r}"


# ===========================================================================
# 2. find_feedback_files
# ===========================================================================


class TestFindFeedbackFiles:
    """find_feedback_files(feedback_dir) 동작 검증"""

    def test_find_feedback_files_normal(self, tmp_path):
        """feedback_*.md 3개 + 다른 파일들 → 3개만 반환"""
        feedback_dir = tmp_path / "feedback"
        feedback_dir.mkdir()

        # feedback_*.md 파일 3개 생성
        (feedback_dir / "feedback_design_team_routing.md").write_text("내용1", encoding="utf-8")
        (feedback_dir / "feedback_deploy_checklist.md").write_text("내용2", encoding="utf-8")
        (feedback_dir / "feedback_api_review_v2.md").write_text("내용3", encoding="utf-8")

        # 다른 패턴의 파일들 (포함되면 안 됨)
        (feedback_dir / "summary.md").write_text("요약", encoding="utf-8")
        (feedback_dir / "notes.txt").write_text("메모", encoding="utf-8")
        (feedback_dir / "feedback_draft.txt").write_text("드래프트", encoding="utf-8")

        result = find_feedback_files(feedback_dir)

        assert isinstance(result, list)
        assert len(result) == 3
        for name in result:
            assert name.startswith("feedback_")
            assert name.endswith(".md")

    def test_find_feedback_files_empty_dir(self, tmp_path):
        """빈 디렉토리 → 빈 리스트 반환"""
        empty_dir = tmp_path / "feedback"
        empty_dir.mkdir()

        result = find_feedback_files(empty_dir)

        assert result == []

    def test_find_feedback_files_no_dir(self, tmp_path):
        """디렉토리가 존재하지 않으면 빈 리스트 반환"""
        non_existent_dir = tmp_path / "no_such_feedback_dir"

        result = find_feedback_files(non_existent_dir)

        assert result == []


# ===========================================================================
# 3. match_feedback_to_task
# ===========================================================================


class TestMatchFeedbackToTask:
    """match_feedback_to_task(task_desc, feedback_dir) 동작 검증"""

    def test_match_feedback_to_task(self, tmp_path):
        """'design routing' task → feedback_design_team_routing.md 매칭"""
        feedback_dir = tmp_path / "feedback"
        feedback_dir.mkdir()

        # 매칭 대상 파일
        (feedback_dir / "feedback_design_team_routing.md").write_text(
            "# 디자인 팀 라우팅 피드백", encoding="utf-8"
        )
        # 매칭 안 되는 파일
        (feedback_dir / "feedback_api_gateway_setup.md").write_text(
            "# API 게이트웨이 설정", encoding="utf-8"
        )

        result = match_feedback_to_task("design routing 관련 작업", feedback_dir)

        assert isinstance(result, list)
        assert "feedback_design_team_routing.md" in result
        assert "feedback_api_gateway_setup.md" not in result

    def test_match_feedback_to_task_no_match(self, tmp_path):
        """관련 없는 task_desc → 빈 리스트 반환"""
        feedback_dir = tmp_path / "feedback"
        feedback_dir.mkdir()

        (feedback_dir / "feedback_design_team_routing.md").write_text(
            "# 디자인 팀 라우팅", encoding="utf-8"
        )

        result = match_feedback_to_task("데이터베이스 마이그레이션 스크립트 작성", feedback_dir)

        assert isinstance(result, list)
        # "design", "team", "routing" 키워드가 없으므로 매칭 없음
        assert "feedback_design_team_routing.md" not in result

    def test_match_feedback_to_task_keyword_extraction(self, tmp_path):
        """파일명 feedback_design_team_routing_v2.md → ["design", "team", "routing"] 키워드 추출"""
        feedback_dir = tmp_path / "feedback"
        feedback_dir.mkdir()

        (feedback_dir / "feedback_design_team_routing_v2.md").write_text(
            "# 피드백 내용", encoding="utf-8"
        )

        # v2는 버전 접미사로 키워드에서 제외될 수 있으므로 핵심 키워드만 사용
        result = match_feedback_to_task("team routing 검토", feedback_dir)

        assert isinstance(result, list)
        assert "feedback_design_team_routing_v2.md" in result

    def test_match_feedback_to_task_empty_dir(self, tmp_path):
        """feedback 디렉토리가 없으면 빈 리스트 반환"""
        non_existent_dir = tmp_path / "no_feedback"

        result = match_feedback_to_task("design routing", non_existent_dir)

        assert result == []


# ===========================================================================
# 4. get_next_mc_id
# ===========================================================================


class TestGetNextMcId:
    """get_next_mc_id(log_path) 동작 검증"""

    def test_get_next_mc_id_no_file(self, tmp_path):
        """로그 파일이 없으면 'MC-0001' 반환"""
        log_path = tmp_path / "memory-check-log.json"

        result = get_next_mc_id(log_path)

        assert result == "MC-0001"

    def test_get_next_mc_id_empty_checks(self, tmp_path):
        """checks 배열이 비어 있으면 'MC-0001' 반환"""
        log_path = tmp_path / "memory-check-log.json"
        log_path.write_text(json.dumps({"checks": []}), encoding="utf-8")

        result = get_next_mc_id(log_path)

        assert result == "MC-0001"

    def test_get_next_mc_id_existing(self, tmp_path):
        """마지막 MC-0005가 기록되어 있으면 'MC-0006' 반환"""
        log_path = tmp_path / "memory-check-log.json"
        log_data = {
            "checks": [
                {"mc_id": "MC-0001", "task_id": "task-001"},
                {"mc_id": "MC-0002", "task_id": "task-002"},
                {"mc_id": "MC-0003", "task_id": "task-003"},
                {"mc_id": "MC-0004", "task_id": "task-004"},
                {"mc_id": "MC-0005", "task_id": "task-005"},
            ]
        }
        log_path.write_text(json.dumps(log_data), encoding="utf-8")

        result = get_next_mc_id(log_path)

        assert result == "MC-0006"

    def test_get_next_mc_id_format(self, tmp_path):
        """MC ID는 항상 4자리 zero-padding 형식이어야 함"""
        log_path = tmp_path / "memory-check-log.json"
        log_data = {
            "checks": [{"mc_id": "MC-0009", "task_id": "task-009"}]
        }
        log_path.write_text(json.dumps(log_data), encoding="utf-8")

        result = get_next_mc_id(log_path)

        assert result == "MC-0010"
        # 형식 검증: MC-XXXX (X는 4자리 숫자)
        assert result.startswith("MC-")
        assert len(result) == 7  # "MC-" + 4자리


# ===========================================================================
# 5. issue_mc
# ===========================================================================


class TestIssueMc:
    """issue_mc(...) 동작 검증"""

    def _make_memory_md(self, tmp_path, star_count=2):
        """테스트용 MEMORY.md 생성"""
        memory_path = tmp_path / "MEMORY.md"
        lines = ["# 팀 메모리\n"]
        for i in range(star_count):
            lines.append(f"★ 체크 항목 {i + 1}\n")
        memory_path.write_text("".join(lines), encoding="utf-8")
        return memory_path

    def test_issue_mc_creates_log(self, tmp_path):
        """로그 파일이 없을 때 issue_mc() 호출 → 로그 파일 생성 + MC 기록 확인"""
        log_path = tmp_path / "memory-check-log.json"
        memory_path = self._make_memory_md(tmp_path, star_count=2)
        feedback_dir = tmp_path / "feedback"
        feedback_dir.mkdir()

        result = issue_mc(
            task_id="task-001",
            task_desc="API 라우팅 설계 작업",
            log_path=log_path,
            memory_path=memory_path,
            feedback_dir=feedback_dir,
        )

        # 반환값 구조 검증
        assert isinstance(result, dict)
        assert "mc_id" in result
        assert result["mc_id"] == "MC-0001"
        assert result["task_id"] == "task-001"
        assert "star_items_checked" in result
        assert result["star_items_checked"] == 2
        assert "memory_items_read" in result
        assert isinstance(result["memory_items_read"], list)

        # 로그 파일 생성 확인
        assert log_path.exists()
        log_data = json.loads(log_path.read_text(encoding="utf-8"))
        assert "checks" in log_data
        assert len(log_data["checks"]) == 1
        assert log_data["checks"][0]["mc_id"] == "MC-0001"
        assert log_data["checks"][0]["task_id"] == "task-001"

    def test_issue_mc_appends_to_existing(self, tmp_path):
        """기존 로그에 이미 MC가 있을 때 issue_mc() → 기존 항목 유지 + 새 항목 추가"""
        log_path = tmp_path / "memory-check-log.json"
        memory_path = self._make_memory_md(tmp_path, star_count=1)
        feedback_dir = tmp_path / "feedback"
        feedback_dir.mkdir()

        # 기존 로그 데이터 준비
        existing_log = {
            "checks": [
                {
                    "mc_id": "MC-0001",
                    "task_id": "task-prev",
                    "star_items_checked": 3,
                    "memory_items_read": ["항목A"],
                }
            ]
        }
        log_path.write_text(json.dumps(existing_log), encoding="utf-8")

        result = issue_mc(
            task_id="task-002",
            task_desc="새 기능 배포 작업",
            log_path=log_path,
            memory_path=memory_path,
            feedback_dir=feedback_dir,
        )

        # 새 MC ID는 MC-0002
        assert result["mc_id"] == "MC-0002"
        assert result["task_id"] == "task-002"

        # 로그에 기존 항목 + 새 항목이 모두 존재해야 함
        log_data = json.loads(log_path.read_text(encoding="utf-8"))
        assert len(log_data["checks"]) == 2
        mc_ids = [c["mc_id"] for c in log_data["checks"]]
        assert "MC-0001" in mc_ids
        assert "MC-0002" in mc_ids

    def test_issue_mc_return_structure(self, tmp_path):
        """issue_mc() 반환 딕셔너리의 필수 키 검증"""
        log_path = tmp_path / "memory-check-log.json"
        memory_path = self._make_memory_md(tmp_path, star_count=3)
        feedback_dir = tmp_path / "feedback"
        feedback_dir.mkdir()

        result = issue_mc(
            task_id="task-003",
            task_desc="테스트 태스크",
            log_path=log_path,
            memory_path=memory_path,
            feedback_dir=feedback_dir,
        )

        required_keys = {"mc_id", "task_id", "timestamp", "star_items_checked", "memory_items_read"}
        assert required_keys.issubset(result.keys()), (
            f"반환 딕셔너리에 누락된 키: {required_keys - result.keys()}"
        )


# ===========================================================================
# 6. get_unchecked_tasks
# ===========================================================================


class TestGetUncheckedTasks:
    """get_unchecked_tasks(log_path, timers_path) 동작 검증"""

    def _make_timers(self, tmp_path, running_tasks: list[dict]) -> Path:
        """task-timers.json 생성 헬퍼. task-timers는 {task_id: entry} dict 형식."""
        timers_path = tmp_path / "task-timers.json"
        tasks_dict = {}
        for t in running_tasks:
            tid = t["task_id"]
            tasks_dict[tid] = {k: v for k, v in t.items() if k != "task_id"}
        data = {"tasks": tasks_dict}
        timers_path.write_text(json.dumps(data), encoding="utf-8")
        return timers_path

    def _make_log(self, tmp_path, checked_task_ids: list[str]) -> Path:
        """memory-check-log.json 생성 헬퍼"""
        log_path = tmp_path / "memory-check-log.json"
        checks = [
            {"mc_id": f"MC-{i + 1:04d}", "task_id": tid}
            for i, tid in enumerate(checked_task_ids)
        ]
        log_path.write_text(json.dumps({"checks": checks}), encoding="utf-8")
        return log_path

    def test_get_unchecked_tasks_all_checked(self, tmp_path):
        """running 태스크 2개 모두 MC 발급 완료 → 빈 리스트 반환"""
        running_tasks = [
            {"task_id": "task-001", "team_id": "dev1", "status": "running"},
            {"task_id": "task-002", "team_id": "dev2", "status": "running"},
        ]
        timers_path = self._make_timers(tmp_path, running_tasks)
        log_path = self._make_log(tmp_path, ["task-001", "task-002"])

        result = get_unchecked_tasks(log_path, timers_path)

        assert result == []

    def test_get_unchecked_tasks_some_missing(self, tmp_path):
        """running 태스크 2개 중 1개만 MC 없음 → 해당 1개 반환"""
        running_tasks = [
            {"task_id": "task-001", "team_id": "dev1", "status": "running"},
            {"task_id": "task-002", "team_id": "dev2", "status": "running"},
        ]
        timers_path = self._make_timers(tmp_path, running_tasks)
        # task-001만 체크됨, task-002는 미체크
        log_path = self._make_log(tmp_path, ["task-001"])

        result = get_unchecked_tasks(log_path, timers_path)

        assert isinstance(result, list)
        assert len(result) == 1
        assert result[0]["task_id"] == "task-002"
        assert result[0]["team_id"] == "dev2"

    def test_get_unchecked_tasks_no_timers(self, tmp_path):
        """task-timers.json이 없으면 빈 리스트 반환"""
        non_existent_timers = tmp_path / "task-timers.json"
        log_path = self._make_log(tmp_path, [])

        result = get_unchecked_tasks(log_path, non_existent_timers)

        assert result == []

    def test_get_unchecked_tasks_no_log(self, tmp_path):
        """memory-check-log.json이 없으면 running 태스크 전부 반환"""
        running_tasks = [
            {"task_id": "task-001", "team_id": "dev1", "status": "running"},
            {"task_id": "task-002", "team_id": "dev2", "status": "running"},
        ]
        timers_path = self._make_timers(tmp_path, running_tasks)
        non_existent_log = tmp_path / "memory-check-log.json"

        result = get_unchecked_tasks(non_existent_log, timers_path)

        assert isinstance(result, list)
        assert len(result) == 2
        task_ids = {t["task_id"] for t in result}
        assert task_ids == {"task-001", "task-002"}

    def test_get_unchecked_tasks_only_running_included(self, tmp_path):
        """status가 running인 태스크만 대상이어야 함 (completed 제외)"""
        all_tasks = [
            {"task_id": "task-001", "team_id": "dev1", "status": "running"},
            {"task_id": "task-002", "team_id": "dev2", "status": "completed"},
            {"task_id": "task-003", "team_id": "dev3", "status": "running"},
        ]
        timers_path = self._make_timers(tmp_path, all_tasks)
        # 아무것도 체크되지 않은 로그
        log_path = self._make_log(tmp_path, [])

        result = get_unchecked_tasks(log_path, timers_path)

        # completed 태스크(task-002)는 결과에 포함되지 않아야 함
        task_ids = {t["task_id"] for t in result}
        assert "task-002" not in task_ids
        assert "task-001" in task_ids
        assert "task-003" in task_ids
