"""Shared LLM utilities for Gemini Flash-Lite calls with disk caching.

All LLM features degrade gracefully when GOOGLE_API_KEY is not set.
"""

from __future__ import annotations

import hashlib
import json
import logging
import os
import urllib.request
from pathlib import Path

logger = logging.getLogger(__name__)

CACHE_DIR = Path.home() / ".orbit" / "cache"

_GEMINI_URL = (
    "https://generativelanguage.googleapis.com/v1beta/"
    "models/gemini-2.5-flash-lite:generateContent?key={key}"
)


def load_api_key() -> str | None:
    """Load GOOGLE_API_KEY from environment, orbit auth store, or backend/.env."""
    key = os.environ.get("GOOGLE_API_KEY")
    if key:
        return key

    # Check orbit auth store (~/.config/orbit/credentials.json)
    creds_path = Path.home() / ".config" / "orbit" / "credentials.json"
    if creds_path.exists():
        try:
            creds = json.loads(creds_path.read_text())
            stored_key = creds.get("google_api_key")
            if stored_key:
                return stored_key
        except (json.JSONDecodeError, OSError):
            pass

    # Search common .env locations
    candidates = [
        Path("backend/.env"),
        Path(__file__).parent.parent.parent / "backend" / ".env",
    ]
    for env_path in candidates:
        if env_path.exists():
            try:
                for line in env_path.read_text().splitlines():
                    line = line.strip()
                    if line.startswith("GOOGLE_API_KEY="):
                        val = line.split("=", 1)[1].strip().strip('"').strip("'")
                        if val:
                            return val
            except OSError:
                continue
    return None


def call_gemini(
    prompt: str,
    api_key: str,
    temperature: float = 0.0,
    max_tokens: int = 4096,
    conversation: list[dict] | None = None,
) -> str | None:
    """Call Gemini Flash-Lite and return the text response.

    Args:
        prompt: The prompt text (used when conversation is None).
        api_key: Google API key.
        temperature: Sampling temperature. 0 = deterministic.
        max_tokens: Maximum output tokens.
        conversation: Optional multi-turn conversation as
            [{"role": "user"|"assistant", "content": "..."}].

    Returns:
        Response text, or None on any failure.
    """
    if conversation:
        contents = [
            {
                "role": "model" if m["role"] == "assistant" else "user",
                "parts": [{"text": m["content"]}],
            }
            for m in conversation
        ]
    else:
        contents = [{"role": "user", "parts": [{"text": prompt}]}]

    body = json.dumps({
        "contents": contents,
        "generationConfig": {
            "temperature": temperature,
            "maxOutputTokens": max_tokens,
        },
    }).encode()

    url = _GEMINI_URL.format(key=api_key)
    try:
        req = urllib.request.Request(
            url, data=body, headers={"Content-Type": "application/json"},
        )
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read())
        candidate = data["candidates"][0]
        finish = candidate.get("finishReason", "")
        if finish not in ("STOP", "MAX_TOKENS"):
            logger.debug("Gemini finish reason: %s", finish)
            return None
        text = candidate["content"]["parts"][0]["text"]
        if finish == "MAX_TOKENS":
            logger.debug("Gemini response truncated at max_tokens")
        return text
    except Exception as e:
        logger.debug("Gemini call failed: %s", e)
        return None


# ---------------------------------------------------------------------------
# Disk cache
# ---------------------------------------------------------------------------


def cache_key(data: str) -> str:
    """Deterministic short hash for cache lookup."""
    return hashlib.md5(data.encode()).hexdigest()[:16]


def cache_get(key: str, category: str = "general") -> str | None:
    """Read a cached value, or None if miss."""
    f = CACHE_DIR / category / f"{key}.txt"
    if f.exists():
        try:
            return f.read_text()
        except OSError:
            return None
    return None


def cache_set(key: str, value: str, category: str = "general") -> None:
    """Write a value to the disk cache."""
    d = CACHE_DIR / category
    try:
        d.mkdir(parents=True, exist_ok=True)
        (d / f"{key}.txt").write_text(value)
    except OSError as e:
        logger.debug("Cache write failed (best-effort): %s", e)
