"""Gemini transcription provider.

Modelled on the working implementation in garden2
(`/srv/apps/garden2/backend/app/services/ai/transcribe.py`) - same stdlib
`urllib` approach, same inline-base64 upload, same response unwrapping. The
prompt differs: this one is tuned for plant maintenance vocabulary rather
than gardening.

Inline base64 is fine at our size ceiling (media.MAX_BYTES is 25 MB, and a
voice memo is far smaller). Anything larger would need the Files API.

The blocking HTTP call runs in a worker thread via `asyncio.to_thread` so the
event loop is never blocked - this is why the route is `async def`
(`react.md`: async only for AI and SSE routes).

The API key travels in the request URL and is never logged, echoed, or
included in an error message.
"""

from __future__ import annotations

import asyncio
import base64
import json
import urllib.error
import urllib.request

from app.config import get_settings
from app.services.ai.providers.base import (
    AI_BAD_RESPONSE,
    AI_NOT_CONFIGURED,
    AI_UPSTREAM_ERROR,
    TranscriptionError,
)

DEFAULT_MODEL = "gemini-2.5-flash"

# Written against how a technician actually talks on a plant floor. Mike's own
# example recording is the reference: "conveyor one packaging, replaced 10 rows
# of modular belt, the one horsepower motor is running at 80% and is making a
# high-pitched whining noise."
_PROMPT = (
    "Transcribe this audio recording verbatim. It is a short voice memo from "
    "an industrial maintenance technician describing a problem with plant "
    "equipment - conveyors, motors, bearings, belts, gearboxes, pumps, "
    "sprockets, and the parts used to repair them. "
    "Return ONLY the transcript text. No preamble, no explanation, no "
    "quotation marks. Keep the speaker's own words and their equipment names. "
    "Write measurements and part numbers as spoken (for example '2 HP', "
    "'8-32 machine bolts'). Resolve obvious mis-hearings of maintenance terms "
    "when the context makes the intended word clear. "
    "If the audio contains no intelligible speech, return an empty response."
)


class GeminiTranscriptionProvider:
    name = "gemini"

    def is_configured(self) -> bool:
        return get_settings().has_gemini_key

    async def transcribe(self, audio: bytes, mime: str) -> str:
        settings = get_settings()
        if not settings.has_gemini_key:
            raise TranscriptionError(
                AI_NOT_CONFIGURED,
                "Voice transcription is not configured on this server.",
                "CP_GEMINI_API_KEY is unset. Type the description instead, or "
                "use the microphone on your phone keyboard.",
            )
        return await asyncio.to_thread(
            self._transcribe_sync,
            audio,
            mime,
            api_key=settings.gemini_api_key,
            model=settings.transcribe_model or DEFAULT_MODEL,
        )

    @staticmethod
    def _transcribe_sync(audio: bytes, mime: str, *, api_key: str, model: str) -> str:
        payload = {
            "contents": [
                {
                    "parts": [
                        {"text": _PROMPT},
                        {
                            "inline_data": {
                                "mime_type": mime,
                                "data": base64.b64encode(audio).decode("ascii"),
                            }
                        },
                    ]
                }
            ],
            "generationConfig": {"temperature": 0.0, "responseMimeType": "text/plain"},
        }
        url = (
            "https://generativelanguage.googleapis.com/v1beta/models/"
            f"{model}:generateContent?key={api_key}"
        )
        request = urllib.request.Request(
            url,
            data=json.dumps(payload).encode("utf-8"),
            headers={"Content-Type": "application/json"},
            method="POST",
        )

        try:
            with urllib.request.urlopen(request, timeout=120) as response:
                body = response.read().decode("utf-8")
        except urllib.error.HTTPError as exc:
            # The response body can echo the request URL, which carries the
            # key - so the upstream detail is NOT propagated to the client.
            raise TranscriptionError(
                AI_UPSTREAM_ERROR,
                f"The transcription service returned an error ({exc.code}).",
                "Try again, or type the description instead.",
            ) from None
        except urllib.error.URLError:
            raise TranscriptionError(
                AI_UPSTREAM_ERROR,
                "The transcription service could not be reached.",
                "Check the server's connectivity, or type the description.",
            ) from None

        try:
            data = json.loads(body)
        except json.JSONDecodeError:
            raise TranscriptionError(
                AI_BAD_RESPONSE, "The transcription service sent an unreadable reply."
            ) from None

        candidates = data.get("candidates") or []
        if not candidates:
            # Safety blocks and empty audio both land here. Neither is an error
            # worth failing the capture over.
            return ""
        parts = (candidates[0].get("content") or {}).get("parts") or []
        text = "".join(part.get("text", "") for part in parts)
        return text.strip()
