"""
LLM JSON Response Parser — 共用 helper (Gap 3 Review P1 優化)
==============================================================
4 個 LLM scanner 原本各自重複 3-path JSON parse 邏輯 (~80 行 × 4 = 320 行).
抽成統一 helper,未來擴加 LLM 的 service 直接呼叫.

Origin (2026-04-19 pr-review-toolkit code review 指出):
  - hermes_rule_quality_job:167-186
  - capacity_forecaster_job:204-221
  - compliance_scanner_job:446-463
  - coverage_evaluator_job:241-257
  全部重複 strip ``` → direct parse → NemoTron wrapper → description 內嵌 JSON 的 pattern.

2026-04-19 ogt + Claude Opus 4.7 (1M context) Asia/Taipei
"""
from __future__ import annotations

import json as _json
from typing import Any

import structlog

logger = structlog.get_logger(__name__)


def parse_llm_json_response(
    text: str,
    required_key: str,
    logger_context: str = "",
) -> dict[str, Any] | None:
    """
    解析 LLM JSON 回應,含 3-path fallback.

    Args:
        text: LLM 回傳的原始文字
        required_key: JSON 必須含的 key (e.g. 'posture_grade', 'priority_actions')
                      用來區分「真 JSON」vs 「NemoTron wrapper」
        logger_context: log warning 時附加的 context (e.g. 'compliance_scan')

    Returns:
        dict | None — 解析成功且含 required_key 回 dict,否則 None

    3-path fallback:
      Path 1: 直接 JSON → 有 required_key 就用
      Path 2: NemoTron wrapper (description/action_title/reasoning 內嵌 JSON 字串)
      Path 3: Path 2 的 description 含 required_key 直接採用

    失敗時 log warning 但不 raise,呼叫者決定如何 fallback.
    """
    if not text:
        return None

    _raw = text.strip()
    # 剝去 markdown code fence ```json...``` 或 ```...```
    if _raw.startswith("```"):
        _raw = _raw.strip("`").lstrip("json").strip()

    try:
        parsed = _json.loads(_raw)
    except (_json.JSONDecodeError, ValueError) as e:
        logger.warning(
            "llm_json_parse_raw_failed",
            context=logger_context,
            error=str(e),
            raw_prefix=_raw[:200],
        )
        return None

    if not isinstance(parsed, dict):
        return None

    # Path 1: 直接 JSON 含 required_key
    if required_key in parsed:
        return parsed

    # Path 2+3: NemoTron wrapper — description / action_title / reasoning 可能是內嵌 JSON
    for wrapper_key in ("description", "action_title", "reasoning"):
        desc = parsed.get(wrapper_key)
        if not desc:
            continue
        desc_str = str(desc).strip()
        # Path 2: description 是內嵌 JSON 字串
        if desc_str.startswith("{"):
            try:
                inner = _json.loads(desc_str)
                if isinstance(inner, dict) and required_key in inner:
                    return inner
            except (_json.JSONDecodeError, ValueError):
                pass

    # Path 3: 所有嘗試都失敗
    logger.warning(
        "llm_json_missing_required_key",
        context=logger_context,
        required_key=required_key,
        keys_found=list(parsed.keys())[:10],
    )
    return None
