"""
gemini_severity_parser.py — Gemini PR 리뷰 High/Medium/Low 통합 판정 (task-2468 P0-3)

task-2467+3에서 시각 priority(High 3건)와 gate high_severity_hits(0건) 불일치 사고 발생
→ 단일 parser로 통합.

기존 gemini_evidence_verify.match_high_severity는 emoji(🔴/❌) / severity:high|critical /
BLOCKING|CRITICAL|MUST FIX / ## High|Critical|Blocking 패턴만 인식.
본 모듈은 추가 패턴까지 포괄:
- emoji 확장: 🔴 ❌ 🚨 ⚠️ ⛔
- heading 확장: ## High / ### Critical / ## Critical / ### High / ### Blocking / #### High 등
- severity label 확장: severity: high|critical / priority: high|critical (대소문자 무관)
- inline label 확장: **High:** / **Critical:** / **[High]** / **[Critical]**
- keyword 확장: BLOCKING / CRITICAL / MUST FIX / MUST_FIX / High Priority / Critical Issue
- code block 내부는 strip (false positive 방지)
"""
from __future__ import annotations

import json
import re
import subprocess

# ---------------------------------------------------------------------------
# code block strip
# ---------------------------------------------------------------------------


def strip_code_blocks(body: str) -> str:
    """fenced code block (``` ... ```) 과 inline code (`...`) 제거."""
    if not body:
        return body
    # fenced code blocks (multiline)
    cleaned = re.sub(r"```[\s\S]*?```", " ", body)
    # inline code (single backtick)
    cleaned = re.sub(r"`[^`\n]*`", " ", cleaned)
    return cleaned


# ---------------------------------------------------------------------------
# High severity 패턴
# (기존 gemini_evidence_verify 패턴 + 확장 패턴 모두 포괄)
# ---------------------------------------------------------------------------

# emoji: 🔴 ❌ 🚨 ⛔ + ⚠️ (조합 문자)
_HIGH_EMOJI = re.compile(r"[🔴❌🚨⛔]|⚠️")

# severity/priority label: severity: high|critical / priority: high|critical (대소문자 무관)
_HIGH_SEVERITY_LABEL = re.compile(
    r"(?:severity|priority)\s*[:=]\s*(high|critical)", re.IGNORECASE
)

# 키워드: 대문자 정확 (기존) + MUST_FIX + HIGH PRIORITY + CRITICAL ISSUE 추가
_HIGH_KEYWORD = re.compile(
    r"\b(BLOCKING|CRITICAL|MUST[\s_]FIX|HIGH\s+PRIORITY|CRITICAL\s+ISSUE)\b"
)

# heading: ## High / ### Critical / ## Critical / ### High / ### Blocking / #### High 등
# 기존은 ##만 인식 → #{2,6}으로 확장
_HIGH_HEADING = re.compile(
    r"^\s*#{2,6}\s+(High|Critical|Blocking)\b", re.MULTILINE | re.IGNORECASE
)

# heading "High Priority" 형태도 포괄 (## High Priority, ### High Priority 등)
_HIGH_HEADING_PRIORITY = re.compile(
    r"^\s*#{2,6}\s+(High\s+Priority|Critical\s+Issue|Critical\s+Problem)\b",
    re.MULTILINE | re.IGNORECASE,
)

# inline label: **High:** / **Critical:** / **[High]** / **[Critical]** / **Blocking:**
_HIGH_INLINE_LABEL = re.compile(
    r"\*\*\s*\[?\s*(High|Critical|Blocking)\s*\]?\s*[:.*]", re.IGNORECASE
)

# image alt-text label: ![High](...) / ![Critical](...) / ![Blocking](...)
# Gemini 가 severity 를 image badge 로 표시하는 형식 (task-2471 hardening).
_HIGH_IMAGE_LABEL = re.compile(
    r"!\[\s*(High|Critical|Blocking)\s*\]\(", re.IGNORECASE
)

# ---------------------------------------------------------------------------
# Medium 패턴
# ---------------------------------------------------------------------------

_MED_HEADING = re.compile(r"^\s*#{2,6}\s+(Medium)\b", re.MULTILINE | re.IGNORECASE)
_MED_LABEL = re.compile(r"(?:severity|priority)\s*[:=]\s*medium", re.IGNORECASE)
_MED_INLINE = re.compile(r"\*\*\s*\[?\s*Medium\s*\]?\s*[:.*]", re.IGNORECASE)

# ⚠️ + "Medium" 조합 (emoji는 별도 — 단, medium 컨텍스트에서만)
_MED_EMOJI_CONTEXT = re.compile(r"⚠️\s+\w*\s*Medium", re.IGNORECASE)

# ---------------------------------------------------------------------------
# Low 패턴
# ---------------------------------------------------------------------------

_LOW_HEADING = re.compile(r"^\s*#{2,6}\s+(Low|Minor)\b", re.MULTILINE | re.IGNORECASE)
_LOW_LABEL = re.compile(r"(?:severity|priority)\s*[:=]\s*low", re.IGNORECASE)
_LOW_INLINE = re.compile(r"\*\*\s*\[?\s*Low\s*\]?\s*[:.*]", re.IGNORECASE)


# ---------------------------------------------------------------------------
# 핵심 함수
# ---------------------------------------------------------------------------


def count_severities(body: str | None) -> dict:
    """body에서 high/medium/low 매칭 개수 + 매칭 패턴 리스트 반환.

    Returns:
        {"high": int, "medium": int, "low": int,
         "high_hits": list[str], "medium_hits": list[str], "low_hits": list[str]}

    각 매칭은 1건으로 카운트.
    같은 본문에서 같은 패턴 type이 여러 번 등장하면 각각 카운트
    (예: ## High 두 개 = 2건).
    code block 내부는 strip (false positive 방지).
    """
    if body is None:
        return {
            "high": 0, "medium": 0, "low": 0,
            "high_hits": [], "medium_hits": [], "low_hits": [],
        }

    stripped = strip_code_blocks(body)

    high_hits: list[str] = []
    medium_hits: list[str] = []
    low_hits: list[str] = []

    # --- HIGH ---
    # emoji 패턴 (각 매칭 1건씩)
    for m in _HIGH_EMOJI.finditer(stripped):
        high_hits.append(f"emoji:{m.group()}")

    # severity/priority label (각 매칭 1건씩)
    for m in _HIGH_SEVERITY_LABEL.finditer(stripped):
        high_hits.append(f"severity_label:{m.group()}")

    # keyword (각 매칭 1건씩)
    for m in _HIGH_KEYWORD.finditer(stripped):
        high_hits.append(f"keyword:{m.group()}")

    # heading (각 매칭 1건씩) — ## High / ### Critical 등
    for m in _HIGH_HEADING.finditer(stripped):
        high_hits.append(f"heading:{m.group().strip()}")

    # heading "High Priority" 등 추가 패턴
    for m in _HIGH_HEADING_PRIORITY.finditer(stripped):
        high_hits.append(f"heading_priority:{m.group().strip()}")

    # inline label **High:** 등
    for m in _HIGH_INLINE_LABEL.finditer(stripped):
        high_hits.append(f"inline:{m.group()}")

    # image alt-text label ![High/Critical/Blocking](...) — task-2471 hardening
    for m in _HIGH_IMAGE_LABEL.finditer(stripped):
        high_hits.append(f"image:{m.group()}")

    # --- MEDIUM ---
    for m in _MED_HEADING.finditer(stripped):
        medium_hits.append(f"heading:{m.group().strip()}")

    for m in _MED_LABEL.finditer(stripped):
        medium_hits.append(f"severity_label:{m.group()}")

    for m in _MED_INLINE.finditer(stripped):
        medium_hits.append(f"inline:{m.group()}")

    for m in _MED_EMOJI_CONTEXT.finditer(stripped):
        medium_hits.append(f"emoji_context:{m.group()}")

    # --- LOW ---
    for m in _LOW_HEADING.finditer(stripped):
        low_hits.append(f"heading:{m.group().strip()}")

    for m in _LOW_LABEL.finditer(stripped):
        low_hits.append(f"severity_label:{m.group()}")

    for m in _LOW_INLINE.finditer(stripped):
        low_hits.append(f"inline:{m.group()}")

    return {
        "high": len(high_hits),
        "medium": len(medium_hits),
        "low": len(low_hits),
        "high_hits": high_hits,
        "medium_hits": medium_hits,
        "low_hits": low_hits,
    }


def has_high_severity(body: str | None) -> bool:
    """body에 high severity 패턴이 1건 이상 존재하면 True."""
    return count_severities(body)["high"] >= 1


def _gh_api(endpoint: str, timeout: int = 30) -> tuple[int, list | dict]:
    """gh api {endpoint} 호출 → (returncode, parsed_json_or_empty)."""
    try:
        proc = subprocess.run(
            ["gh", "api", endpoint],
            capture_output=True, text=True, timeout=timeout,
        )
        rc = proc.returncode
        if rc != 0 or not proc.stdout.strip():
            return rc, []
        try:
            data = json.loads(proc.stdout)
        except json.JSONDecodeError:
            return rc, []
        return rc, data
    except subprocess.TimeoutExpired:
        return -1, []
    except Exception:
        return -1, []


def parse_pr_review_severities(repo: str, pr_number: int) -> dict:
    """PR의 모든 review + comment에서 severity count 통합. gh CLI 호출.

    Returns:
        {"high": int, "medium": int, "low": int,
         "primary": list[dict],
         "high_hits": list[str], "medium_hits": list[str], "low_hits": list[str]}
    """
    totals: dict[str, int] = {"high": 0, "medium": 0, "low": 0}
    all_high_hits: list[str] = []
    all_medium_hits: list[str] = []
    all_low_hits: list[str] = []
    primary: list[dict] = []

    # PR reviews
    _, reviews = _gh_api(f"repos/{repo}/pulls/{pr_number}/reviews")
    if isinstance(reviews, list):
        for r in reviews:
            body = r.get("body") or ""
            sv = count_severities(body)
            totals["high"] += sv["high"]
            totals["medium"] += sv["medium"]
            totals["low"] += sv["low"]
            all_high_hits.extend(sv["high_hits"])
            all_medium_hits.extend(sv["medium_hits"])
            all_low_hits.extend(sv["low_hits"])
            primary.append({
                "type": "review",
                "id": r.get("id"),
                "body_snippet": body[:200],
                "high": sv["high"],
                "medium": sv["medium"],
                "low": sv["low"],
            })

    # PR review comments (line comments)
    _, review_comments = _gh_api(f"repos/{repo}/pulls/{pr_number}/comments")
    if isinstance(review_comments, list):
        for c in review_comments:
            body = c.get("body") or ""
            sv = count_severities(body)
            totals["high"] += sv["high"]
            totals["medium"] += sv["medium"]
            totals["low"] += sv["low"]
            all_high_hits.extend(sv["high_hits"])
            all_medium_hits.extend(sv["medium_hits"])
            all_low_hits.extend(sv["low_hits"])
            primary.append({
                "type": "review_comment",
                "id": c.get("id"),
                "body_snippet": body[:200],
                "high": sv["high"],
                "medium": sv["medium"],
                "low": sv["low"],
            })

    # Issue (PR) comments
    _, issue_comments = _gh_api(f"repos/{repo}/issues/{pr_number}/comments")
    if isinstance(issue_comments, list):
        for c in issue_comments:
            body = c.get("body") or ""
            sv = count_severities(body)
            totals["high"] += sv["high"]
            totals["medium"] += sv["medium"]
            totals["low"] += sv["low"]
            all_high_hits.extend(sv["high_hits"])
            all_medium_hits.extend(sv["medium_hits"])
            all_low_hits.extend(sv["low_hits"])
            primary.append({
                "type": "issue_comment",
                "id": c.get("id"),
                "body_snippet": body[:200],
                "high": sv["high"],
                "medium": sv["medium"],
                "low": sv["low"],
            })

    return {
        "high": totals["high"],
        "medium": totals["medium"],
        "low": totals["low"],
        "primary": primary,
        "high_hits": all_high_hits,
        "medium_hits": all_medium_hits,
        "low_hits": all_low_hits,
    }


# ---------------------------------------------------------------------------
# 하위호환: 기존 gemini_evidence_verify.match_high_severity 동작 재현
# ---------------------------------------------------------------------------


def match_high_severity(body: str) -> list[str]:
    """기존 gemini_evidence_verify.match_high_severity 호환 인터페이스.

    body에서 high-severity 패턴 매칭 결과 반환 (code block 제외 후).
    각 패턴 type별로 최대 1건 반환 (기존 동작 유지).
    """
    if not body:
        return []
    stripped = strip_code_blocks(body)
    hits: list[str] = []
    if _HIGH_EMOJI.search(stripped):
        hits.append("emoji:🔴/❌/🚨/⚠️/⛔")
    if _HIGH_SEVERITY_LABEL.search(stripped):
        hits.append("severity:high/critical")
    if _HIGH_KEYWORD.search(stripped):
        hits.append("keyword:BLOCKING/CRITICAL/MUST_FIX/HIGH_PRIORITY")
    if _HIGH_HEADING.search(stripped) or _HIGH_HEADING_PRIORITY.search(stripped):
        hits.append("header:##/###/####High/Critical/Blocking")
    if _HIGH_INLINE_LABEL.search(stripped):
        hits.append("inline:**High**/Critical/Blocking")
    if _HIGH_IMAGE_LABEL.search(stripped):
        hits.append("image:![High/Critical/Blocking](...)")
    return hits
