"""DB-backed LLM call cache keyed by SHA-256 of inputs."""
from __future__ import annotations

import hashlib
from typing import Any

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from libs.common.logging import get_logger
from libs.common.time_utils import utc_now
from libs.llm.exceptions import LLMCacheError

logger = get_logger(__name__)

_SCHEMA_VERSION = "1.0.0"


def build_cache_key(
    text: str,
    prompt_name: str,
    prompt_version: str,
    model_name: str,
    schema_version: str = _SCHEMA_VERSION,
) -> str:
    """Return SHA-256 cache key for this (text, prompt, model, schema) combination."""
    text_hash = hashlib.sha256(text.encode()).hexdigest()
    hint_hash = hashlib.sha256(
        f"{prompt_name}:{prompt_version}:{model_name}:{schema_version}".encode()
    ).hexdigest()
    combined = f"{text_hash}:{hint_hash}"
    return hashlib.sha256(combined.encode()).hexdigest()


class LLMCacheStore:
    """Read/write LLM responses from/to the llm_call_cache table."""

    async def get(self, session: AsyncSession, cache_key: str) -> dict[str, Any] | None:
        """Return cached normalized dict if found, else None."""
        from libs.db.models import LLMCallCache  # late import to avoid circular deps

        try:
            result = await session.execute(
                select(LLMCallCache).where(LLMCallCache.cache_key == cache_key)
            )
            row = result.scalar_one_or_none()
            if row is None:
                return None
            logger.debug("llm_cache_hit", cache_key=cache_key[:16])
            return row.normalized_json  # type: ignore[return-value]
        except Exception as exc:
            raise LLMCacheError(
                f"Cache read failed: {exc}",
                source="llm_cache",
                context={"cache_key": cache_key[:16]},
            ) from exc

    async def put(
        self,
        session: AsyncSession,
        cache_key: str,
        document_id: str | None,
        model_name: str,
        prompt_version: str,
        schema_version: str,
        raw_prompt: str,
        raw_response: str,
        normalized: dict[str, Any],
        token_usage: dict[str, int],
        elapsed_ms: int,
    ) -> None:
        """Store a new cache entry. Silently skips on duplicate key."""
        from libs.db.models import LLMCallCache  # late import

        try:
            existing = await session.execute(
                select(LLMCallCache).where(LLMCallCache.cache_key == cache_key)
            )
            if existing.scalar_one_or_none() is not None:
                logger.debug("llm_cache_skip_dup", cache_key=cache_key[:16])
                return

            row = LLMCallCache(
                cache_key=cache_key,
                document_id=document_id,
                model_name=model_name,
                prompt_version=prompt_version,
                schema_version=schema_version,
                raw_prompt=raw_prompt,
                raw_response=raw_response,
                normalized_json=normalized,
                token_usage_json=token_usage,
                elapsed_ms=elapsed_ms,
                created_at_utc=utc_now(),
            )
            session.add(row)
            await session.flush()
            logger.debug("llm_cache_stored", cache_key=cache_key[:16], elapsed_ms=elapsed_ms)
        except Exception as exc:
            raise LLMCacheError(
                f"Cache write failed: {exc}",
                source="llm_cache",
                context={"cache_key": cache_key[:16]},
            ) from exc
