"""OpenRouter transcription provider adapter."""

from __future__ import annotations

import base64
import logging
from dataclasses import dataclass
from typing import Any
from typing import cast

from openrouter import OpenRouter
from openrouter.components.chatmessages import ChatMessagesTypedDict

from transcription.config import Settings
from transcription.config import get_settings
from transcription.providers.base import ProviderAuthError
from transcription.providers.base import ProviderError
from transcription.providers.base import ProviderResponseError
from transcription.providers.base import TranscriptionResult

logger = logging.getLogger(__name__)

DEFAULT_OPENROUTER_MODEL = "google/gemini-2.5-flash"


@dataclass(frozen=True)
class OpenRouterRequest:
    """Normalized request payload fields for OpenRouter calls."""

    model: str
    messages: list[dict[str, Any]]
    http_referer: str | None
    x_open_router_title: str | None


class OpenRouterTranscriptionProvider:
    """Adapter that performs image transcription through OpenRouter."""

    def __init__(self, *, settings: Settings | None = None, client: OpenRouter | None = None):
        self._settings = settings or get_settings()
        self._model = self._settings.provider_model or DEFAULT_OPENROUTER_MODEL
        self._client = client or OpenRouter(api_key=self._settings.openrouter_api_key)

    @property
    def model(self) -> str:
        """Return the resolved OpenRouter model slug."""
        return self._model

    async def transcribe(self, *, prompt_text: str, image_bytes: bytes, mime_type: str) -> TranscriptionResult:
        """Send prompt + image to OpenRouter and return normalized text output."""
        request = self._build_request(prompt_text=prompt_text, image_bytes=image_bytes, mime_type=mime_type)
        try:
            response = await self._client.chat.send_async(
                messages=cast(list[ChatMessagesTypedDict], request.messages),
                model=request.model,
                http_referer=request.http_referer,
                x_open_router_title=request.x_open_router_title,
            )
        except Exception as exc:
            message = str(exc).lower()
            if "401" in message or "auth" in message or "api key" in message:
                raise ProviderAuthError("OpenRouter authentication failed") from exc
            raise ProviderError("OpenRouter request failed") from exc

        text = self._extract_text(response)
        model = self._get_optional_attr(response, "model") or self.model
        finish_reason = self._extract_finish_reason(response)
        usage_input_tokens, usage_output_tokens, usage_total_tokens = self._extract_usage(response)
        logger.info("OpenRouter transcription completed using model=%s", model)
        return TranscriptionResult(
            text=text,
            provider="openrouter",
            prompt_name="",
            model=model,
            finish_reason=finish_reason,
            usage_input_tokens=usage_input_tokens,
            usage_output_tokens=usage_output_tokens,
            usage_total_tokens=usage_total_tokens,
        )

    def _build_request(self, *, prompt_text: str, image_bytes: bytes, mime_type: str) -> OpenRouterRequest:
        image_b64 = base64.b64encode(image_bytes).decode("ascii")
        data_url = f"data:{mime_type};base64,{image_b64}"

        messages: list[dict[str, Any]] = [
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": prompt_text},
                    {"type": "image_url", "image_url": {"url": data_url}},
                ],
            }
        ]

        return OpenRouterRequest(
            model=self.model,
            messages=messages,
            http_referer=self._settings.openrouter_http_referer,
            x_open_router_title=self._settings.openrouter_app_title,
        )

    def _extract_text(self, response: Any) -> str:
        choices = self._get_optional_attr(response, "choices")
        if not choices:
            raise ProviderResponseError("OpenRouter response missing choices")

        first_choice = choices[0]
        message = self._get_optional_attr(first_choice, "message")
        if message is None:
            raise ProviderResponseError("OpenRouter response missing assistant message")

        content = self._get_optional_attr(message, "content")
        text = self._normalize_content(content)
        if not text:
            raise ProviderResponseError("OpenRouter response contained no transcription text")
        return text

    def _extract_finish_reason(self, response: Any) -> str | None:
        choices = self._get_optional_attr(response, "choices")
        if not choices:
            return None
        first_choice = choices[0]
        finish_reason = self._get_optional_attr(first_choice, "finish_reason")
        if isinstance(finish_reason, str) and finish_reason.strip():
            return finish_reason.strip()
        return None

    def _extract_usage(self, response: Any) -> tuple[int | None, int | None, int | None]:
        usage = self._get_optional_attr(response, "usage")
        if usage is None:
            return None, None, None

        input_tokens = self._as_int(self._get_optional_attr(usage, "prompt_tokens"))
        output_tokens = self._as_int(self._get_optional_attr(usage, "completion_tokens"))
        total_tokens = self._as_int(self._get_optional_attr(usage, "total_tokens"))

        if input_tokens is None:
            input_tokens = self._as_int(self._get_optional_attr(usage, "input_tokens"))
        if output_tokens is None:
            output_tokens = self._as_int(self._get_optional_attr(usage, "output_tokens"))
        if total_tokens is None:
            total_tokens = self._as_int(self._get_optional_attr(usage, "total"))

        return input_tokens, output_tokens, total_tokens

    def _normalize_content(self, content: Any) -> str:
        if isinstance(content, str):
            return content.strip()

        if isinstance(content, list):
            parts: list[str] = []
            for item in content:
                text_part = None
                text_part = item.get("text") if isinstance(item, dict) else self._get_optional_attr(item, "text")

                if isinstance(text_part, str) and text_part.strip():
                    parts.append(text_part.strip())
            return "\n".join(parts).strip()

        return ""

    @staticmethod
    def _get_optional_attr(obj: Any, key: str) -> Any:
        if obj is None:
            return None
        if isinstance(obj, dict):
            return obj.get(key)
        return getattr(obj, key, None)

    @staticmethod
    def _as_int(value: Any) -> int | None:
        if isinstance(value, int):
            return value
        return None
