# aicodec/infrastructure/cli/commands/utils.py
import json
import re
import sys
from importlib.resources import files
from pathlib import Path

from jsonschema import ValidationError, validate


class JsonPreparationError(Exception):
    pass


def get_user_confirmation(prompt: str, default_yes: bool = True) -> bool:
    """Generic function to get a yes/no confirmation from the user."""
    options = "[Y/n]" if default_yes else "[y/N]"
    while True:
        response = input(f"{prompt} {options} ").lower().strip()
        if not response:
            return default_yes
        if response in ["y", "yes"]:
            return True
        if response in ["n", "no"]:
            return False
        print("Invalid input. Please enter 'y' or 'n'.")


def get_list_from_user(prompt: str) -> list[str]:
    """Gets a comma-separated list of items from the user."""
    response = input(
        f"{prompt} (comma-separated, press Enter to skip): ").strip()
    if not response:
        return []
    return [item.strip() for item in response.split(",")]


def parse_json_file(file_path: Path) -> str:
    """Reads and returns the content of a JSON file as a formatted string."""
    try:
        content = file_path.read_text(encoding="utf-8")
        return json.dumps(json.loads(content), separators=(',', ':'))
    except FileNotFoundError:
        print(f"Error: JSON file '{file_path}' not found.", file=sys.stderr)
        sys.exit(1)
    except json.JSONDecodeError as e:
        print(
            f"Error: Failed to parse JSON file '{file_path}': {e}", file=sys.stderr)
        sys.exit(1)


def clean_prepare_json_string(llm_json: str) -> dict:
    """
    Cleans and validates a JSON string generated by an LLM for the prepare command.
    Returns the cleaned JSON string if valid, otherwise raises an exception.
    """
    try:
        schema_path = files("aicodec") / "assets" / "decoder_schema.json"
        schema_content = schema_path.read_text(encoding="utf-8")
        schema = json.loads(schema_content)
    except (FileNotFoundError, json.JSONDecodeError) as e:
        print(f"Error: Could not load the internal JSON schema. {e}")
        return
    cleaned_str = clean_json_string(llm_json)
    try:
        cleaned_json = json.loads(cleaned_str)
    except json.JSONDecodeError:
        print("Given JSON is invalid after cleaning. Trying to fix it with markdown to json conversion")
        cleaned_str = fix_and_parse_ai_json(cleaned_str)
        try:
            cleaned_json = json.loads(cleaned_str)
        except json.JSONDecodeError as e:
            raise JsonPreparationError(
                f"Error: Failed to parse JSON after attempting to fix it. {e}") from e
    try:
        validate(instance=cleaned_json, schema=schema)
    except ValidationError as e:
        raise JsonPreparationError(
            f"Error: JSON validation failed. {e}") from e
    return json.dumps(cleaned_json, indent=4)


def clean_json_string(s: str) -> str:
    """
    Cleans a string intended for JSON parsing.

    1. Replaces actual non-breaking spaces (\u00a0 or \xa0) with regular spaces.
    2. Replaces the literal text "\\u00a0" with a regular space.
    3. Removes problematic ASCII control characters (0-8, 11-12, 14-31, 127)
       while preserving tab (\t), newline (\n), and carriage return (\r).
    """

    # 1. Replace the actual non-breaking space character with a regular space
    s = re.sub(r'\xa0', ' ', s)

    # 2. Replace the literal text sequence "\\u00a0" with a regular space
    # (The first \ escapes the second \ for the regex)
    s = re.sub(r'\\u00a0', ' ', s)

    # 3. Remove other control characters, preserving \t, \n, \r
    #    (Ranges: 0-8, 11-12, 14-31, and 127)
    s = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', s)

    return s


## Helpers for Robust AI-Generated JSON Fixing ##
# --- 1. Pre-compiled Regex Constants for Efficiency ---
# Fixes stray backslashes: finds 1+ slashes followed by a "non-escape" char
JSON_STRAY_BACKSLASH_PATTERN = re.compile(r'(\\+)(?:([^"\\/bfnrtu])|$)')

# Finds "summary" or "content" string values in the JSON.
# This is more robust: it captures ("key": ")(...content...)(" , or })
# It correctly captures the start/end quotes as groups 1 and 3.
TARGET_FIELD_PATTERN_TEMPLATE = r'("{field_name}":\s*")(.*?)("\s*(?:,|}}))'

JSON_SUMMARY_PATTERN = re.compile(
    TARGET_FIELD_PATTERN_TEMPLATE.format(field_name="summary"),
    re.DOTALL
)
JSON_CONTENT_PATTERN = re.compile(
    TARGET_FIELD_PATTERN_TEMPLATE.format(field_name="content"),
    re.DOTALL
)

# --- 2. Markdown Escape Map ---
MARKDOWN_ESCAPES = {
    r'\_': '_',
    r'\*': '*',
    r'\.': '.',
    r'\#': '#',
    r'\-': '-',
    r'\+': '+',
    r'\!': '!',
    r'\`': '`',
    r'\[': '[',
    r'\]': ']',
    r'\(': '(',
    r'\)': ')',
    r'\{': '{',
    r'\}': '}',
    r'\>': '>',
    r'\|': '|',
}

# --- 3. Helper Functions for Clean Code ---


def _fix_global_markdown_escapes(text: str) -> str:
    """Fixes all Markdown "over-escaping" (e.g., \_ -> _) globally."""
    for escaped, unescaped in MARKDOWN_ESCAPES.items():
        text = text.replace(escaped, unescaped)
    return text


def _backslash_replacer(match: re.Match) -> str:
    """
    Replacer function for JSON_STRAY_BACKSLASH_PATTERN.
    Escapes a stray backslash only if it's part of an ODD-numbered
    sequence of backslashes.
    """
    slashes = match.group(1)
    # char is group 2, or empty string if end-of-line
    char = match.group(2) or ''

    if len(slashes) % 2 == 1:
        # Odd number of slashes: \U -> \\U or \\\U -> \\\\U
        # This is a stray slash that needs escaping.
        slashes += '\\'

    # Even number of slashes (e.g., \\U) is already escaped.
    # Return the (potentially fixed) slashes and the character.
    return slashes + char


def _fix_stray_backslashes(s: str) -> str:
    """Robustly fixes stray backslashes in a string."""
    return JSON_STRAY_BACKSLASH_PATTERN.sub(_backslash_replacer, s)


def _fix_json_string_content(content: str) -> str:
    """
    Applies all required JSON string-value fixes in the correct order.
    """
    # STEP (A): Escape control characters.
    # This MUST run before fixing backslashes.
    fixed_content = content.replace('\r', '\\r')
    fixed_content = fixed_content.replace('\n', '\\n')
    fixed_content = fixed_content.replace('\t', '\\t')

    # STEP (B): Fix unescaped backslashes (ROBUSTLY).
    # This correctly handles \\U vs \U and ignores the \\n, \\r, \\t
    # we just created.
    fixed_content = _fix_stray_backslashes(fixed_content)

    # STEP (C): Fix unescaped double-quotes.
    # This logic is correct, as it only escapes a " if it's
    # NOT already preceded by a (single) backslash.
    fixed_content = re.sub(r'(?<!\\)"', r'\"', fixed_content)

    return fixed_content


def _json_string_value_replacer(match: re.Match) -> str:
    """
    The main re.sub replacer function for summary_regex and content_regex.
    It extracts the content, fixes it, and reassembles the string.
    """
    pre = match.group(1)   # ("summary": "
    content = match.group(2)  # ... the broken content ...
    post = match.group(3)  # ",

    fixed_content = _fix_json_string_content(content)

    return f"{pre}{fixed_content}{post}"

# --- 4. Main Public Function ---


def fix_and_parse_ai_json(text: str) -> str | None:
    """
    Fixes common AI-generated JSON errors for a specific schema.

    1. Fixes Markdown "over-escaping" (e.g., \_) globally.
    2. Fixes JSON "under-escaping" (e.g., unescaped ", \, newlines)
       only within the "summary" and "content" string values.
    """
    try:
        # 1. Fix all Markdown "over-escaping" globally.
        text = _fix_global_markdown_escapes(text)
        try:
            # Quick check: try parsing the JSON now
            json.loads(text)
            return text  # It's valid now, return early
        except json.JSONDecodeError:
            pass  # Still invalid, proceed with targeted fixes

        # 2. Run the targeted replacements
        text = JSON_SUMMARY_PATTERN.sub(_json_string_value_replacer, text)
        text = JSON_CONTENT_PATTERN.sub(_json_string_value_replacer, text)

    except Exception as e:
        # Log the error if you have a logger
        print(f"Error during regex replacement: {e}")
        return None

    return text
