"""Запасной путь извлечения через Gemini Vision.

Когда нативный парсер не справился (битый текстовый слой, нестандартная вёрстка,
скан), страница PDF отрисовывается в картинку и отдаётся Gemini с требованием
вернуть позиции прайса строго по JSON-схеме. Требует переменную окружения
GEMINI_API_KEY; модель задаётся через GEMINI_MODEL (по умолчанию gemini-2.0-flash).
"""

from __future__ import annotations

import json
import os
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[2]))

from contracts.models import RawRow  # noqa: E402

_VALID_TIERS = {"resident", "nonresident", "cis", "far", "insurance", "partner"}

_PROMPT = """Ты извлекаешь позиции из прайс-листа медицинской клиники на изображении.
Верни СТРОГО JSON-массив объектов, без пояснений и текста вокруг. Поля объекта:
- "name": название услуги как в документе (строка);
- "code": код услуги или тарификатора, если есть, иначе null;
- "unit": единица измерения, если есть, иначе null;
- "prices": объект «тариф → число в тенге». Ключи тарифов:
  "resident" (резидент / гражданин РК), "nonresident" (нерезидент / иностранец),
  "cis" (СНГ / ближнее зарубежье), "far" (дальнее зарубежье),
  "insurance" (страховые компании), "partner" (партнёр).
  Если в строке одна цена — положи её в "resident".
Пропускай заголовки разделов, итоги и строки без цены. Числа — без пробелов и валюты."""


def _render_pages(path: str, max_pages: int, dpi: int) -> list[bytes]:
    """Отрисовать первые страницы PDF в PNG-картинки."""
    import pymupdf  # PyMuPDF

    document = pymupdf.open(path)
    images = []
    for page in document[:max_pages]:
        images.append(page.get_pixmap(dpi=dpi).tobytes("png"))
    return images


def _parse_response(text: str) -> list[RawRow]:
    try:
        data = json.loads(text)
    except (json.JSONDecodeError, ValueError):
        return []
    rows: list[RawRow] = []
    for obj in data if isinstance(data, list) else []:
        if not isinstance(obj, dict):
            continue
        name = str(obj.get("name") or "").strip()
        prices = {
            tier: float(value)
            for tier, value in (obj.get("prices") or {}).items()
            if tier in _VALID_TIERS and isinstance(value, (int, float)) and value > 0
        }
        if name and prices:
            rows.append(
                RawRow(
                    service_name_raw=name,
                    service_code_source=(obj.get("code") or None),
                    prices=prices,
                    unit=(obj.get("unit") or None),
                )
            )
    return rows


def extract_with_vision(path: str, max_pages: int = 40, dpi: int = 140) -> list[RawRow]:
    """Извлечь позиции из PDF через Gemini Vision. Возвращает список RawRow.

    Страницы распознаются по одной. При превышении квоты (429) делается пауза и
    повтор — большой документ не должен срываться из-за лимита запросов в минуту.
    """
    import time

    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        raise RuntimeError("нет GEMINI_API_KEY в окружении")

    from google import genai
    from google.genai import types

    client = genai.Client(api_key=api_key)
    model = os.environ.get("GEMINI_MODEL", "gemini-2.5-flash")
    config = types.GenerateContentConfig(response_mime_type="application/json", temperature=0)

    rows: list[RawRow] = []
    for png in _render_pages(path, max_pages, dpi):
        part = types.Part.from_bytes(data=png, mime_type="image/png")
        for attempt in range(6):
            try:
                response = client.models.generate_content(
                    model=model, contents=[part, _PROMPT], config=config
                )
                rows.extend(_parse_response(response.text))
                break
            except Exception as exc:  # пауза и повтор при превышении квоты (429)
                if ("RESOURCE_EXHAUSTED" in str(exc) or "429" in str(exc)) and attempt < 5:
                    time.sleep(15)
                    continue
                raise
    return rows
