import json
import urllib.error
import urllib.request
from typing import Any, Dict

from .config import AI_MODEL, OPENROUTER_API_KEY, OPENROUTER_URL


def _sanitize_prompt(prompt: str) -> str:
    """Strip control characters and enforce a maximum length."""
    cleaned = "".join(ch for ch in prompt if ch >= " " or ch in "\n\r\t")
    return cleaned[:4000]


def call_openrouter_json(payload: Dict[str, Any]) -> Dict[str, Any]:
    if not OPENROUTER_API_KEY:
        raise RuntimeError(
            "AI service is not configured. "
            "Please set OPENROUTER_API_KEY in the environment and restart the server."
        )
    request_body = json.dumps(payload).encode("utf-8")
    headers = {
        "Authorization": f"Bearer {OPENROUTER_API_KEY}",
        "Content-Type": "application/json",
    }
    request = urllib.request.Request(
        OPENROUTER_URL, data=request_body, headers=headers, method="POST"
    )
    try:
        with urllib.request.urlopen(request, timeout=30) as response:
            return json.loads(response.read().decode("utf-8"))
    except urllib.error.HTTPError as error:
        raise RuntimeError(f"OpenRouter request failed (HTTP {error.code})")
    except urllib.error.URLError as error:
        raise RuntimeError(f"OpenRouter connection error: {error.reason}")


def call_openrouter(prompt: str) -> str:
    payload = {
        "model": AI_MODEL,
        "messages": [{"role": "user", "content": prompt}],
    }
    return extract_answer(call_openrouter_json(payload)).strip()


def run_kanban_ai(board: Dict[str, Any], prompt: str) -> Dict[str, Any]:
    payload = _build_kanban_payload(board, prompt)
    return extract_json_object(call_openrouter_json(payload))


def safe_ai_error(exc: Exception) -> str:
    msg = str(exc)
    if msg.startswith("AI service is not configured"):
        return msg
    return "AI service error. Please try again."


# --- Response parsing ---

def extract_answer(response_json: Dict[str, Any]) -> str:
    choices = response_json.get("choices")
    if not choices or not isinstance(choices, list):
        raise ValueError("OpenRouter returned no choices")
    choice = choices[0]
    if not isinstance(choice, dict):
        raise ValueError("Unexpected OpenRouter choice format")
    message = choice.get("message")
    if isinstance(message, dict):
        content = message.get("content")
        if isinstance(content, str):
            return content
        if isinstance(content, list) and content:
            first = content[0]
            if isinstance(first, dict) and "text" in first:
                return first["text"]
            if isinstance(first, str):
                return first
    if "text" in choice and isinstance(choice["text"], str):
        return choice["text"]
    output = response_json.get("output")
    if isinstance(output, list) and output:
        first = output[0]
        if isinstance(first, dict):
            output_content = first.get("content")
            if isinstance(output_content, list) and output_content:
                part = output_content[0]
                if isinstance(part, dict) and "text" in part:
                    return part["text"]
                if isinstance(part, str):
                    return part
    raise ValueError("Unable to parse OpenRouter response")


def extract_json_object(response_json: Dict[str, Any]) -> Dict[str, Any]:
    choices = response_json.get("choices")
    if not choices or not isinstance(choices, list):
        raise ValueError("OpenRouter returned no choices")
    choice = choices[0]
    if not isinstance(choice, dict):
        raise ValueError("Unexpected OpenRouter choice format")
    message = choice.get("message")
    content = None
    if isinstance(message, dict):
        content = message.get("content")
    elif "text" in choice and isinstance(choice["text"], str):
        content = choice["text"]
    if isinstance(content, dict):
        return content
    if isinstance(content, list) and content:
        first = content[0]
        if isinstance(first, dict):
            return first
        if isinstance(first, str):
            content = first
    if isinstance(content, str):
        return _parse_json_from_text(content)
    output = response_json.get("output")
    if isinstance(output, list) and output:
        first = output[0]
        if isinstance(first, dict):
            output_content = first.get("content")
            if isinstance(output_content, list) and output_content:
                part = output_content[0]
                if isinstance(part, dict):
                    return part
                if isinstance(part, str):
                    return _parse_json_from_text(part)
    raise ValueError("Unable to parse OpenRouter structured response")


def _parse_json_from_text(text: str) -> Dict[str, Any]:
    cleaned = text.strip()
    if cleaned.startswith("```") and cleaned.endswith("```"):
        cleaned = "\n".join(cleaned.splitlines()[1:-1]).strip()
    try:
        return json.loads(cleaned)
    except json.JSONDecodeError:
        return json.loads(_extract_first_json_object(cleaned))


def _extract_first_json_object(text: str) -> str:
    start = text.find("{")
    if start == -1:
        raise ValueError("No JSON object found in AI response")
    depth = 0
    in_string = False
    escape = False
    for index, char in enumerate(text[start:], start):
        if escape:
            escape = False
            continue
        if char == "\\":
            escape = True
            continue
        if char == '"':
            in_string = not in_string
            continue
        if in_string:
            continue
        if char == "{":
            depth += 1
        elif char == "}":
            depth -= 1
            if depth == 0:
                return text[start : index + 1]
    raise ValueError("Unbalanced JSON braces in AI response")


def _build_kanban_payload(board: Dict[str, Any], prompt: str) -> Dict[str, Any]:
    return {
        "model": AI_MODEL,
        "messages": [
            {
                "role": "system",
                "content": (
                    "You are a Kanban board assistant. Respond with valid JSON only. "
                    "Return an object with 'answer' (string) and optional 'board' (the full updated board) "
                    "when the board should be changed. Do not include any text outside the JSON object."
                ),
            },
            {
                "role": "user",
                "content": (
                    "Current board:\n"
                    f"{json.dumps(board, indent=2)}\n\n"
                    f"<request>{_sanitize_prompt(prompt)}</request>"
                ),
            },
        ],
        "response_format": {"type": "json_object"},
    }
