import json
import re
import logging
from typing import Optional, Dict, Any

logger = logging.getLogger(__name__)


class ModelJSONParser:
    """
    A JSON extractor for LLM responses.
    Handles:
    - Backtick fenced blocks
    - Extra text before/after JSON
    - Loose/truncated JSON
    - Trailing commas
    - Missing quotes around keys
    """

    @staticmethod
    def parse(response_text: str) -> Optional[Dict[str, Any]]:
        """
        Main public method.
        Tries multiple parsing strategies safely.
        Returns Python dict or None.
        """

        if not response_text or not isinstance(response_text, str):
            return None

        data = ModelJSONParser._try_json(response_text)
        if data:
            return data

        fenced = ModelJSONParser._extract_fenced_json(response_text)
        data = ModelJSONParser._try_json(fenced)
        if data:
            return data

        extracted = ModelJSONParser._extract_json_object(response_text)
        data = ModelJSONParser._try_json(extracted)
        if data:
            return data

        cleaned = ModelJSONParser._clean_json(response_text)
        data = ModelJSONParser._try_json(cleaned)
        if data:
            return data

        logger.warning("ModelJSONParser: Failed to parse JSON after all strategies.")
        return None

    @staticmethod
    def _try_json(text: str) -> Optional[Dict[str, Any]]:
        """Safe JSON.parse wrapper."""
        try:
            return json.loads(text)
        except Exception:
            return None

    @staticmethod
    def _extract_fenced_json(text: str) -> str:
        """
        Extracts content inside ```json ... ``` or ``` ... ```
        """
        pattern = r"```(?:json)?\s*(.*?)```"
        match = re.search(pattern, text, flags=re.DOTALL | re.IGNORECASE)
        return match.group(1).strip() if match else text

    @staticmethod
    def _extract_json_object(text: str) -> str:
        """
        Extract the first {...} block.
        """
        match = re.search(r"\{.*\}", text, flags=re.DOTALL)
        return match.group(0) if match else text

    @staticmethod
    def _clean_json(text: str) -> str:
        """
        Attempt to fix common LLM JSON glitches.
        """
        cleaned = text

        cleaned = cleaned.replace("```json", "").replace("```", "")

        cleaned = re.sub(r",\s*([\]}])", r"\1", cleaned)

        if "{" in cleaned and "}" in cleaned:
            cleaned = cleaned[cleaned.find("{"): cleaned.rfind("}") + 1]

        return cleaned.strip()
