"""
critical_gap.py - 보고서 CRITICAL 이슈 미수정 검증 verifier

보고서에서 CRITICAL 이슈가 보고되었으나 수정이 확인되지 않으면 FAIL.
"""

import os
import re

DEFAULT_REPORTS_DIR = "/home/jay/workspace/memory/reports"

# CRITICAL 이슈 탐지 키워드 (호환성 유지용 - 외부 import 없음, 신규 로직은 정규식 사용)
CRITICAL_KEYWORDS = ["CRITICAL", "critical", "심각", "블로커"]

# 수정 확인 키워드
RESOLVED_KEYWORDS = ["수정 완료", "해결", "fixed", "resolved"]

# 영문 word boundary, 한글은 boundary 패턴 분리
_CRITICAL_RE_EN = re.compile(r"\b(CRITICAL|critical)\b")
_CRITICAL_RE_KO = re.compile(r"(?:^|[\s,。、!?])(?:심각|블로커)(?:[\s,。、!?]|$)")

# 이슈 마커 패턴 (둘 중 하나여야 critical로 인정):
# (a) 마크다운 헤더 (## CRITICAL 이슈)
# (b) 리스트 마커 시작 (- CRITICAL: ...)
# (c) "severity: critical" 메타 라벨
ISSUE_MARKER_PATTERNS = [
    re.compile(r"^\s*#{1,6}\s+.*(?:CRITICAL|critical|심각|블로커)"),
    re.compile(r"^\s*[-*]\s+.*(?:CRITICAL|critical|심각|블로커)"),
    re.compile(r"^\s*\d+\.\s+.*(?:CRITICAL|critical|심각|블로커)"),
    re.compile(r"severity\s*[:=]\s*critical", re.IGNORECASE),
    re.compile(r"!\[(?:security-critical|critical|high)\]", re.IGNORECASE),
]

# 같은 줄에 RESOLVED 마커가 인라인으로 있으면 차단
RESOLVED_INLINE_PATTERNS = [
    re.compile(r"\*\*\[.*resolved.*\]\*\*", re.IGNORECASE),
    re.compile(r"✅\s*RESOLVED"),
    re.compile(r"~~.*(critical|심각).*~~"),
]

# RESOLVED 키워드를 word boundary + 부정 컨텍스트 차단으로 강화
# - "해결" 단독 매칭 시 직후/직전에 "미", "안", "불", "되지 않" 등이 오면 unresolved로 간주
_RESOLVED_PATTERNS = [
    re.compile(r"수정\s*완료"),                       # "수정 완료" / "수정완료"
    re.compile(r"\bfixed\b", re.IGNORECASE),
    re.compile(r"\bresolved\b", re.IGNORECASE),
    re.compile(r"✅\s*RESOLVED", re.IGNORECASE),
    # "해결" 매칭 — 직후 부정어 차단
    # 매칭: "해결됨", "해결되었", "해결 완료", "해결책" 등
    # 비매칭: "해결 미완료", "해결되지 않", "미해결", "해결 안 됨"
    re.compile(r"해결(?:됨|되었|되었음|됐|책|책을|책이|\s*완료)"),
]

# 부정 컨텍스트 (이게 같은 줄에 있으면 RESOLVED 후보 무효화)
_UNRESOLVED_HINTS = [
    re.compile(r"미\s*해결"),
    re.compile(r"미\s*완료"),
    re.compile(r"해결\s*되지\s*않"),
    re.compile(r"해결\s*안\s*됨"),
    re.compile(r"해결\s*못"),
    re.compile(r"unresolved", re.IGNORECASE),
    re.compile(r"not\s+(?:fixed|resolved)", re.IGNORECASE),
]


def _detect_critical_lines(lines: list[str]) -> list[tuple[int, str]]:
    found: list[tuple[int, str]] = []
    for i, line in enumerate(lines, start=1):
        # 정규식으로 keyword 후보 찾기
        if not (_CRITICAL_RE_EN.search(line) or _CRITICAL_RE_KO.search(line)):
            continue
        # 이슈 마커 패턴 중 하나라도 매칭해야 인정
        if not any(p.search(line) for p in ISSUE_MARKER_PATTERNS):
            continue
        # 같은 줄에 인라인 RESOLVED 마커 있으면 skip
        if any(p.search(line) for p in RESOLVED_INLINE_PATTERNS):
            continue
        found.append((i, line.strip()))
    return found


def _detect_resolved_after(lines: list[str], critical_line_num: int) -> bool:
    """
    critical_line_num 이후 줄에서 RESOLVED 마커 탐지. 부정 컨텍스트는 차단.

    Args:
        lines: 전체 줄 목록
        critical_line_num: CRITICAL 이슈가 발견된 줄 번호 (1-based)

    Returns:
        True if resolved marker found after critical_line_num (단, 같은 줄에
        부정 힌트가 있으면 해당 줄은 후보에서 제외).
    """
    # 보고서 후반부: critical 줄 이후의 줄들 검색
    for line in lines[critical_line_num:]:
        # 부정 힌트가 있으면 이 줄은 RESOLVED 후보 X
        if any(p.search(line) for p in _UNRESOLVED_HINTS):
            continue
        # RESOLVED 패턴 매칭
        if any(p.search(line) for p in _RESOLVED_PATTERNS):
            return True
    return False


def verify(task_id: str, report_path: str = "") -> dict:
    """
    보고서에서 CRITICAL 이슈 확인 + 수정 여부 검증.

    보고서 경로: /home/jay/workspace/memory/reports/{task_id}.md
    - CRITICAL 이슈가 있는데 수정 확인이 없으면 FAIL
    - CRITICAL 이슈가 있고 수정 확인도 있으면 PASS
    - CRITICAL 이슈가 없으면 PASS (이슈 없음)
    - 보고서 파일 없으면 SKIP
    - 빈 보고서 → PASS

    Returns:
        {"status": "PASS"|"FAIL"|"WARN"|"SKIP", "details": [...]}
    """
    if not task_id:
        return {"status": "SKIP", "details": ["No task_id provided"]}

    # 보고서 경로 결정
    if report_path:
        path = report_path
    else:
        path = os.path.join(DEFAULT_REPORTS_DIR, f"{task_id}.md")

    # 파일 존재 확인
    if not os.path.exists(path):
        return {
            "status": "SKIP",
            "details": [f"Report not found: {path}"],
        }

    # 파일 읽기
    try:
        with open(path, "r", encoding="utf-8") as f:
            content = f.read()
    except OSError as e:
        return {
            "status": "SKIP",
            "details": [f"Failed to read report: {type(e).__name__}: {e}"],
        }

    lines = content.splitlines()

    # 빈 보고서
    if not lines or not content.strip():
        return {"status": "PASS", "details": ["Report is empty — no CRITICAL issues"]}

    # CRITICAL 이슈 탐지
    critical_lines = _detect_critical_lines(lines)

    if not critical_lines:
        return {
            "status": "PASS",
            "details": [f"No CRITICAL issues found in report: {path}"],
        }

    # CRITICAL 이슈별 수정 확인 여부 체크
    details: list[str] = []
    unresolved: list[str] = []

    for line_num, line_content in critical_lines:
        resolved = _detect_resolved_after(lines, line_num)
        issue_desc = _extract_issue_description(line_content)
        if resolved:
            details.append(f"CRITICAL (line {line_num}) RESOLVED: {issue_desc}")
        else:
            details.append(f"CRITICAL (line {line_num}) UNRESOLVED: {issue_desc}")
            unresolved.append(f"line {line_num}: {issue_desc}")

    if unresolved:
        details.insert(0, f"FAIL — {len(unresolved)} CRITICAL issue(s) not resolved")
        return {"status": "FAIL", "details": details}

    details.insert(0, f"PASS — {len(critical_lines)} CRITICAL issue(s) all resolved")
    return {"status": "PASS", "details": details}


def _extract_issue_description(line: str) -> str:
    """CRITICAL 이슈 줄에서 설명 텍스트 추출 (최대 80자)."""
    # 마크다운 헤더 기호, 번호 목록 기호 등 제거
    cleaned = re.sub(r"^[#\-*\d\.\s]+", "", line).strip()
    if len(cleaned) > 80:
        return cleaned[:80] + "…"
    return cleaned if cleaned else line[:80]
