"""
Gemini vision captcha solver — FREE tier API key (Google AI Studio).
Case-sensitive prompts (same idea as portal ll_exams_bot.php).
No billing required for normal free-tier quota.
"""
from __future__ import annotations

import base64
import json
import os
import re
import urllib.error
import urllib.request
from pathlib import Path

ROOT = Path(__file__).resolve().parent
ENV_PATH = ROOT / ".env"

MODELS = [
    "gemini-2.5-flash",
    "gemini-2.5-flash-lite",
    "gemini-flash-latest",
]

PROMPT = (
    "This is a CASE-SENSITIVE captcha (capital and small letters both matter). "
    "Read ALL characters carefully (usually 6). "
    "Reply with ONLY those characters in the EXACT case shown "
    "(e.g. kW217R not KW217R, h5Ow7y not H5OW7Y). "
    "Digits stay digits (0 vs O, 1 vs l). "
    "No spaces, no quotes, no explanation."
)


def _load_dotenv() -> None:
    if not ENV_PATH.is_file():
        return
    for line in ENV_PATH.read_text(encoding="utf-8", errors="ignore").splitlines():
        line = line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        k, v = line.split("=", 1)
        k, v = k.strip(), v.strip().strip('"').strip("'")
        if k and k not in os.environ:
            os.environ[k] = v


def get_api_key() -> str:
    _load_dotenv()
    key = (
        os.environ.get("GEMINI_API_KEY", "").strip()
        or os.environ.get("GOOGLE_API_KEY", "").strip()
    )
    # ignore placeholder
    if not key or key.upper().startswith("TEMP_") or "PASTE_YOUR" in key.upper():
        return ""
    return key


def clean_captcha(text: str, max_len: int = 8) -> str:
    t = re.sub(r"[^A-Za-z0-9]", "", (text or "").strip())
    return t[:max_len] if max_len > 0 else t


def _mime_from_bytes(data: bytes) -> str:
    if data[:8] == b"\x89PNG\r\n\x1a\n":
        return "image/png"
    if data[:2] == b"\xff\xd8":
        return "image/jpeg"
    if data[:4] == b"RIFF" and data[8:12] == b"WEBP":
        return "image/webp"
    return "image/png"


def _call_gemini(api_key: str, model: str, data: bytes, mime: str) -> str:
    b64 = base64.b64encode(data).decode("ascii")
    url = (
        f"https://generativelanguage.googleapis.com/v1beta/models/"
        f"{model}:generateContent?key={api_key}"
    )
    body = {
        "contents": [
            {
                "parts": [
                    {"text": PROMPT},
                    {"inline_data": {"mime_type": mime, "data": b64}},
                ]
            }
        ],
        "generationConfig": {
            "temperature": 0.1,
            "maxOutputTokens": 32,
        },
    }
    req = urllib.request.Request(
        url,
        data=json.dumps(body).encode("utf-8"),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=45) as resp:
        payload = json.loads(resp.read().decode("utf-8"))
    parts = (
        payload.get("candidates", [{}])[0]
        .get("content", {})
        .get("parts", [])
    )
    text = "".join(str(p.get("text", "")) for p in parts)
    return clean_captcha(text)


def solve_bytes(data: bytes) -> tuple[str, str]:
    """
    Returns (text, model_used). Raises RuntimeError on hard failure.
    """
    key = get_api_key()
    if not key:
        raise RuntimeError("GEMINI_API_KEY not set")

    mime = _mime_from_bytes(data)
    last_err = "Gemini failed"
    for model in MODELS:
        try:
            text = _call_gemini(key, model, data, mime)
            if len(text) >= 4:
                return text, model
            last_err = f"{model}: empty/short"
        except urllib.error.HTTPError as e:
            err_body = e.read().decode("utf-8", errors="ignore")[:300]
            last_err = f"{model}: HTTP {e.code} {err_body}"
            # auth/billing hard fail — stop
            if e.code in (401, 403):
                raise RuntimeError(last_err) from e
            continue
        except Exception as e:
            last_err = f"{model}: {e}"
            continue
    raise RuntimeError(last_err)


def available() -> bool:
    return bool(get_api_key())
