import time
import logging

import requests

from app.infra.constants import MAX_RETRIES
from app.core.shared.gigachat.errors import GigaChatError
from app.core.shared.gigachat.settings import GigaChatSettings
from app.core.shared.gigachat.token_provider import GigaChatTokenProvider
from app.core.shared.network.hard_timeout import run_with_hard_timeout

LOGGER = logging.getLogger(__name__)


class GigaChatClient:
    def __init__(self, settings: GigaChatSettings, token_provider: GigaChatTokenProvider) -> None:
        self._settings = settings
        self._tokens = token_provider

    def complete(self, system_prompt: str, user_prompt: str) -> str:
        token = self._tokens.get_access_token()
        payload = {
            "model": self._settings.model,
            "messages": [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt},
            ],
        }
        response = self._post_with_retry("/chat/completions", payload, token=token, timeout=90, operation_name="completion")
        data = response.json()
        choices = data.get("choices") or []
        if not choices:
            return ""
        message = choices[0].get("message") or {}
        return str(message.get("content") or "")

    def embed(
        self,
        texts: list[str],
        *,
        timeout_sec: int | None = None,
        max_retries: int | None = None,
    ) -> list[list[float]]:
        token = self._tokens.get_access_token()
        payload = {
            "model": self._settings.embedding_model,
            "input": texts,
        }
        response = self._post_with_retry(
            "/embeddings",
            payload,
            token=token,
            timeout=timeout_sec or 90,
            operation_name="embeddings",
            max_retries=max_retries,
        )
        data = response.json()
        items = data.get("data")
        if not isinstance(items, list):
            raise GigaChatError("Unexpected GigaChat embeddings response")
        return [list(map(float, x.get("embedding") or [])) for x in items]

    def _post_with_retry(
        self,
        path: str,
        payload: dict,
        *,
        token: str,
        timeout: int,
        operation_name: str,
        max_retries: int | None = None,
    ):
        last_error: Exception | None = None
        retries = max(1, int(max_retries or MAX_RETRIES))
        for attempt in range(1, retries + 1):
            try:
                LOGGER.warning(
                    "gigachat request start: operation=%s path=%s attempt=%s/%s timeout_sec=%s",
                    operation_name,
                    path,
                    attempt,
                    retries,
                    timeout,
                )
                response = run_with_hard_timeout(
                    lambda: requests.post(
                        f"{self._settings.api_url.rstrip('/')}{path}",
                        json=payload,
                        headers={
                            "Authorization": f"Bearer {token}",
                            "Content-Type": "application/json",
                        },
                        timeout=timeout,
                        verify=self._settings.ssl_verify,
                    ),
                    timeout_sec=timeout,
                    operation_name=f"gigachat_{operation_name}",
                )
                LOGGER.warning(
                    "gigachat request done: operation=%s path=%s attempt=%s/%s status=%s",
                    operation_name,
                    path,
                    attempt,
                    retries,
                    response.status_code,
                )
            except requests.RequestException as exc:
                LOGGER.exception(
                    "gigachat request failed: operation=%s path=%s attempt=%s/%s",
                    operation_name,
                    path,
                    attempt,
                    retries,
                )
                last_error = GigaChatError(f"GigaChat {operation_name} request failed: {exc}")
            else:
                if response.status_code < 400:
                    return response
                last_error = GigaChatError(f"GigaChat {operation_name} error {response.status_code}: {response.text}")
                if not self._is_retryable_status(response.status_code):
                    raise last_error
            if attempt == retries:
                break
            time.sleep(0.1 * attempt)
        if last_error is None:
            raise GigaChatError(f"GigaChat {operation_name} failed without response")
        raise last_error

    def _is_retryable_status(self, status_code: int) -> bool:
        return status_code == 429 or status_code >= 500
