import json
import logging
import subprocess
import tempfile

import httpx
from django.conf import settings

logger = logging.getLogger(__name__)

GEMINI_GENERATE_CONTENT_URL = (
    "https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent"
)


def agent_prompt(
    prompt: str,
    provider: str | None = None,
    history: list[dict] | None = None,
) -> dict:
    """Send a prompt to the configured agent provider and return the response.

    ``history`` is a list of prior turns as ``{"prompt": str, "response": str}``
    dicts, used as conversation context for follow-up questions.

    Returns a dict with ``text``, ``provider`` and ``model`` keys.
    """
    provider = provider or settings.AGENT_PROVIDER
    if provider == "gemini":
        return gemini_agent_prompt(prompt, history=history)
    if provider == "opencode":
        return opencode_agent_prompt(prompt, history=history)
    raise ValueError(f"Unknown agent provider: {provider}")


def _gemini_contents(prompt: str, history: list[dict] | None) -> list[dict]:
    contents = []
    for turn in history or []:
        if not turn.get("response"):
            continue
        contents.append({"parts": [{"text": turn["prompt"]}]})
        contents.append({"parts": [{"text": turn["response"]}]})
    contents.append({"parts": [{"text": prompt}]})
    return contents


def gemini_agent_prompt(prompt: str, history: list[dict] | None = None) -> dict:
    api_key = settings.GOOGLE_API_KEY
    if not api_key:
        raise ValueError("VROBBLER_GOOGLE_API_KEY is not set")
    model = settings.LLM_MODEL

    response = httpx.post(
        GEMINI_GENERATE_CONTENT_URL.format(model=model),
        params={"key": api_key},
        json={"contents": _gemini_contents(prompt, history)},
        timeout=120.0,
    )
    response.raise_for_status()
    data = response.json()

    try:
        text = data["candidates"][0]["content"]["parts"][0]["text"]
    except (KeyError, IndexError) as e:
        raise ValueError(f"Unexpected Gemini response: {data}") from e

    return {"text": text.strip(), "provider": "gemini", "model": model}


def _opencode_prompt_with_history(prompt: str, history: list[dict] | None) -> str:
    if not history:
        return prompt
    lines = ["Previous conversation:"]
    for turn in history:
        if not turn.get("response"):
            continue
        lines.append(f"User: {turn['prompt']}")
        lines.append(f"Assistant: {turn['response']}")
    lines.extend(["", "Continue the conversation and answer the latest question:"])
    return "\n".join(lines) + "\n\n" + prompt


def opencode_agent_prompt(prompt: str, history: list[dict] | None = None) -> dict:
    with tempfile.TemporaryDirectory(prefix="vrobbler-agent-") as tmp_dir:
        try:
            result = subprocess.run(
                [
                    settings.OPENCODE_CMD,
                    "run",
                    "--format",
                    "json",
                    "--dir",
                    tmp_dir,
                    _opencode_prompt_with_history(prompt, history),
                ],
                capture_output=True,
                text=True,
                timeout=900,
            )
        except subprocess.TimeoutExpired as e:
            raise ValueError("opencode agent timed out") from e

    if result.returncode != 0:
        raise ValueError(
            f"opencode exited with {result.returncode}: {result.stderr[:500]}"
        )

    text = _parse_opencode_events(result.stdout)
    if not text:
        raise ValueError("opencode agent returned no text output")
    return {"text": text.strip(), "provider": "opencode", "model": "opencode"}


def _parse_opencode_events(raw: str) -> str:
    parts = []
    for line in raw.splitlines():
        line = line.strip()
        if not line:
            continue
        try:
            event = json.loads(line)
        except json.JSONDecodeError:
            continue
        if event.get("type") != "text":
            continue
        part = event.get("part") or {}
        if part.get("text"):
            parts.append(part["text"])
    return "\n".join(parts)
