from collections.abc import AsyncIterator

import httpx

from app.providers.llm.base import (
    LLMProvider,
    lang_instruction,
    with_lang_reminder,
    sse_delta,
)


SYSTEM_PROMPT = """
You are a voice assistant for spoken conversations with older adults.

Speak naturally, clearly, and calmly.
Use short, simple sentences.
Prefer plain everyday language over technical wording.
Answer in the same language as the user, unless the user asks to switch languages.

Important response rules:
- Output plain text only.
- No markdown.
- No bullet points.
- No numbered lists.
- No tables.
- No code.
- No emojis.
- No URLs unless the user explicitly asks for one.
- Do not use asterisks, hashtags, or formatting symbols.
- Do not write headings.
- Do not use long disclaimers.

Voice style rules:
- Sound helpful, warm, and patient.
- Keep answers brief by default: 1 to 3 short sentences.
- If more detail is needed, explain step by step in natural spoken sentences.
- Ask at most one follow-up question at a time.
- If the answer contains several items, present them as natural speech, not as a list.
- Use wording that sounds good when spoken aloud.
- Avoid abbreviations when possible.
- Avoid symbols when words are better.
- Prefer complete spoken forms for dates, times, and numbers when useful.

Safety and honesty rules:
- If you are unsure, say so briefly and clearly.
- Do not invent facts.
- If current real-world information is needed and unavailable, say that clearly.

Always optimize your answer for listening, not for reading.
""".strip()


class OpenRouterLLMProvider(LLMProvider):
    def __init__(self, api_key: str, model: str):
        self.api_key = (api_key or "").strip()
        self.model = (model or "").strip()

    def _build_messages(
        self, text: str, history: list[dict] | None, language: str | None = None
    ) -> list[dict]:
        if not self.api_key:
            raise ValueError("OPENROUTER_API_KEY is empty")
        if not self.model:
            raise ValueError("OPENROUTER_LLM_MODEL is empty")
        if not text or not text.strip():
            raise ValueError("LLM input text is empty")

        system_content = SYSTEM_PROMPT
        instr = lang_instruction(language)
        if instr:
            system_content = f"{SYSTEM_PROMPT}\n\n{instr}"

        messages = [{"role": "system", "content": system_content}]
        if history:
            messages.extend(history)
        # Sprach-Erinnerung direkt an der letzten Nutzer-Nachricht (schlägt History-Trägheit).
        messages.append({"role": "user", "content": with_lang_reminder(text.strip(), language)})
        return messages

    async def complete(
        self,
        text: str,
        history: list[dict] | None = None,
        session_id: str | None = None,
        language: str | None = None,
    ) -> str:
        payload = {
            "model": self.model,
            "messages": self._build_messages(text, history, language=language),
        }

        timeout = httpx.Timeout(connect=10.0, read=120.0, write=30.0, pool=10.0)

        async with httpx.AsyncClient(timeout=timeout) as client:
            try:
                response = await client.post(
                    "https://openrouter.ai/api/v1/chat/completions",
                    headers={
                        "Authorization": f"Bearer {self.api_key}",
                        "Content-Type": "application/json",
                    },
                    json=payload,
                )
                response.raise_for_status()
            except httpx.HTTPStatusError as exc:
                raise RuntimeError(
                    f"OpenRouter LLM error {exc.response.status_code}: {exc.response.text}"
                ) from exc
            except httpx.TimeoutException as exc:
                raise RuntimeError("OpenRouter LLM timeout") from exc
            except httpx.HTTPError as exc:
                raise RuntimeError(f"OpenRouter LLM transport error: {exc}") from exc

        data = response.json()

        try:
            content = data["choices"][0]["message"]["content"]
        except (KeyError, IndexError, TypeError) as exc:
            raise RuntimeError(f"Unexpected OpenRouter LLM response: {data}") from exc

        if not content or not str(content).strip():
            raise RuntimeError("OpenRouter LLM returned empty content")

        return str(content).strip()

    async def stream(
        self,
        text: str,
        history: list[dict] | None = None,
        session_id: str | None = None,
        language: str | None = None,
    ) -> AsyncIterator[str]:
        payload = {
            "model": self.model,
            "messages": self._build_messages(text, history, language=language),
            "stream": True,
        }
        timeout = httpx.Timeout(connect=10.0, read=120.0, write=30.0, pool=10.0)

        async with httpx.AsyncClient(timeout=timeout) as client:
            try:
                async with client.stream(
                    "POST",
                    "https://openrouter.ai/api/v1/chat/completions",
                    headers={
                        "Authorization": f"Bearer {self.api_key}",
                        "Content-Type": "application/json",
                    },
                    json=payload,
                ) as response:
                    if response.status_code >= 400:
                        body = await response.aread()
                        raise RuntimeError(
                            f"OpenRouter LLM error {response.status_code}: "
                            f"{body.decode(errors='replace')}"
                        )
                    async for line in response.aiter_lines():
                        delta = sse_delta(line)
                        if delta:
                            yield delta
            except httpx.TimeoutException as exc:
                raise RuntimeError("OpenRouter LLM timeout") from exc
            except httpx.HTTPError as exc:
                raise RuntimeError(f"OpenRouter LLM transport error: {exc}") from exc

