"""
Memory Check Confirmation Number System (MC)

매 task 위임 시 MC-XXXX를 발급하여 메모리 읽기 여부를 추적한다.
MEMORY.md의 ★ 항목과 관련 피드백 파일 목록을 로그에 기록한다.
"""

import fcntl
import json
import os
import re
from datetime import datetime
from pathlib import Path
from typing import Any

try:
    from utils.logger import get_logger

    logger = get_logger(__name__)
except ImportError:
    import logging

    logger = logging.getLogger(__name__)

# 기본 경로 상수
_WORKSPACE = Path(os.environ.get("WORKSPACE_ROOT", "/home/jay/workspace"))
_DEFAULT_MEMORY_PATH = _WORKSPACE / "memory" / "MEMORY.md"
_DEFAULT_LOG_PATH = _WORKSPACE / "memory" / "memory-check-log.json"
_DEFAULT_TIMERS_PATH = _WORKSPACE / "memory" / "task-timers.json"

# Anu Claude auto-memory 경로 (피드백 파일 위치)
_ANU_MEMORY_DIR = Path(
    os.environ.get(
        "MEMORY_CHECK_FEEDBACK_DIR",
        str(Path.home() / ".claude" / "projects" / "-home-jay--cokacdir-workspace-autoset" / "memory"),
    )
)
_ANU_MEMORY_PATH = _ANU_MEMORY_DIR / "MEMORY.md"


def parse_star_items(memory_path: Path) -> list[str]:
    """memory_path 파일에서 ★ 포함 라인을 추출하고 마크다운 서식을 제거한다.

    Args:
        memory_path: 읽을 MEMORY.md 파일 경로

    Returns:
        ★ 항목 문자열 리스트 (마크다운 서식 제거됨). 파일 없거나 에러 시 빈 리스트.
    """
    try:
        if not memory_path.exists():
            return []
        content = memory_path.read_text(encoding="utf-8")
    except Exception as e:
        logger.debug(f"[parse_star_items] 파일 읽기 실패 ({memory_path}): {e}")
        return []

    results: list[str] = []
    try:
        for line in content.split("\n"):
            if "★" not in line:
                continue
            # 마크다운 서식 제거
            cleaned = line.strip()
            # 헤딩 기호 제거 (##, ###, ...)
            cleaned = re.sub(r"^#{1,6}\s*", "", cleaned)
            # 볼드/이탤릭 제거 (**, __, *, _)
            cleaned = re.sub(r"\*{1,3}([^*]*)\*{1,3}", r"\1", cleaned)
            cleaned = re.sub(r"_{1,3}([^_]*)_{1,3}", r"\1", cleaned)
            # 취소선 제거 (~~text~~)
            cleaned = re.sub(r"~~([^~]*)~~", r"\1", cleaned)
            # 인라인 코드 제거 (`code`)
            cleaned = re.sub(r"`([^`]*)`", r"\1", cleaned)
            # 링크 문법 제거 ([text](url) → text)
            cleaned = re.sub(r"\[([^\]]*)\]\([^)]*\)", r"\1", cleaned)
            # 이미지 문법 제거 (![alt](url) → alt)
            cleaned = re.sub(r"!\[([^\]]*)\]\([^)]*\)", r"\1", cleaned)
            # 리스트 기호 제거 (-, *, +)
            cleaned = re.sub(r"^[-*+]\s+", "", cleaned)
            # 번호 리스트 제거 (1. 2. ...)
            cleaned = re.sub(r"^\d+\.\s+", "", cleaned)
            # 연속 공백 정리
            cleaned = re.sub(r"\s+", " ", cleaned).strip()
            if cleaned:
                results.append(cleaned)
    except Exception as e:
        logger.debug(f"[parse_star_items] 파싱 중 에러 ({memory_path}): {e}")
        return []

    return results


def find_feedback_files(feedback_dir: Path) -> list[str]:
    """feedback_dir에서 feedback_*.md 패턴에 맞는 파일명 리스트를 반환한다.

    Args:
        feedback_dir: 피드백 파일이 있는 디렉토리 경로

    Returns:
        파일명 리스트 (경로 없이 파일명만). 디렉토리 없거나 에러 시 빈 리스트.
    """
    try:
        if not feedback_dir.exists():
            return []
        return sorted(p.name for p in feedback_dir.glob("feedback_*.md"))
    except Exception as e:
        logger.debug(f"[find_feedback_files] 디렉토리 읽기 실패 ({feedback_dir}): {e}")
        return []


def match_feedback_to_task(task_desc: str, feedback_dir: Path) -> list[str]:
    """task_desc 내용에 키워드 매칭되는 피드백 파일 목록을 반환한다.

    파일명에서 키워드를 추출하여 task_desc와 2개 이상 매칭 시 포함.
    키워드가 1개인 짧은 파일명은 1개 매칭으로도 포함.

    Args:
        task_desc: 작업 설명 문자열
        feedback_dir: 피드백 파일 디렉토리 경로

    Returns:
        매칭된 피드백 파일명 리스트
    """
    try:
        feedback_files = find_feedback_files(feedback_dir)
        if not feedback_files:
            return []

        task_lower = task_desc.lower()
        matched: list[str] = []

        for fname in feedback_files:
            # feedback_ 접두사 제거, .md 확장자 제거
            stem = fname
            if stem.startswith("feedback_"):
                stem = stem[len("feedback_") :]
            if stem.endswith(".md"):
                stem = stem[:-3]

            # _로 분할 후 숫자/버전 제거 (v2, v3, 숫자만인 토큰 등)
            parts = stem.split("_")
            keywords = [p for p in parts if p and not re.match(r"^v?\d+$", p, re.IGNORECASE)]

            if not keywords:
                continue

            # task_desc에서 키워드 매칭 카운트
            match_count = sum(1 for kw in keywords if kw in task_lower)

            # 키워드가 1개뿐인 짧은 파일명은 1개 매칭으로도 포함
            threshold = 1 if len(keywords) <= 1 else 2
            if match_count >= threshold:
                matched.append(fname)

        return matched
    except Exception as e:
        logger.debug(f"[match_feedback_to_task] 매칭 중 에러: {e}")
        return []


def get_next_mc_id(log_path: Path) -> str:
    """log_path의 JSON 파일에서 다음 MC ID를 계산하여 반환한다.

    checks 배열의 마지막 mc_id에서 숫자를 추출해 +1 한다.

    Args:
        log_path: memory-check-log.json 파일 경로

    Returns:
        "MC-XXXX" 형식의 ID 문자열 (제로패딩 4자리, 9999 초과 시 그대로 증가)
    """
    try:
        if not log_path.exists():
            return "MC-0001"

        with open(log_path, "r", encoding="utf-8") as f:
            data = json.load(f)

        checks = data.get("checks", [])
        if not checks:
            return "MC-0001"

        last_mc_id = checks[-1].get("mc_id", "MC-0000")
        match = re.search(r"MC-(\d+)", last_mc_id)
        if not match:
            return "MC-0001"

        next_num = int(match.group(1)) + 1
        # 4자리 패딩 (9999 초과 시 그대로 증가)
        if next_num <= 9999:
            return f"MC-{next_num:04d}"
        else:
            return f"MC-{next_num}"
    except Exception as e:
        logger.debug(f"[get_next_mc_id] MC ID 계산 실패: {e}")
        return "MC-0001"


def issue_mc(
    task_id: str,
    task_desc: str,
    log_path: Path | None = None,
    memory_path: Path | None = None,
    feedback_dir: Path | None = None,
    *,
    _skip_anu_memory: bool = False,
) -> dict:
    """MC(Memory Check Confirmation Number)를 발급하고 로그에 기록한다.

    워크스페이스 MEMORY.md와 Anu MEMORY.md에서 ★ 항목을 파싱하고,
    관련 피드백 파일을 매칭하여 로그에 기록한다.

    Args:
        task_id: 태스크 ID (예: "task-1454.1")
        task_desc: 작업 설명
        log_path: 로그 파일 경로 (기본: _DEFAULT_LOG_PATH)
        memory_path: 워크스페이스 MEMORY.md 경로 (기본: _DEFAULT_MEMORY_PATH)
        feedback_dir: 피드백 파일 디렉토리 (기본: _ANU_MEMORY_DIR)

    Returns:
        {"mc_id": "MC-XXXX", "task_id": "...", "timestamp": "...",
         "star_items_checked": N, "memory_items_read": [...]}
        에러 시 빈 dict 반환
    """
    _custom_memory = memory_path is not None
    if log_path is None:
        log_path = _DEFAULT_LOG_PATH
    if memory_path is None:
        memory_path = _DEFAULT_MEMORY_PATH
    if feedback_dir is None:
        feedback_dir = _ANU_MEMORY_DIR

    lock_path = log_path.parent / ".memory-check-log.lock"
    lock_fd = None

    try:
        log_path.parent.mkdir(parents=True, exist_ok=True)

        lock_fd = open(lock_path, "w")
        fcntl.flock(lock_fd, fcntl.LOCK_EX)

        # 1. 워크스페이스 MEMORY.md ★ 항목 파싱
        ws_star_items = parse_star_items(memory_path)

        # 2. Anu MEMORY.md ★ 항목 파싱 (기본 경로 사용 시에만, 커스텀 경로면 스킵)
        anu_star_items: list[str] = []
        if not _custom_memory and not _skip_anu_memory and _ANU_MEMORY_PATH.exists():
            anu_star_items = parse_star_items(_ANU_MEMORY_PATH)

        # 3. 두 소스의 ★ 항목 합치기 (중복 제거, 순서 유지)
        seen: set[str] = set()
        all_star_items: list[str] = []
        for item in ws_star_items + anu_star_items:
            if item not in seen:
                seen.add(item)
                all_star_items.append(item)

        # 4. 피드백 파일 매칭
        matched_feedback = match_feedback_to_task(task_desc, feedback_dir)

        # 5. MC-XXXX 발급
        mc_id = get_next_mc_id(log_path)

        # 6. 로그 파일에 기록
        timestamp = datetime.now().isoformat()

        if log_path.exists():
            try:
                with open(log_path, "r", encoding="utf-8") as f:
                    log_data = json.load(f)
            except (json.JSONDecodeError, OSError):
                log_data = {"checks": []}
        else:
            log_data = {"checks": []}

        if "checks" not in log_data:
            log_data["checks"] = []

        memory_items_read = all_star_items + matched_feedback

        entry: dict[str, Any] = {
            "mc_id": mc_id,
            "task_id": task_id,
            "timestamp": timestamp,
            "star_items_checked": len(all_star_items),
            "memory_items_read": memory_items_read,
        }
        if matched_feedback:
            entry["matched_feedback_files"] = matched_feedback

        log_data["checks"].append(entry)

        with open(log_path, "w", encoding="utf-8") as f:
            json.dump(log_data, f, ensure_ascii=False, indent=2)

        logger.debug(
            f"[issue_mc] {mc_id} 발급: task_id={task_id}, "
            f"★항목={len(all_star_items)}개, "
            f"피드백={len(matched_feedback)}개"
        )

        return {
            "mc_id": mc_id,
            "task_id": task_id,
            "timestamp": timestamp,
            "star_items_checked": len(all_star_items),
            "memory_items_read": memory_items_read,
        }

    except Exception as e:
        logger.warning(f"[issue_mc] MC 발급 실패 (무시): {e}")
        return {}
    finally:
        if lock_fd is not None:
            try:
                fcntl.flock(lock_fd, fcntl.LOCK_UN)
                lock_fd.close()
            except Exception:
                pass


def get_unchecked_tasks(
    log_path: Path | None = None,
    timers_path: Path | None = None,
) -> list[dict]:
    """running 상태이지만 MC가 발급되지 않은 태스크 목록을 반환한다.

    Args:
        log_path: memory-check-log.json 경로 (기본: _DEFAULT_LOG_PATH)
        timers_path: task-timers.json 경로 (기본: _DEFAULT_TIMERS_PATH)

    Returns:
        [{"task_id": "task-1454.1", "team_id": "dev1-team"}, ...]
        에러 시 빈 리스트 반환
    """
    if log_path is None:
        log_path = _DEFAULT_LOG_PATH
    if timers_path is None:
        timers_path = _DEFAULT_TIMERS_PATH

    try:
        # task-timers.json에서 running 상태 태스크 추출
        running_tasks: list[dict] = []
        if timers_path.exists():
            with open(timers_path, "r", encoding="utf-8") as f:
                timers_data = json.load(f)
            for task_id, task_entry in timers_data.get("tasks", {}).items():
                if task_entry.get("status") == "running":
                    running_tasks.append(
                        {
                            "task_id": task_id,
                            "team_id": task_entry.get("team_id", ""),
                        }
                    )

        if not running_tasks:
            return []

        # memory-check-log.json에서 MC가 발급된 task_id 집합 추출
        checked_task_ids: set[str] = set()
        if log_path.exists():
            try:
                with open(log_path, "r", encoding="utf-8") as f:
                    log_data = json.load(f)
                for check in log_data.get("checks", []):
                    tid = check.get("task_id")
                    if tid:
                        checked_task_ids.add(tid)
            except (json.JSONDecodeError, OSError) as e:
                logger.debug(f"[get_unchecked_tasks] 로그 파일 읽기 실패: {e}")

        # running 태스크 중 MC가 없는 것 반환
        return [t for t in running_tasks if t["task_id"] not in checked_task_ids]

    except Exception as e:
        logger.debug(f"[get_unchecked_tasks] 조회 실패: {e}")
        return []
