"""Citation cache backed by AgentCore Memory.

When ``bedrock-agentcore`` is installed and AgentCore Memory is configured,
resolved :class:`CitationRecord` objects are cached to avoid redundant
Data Source API calls on repeated lookups.

Follows the lazy-import pattern from :mod:`alice_cli.agentcore_deps` —
``bedrock-agentcore`` is imported only when :class:`CitationCache` is
instantiated.
"""

from __future__ import annotations

import hashlib
from typing import TYPE_CHECKING

import structlog

from alice_cli.cite.models import CitationRecord

if TYPE_CHECKING:
    from alice_cli.cite.models import CleanedReference

logger = structlog.get_logger(__name__)


def is_agentcore_memory_available() -> bool:
    """Check if ``bedrock-agentcore`` is importable and memory is configured.

    Returns ``False`` on :class:`ImportError` or missing configuration.
    """
    try:
        import bedrock_agentcore  # noqa: F401
        from bedrock_agentcore.memory import MemoryClient  # noqa: F401

        return True
    except (ImportError, Exception):
        return False


class CitationCache:
    """Cache for resolved :class:`CitationRecord` objects backed by AgentCore Memory.

    Follows the lazy-import pattern from ``agentcore_deps.py`` —
    ``bedrock-agentcore`` is imported only when this class is instantiated.

    Cache keys are derived from the identifier value (for identifier-based
    lookups) or a normalized hash of the query text (for free-text lookups).
    :class:`CitationRecord` objects are serialized to JSON for storage.
    """

    def __init__(self, region: str = "us-east-1") -> None:
        """Create the AgentCore Memory client for citation caching.

        Parameters
        ----------
        region:
            AWS region for the memory client.

        Raises
        ------
        ImportError
            If ``bedrock-agentcore`` is not installed.
        """
        from bedrock_agentcore.memory import MemoryClient

        self._memory = MemoryClient(region_name=region)
        logger.debug("CitationCache initialised", region=region)

    # ── Key generation ────────────────────────────────────────────────

    @staticmethod
    def _cache_key(ref: CleanedReference) -> str:
        """Derive a cache key from a :class:`CleanedReference`.

        For identifier-based lookups the key is the identifier value itself.
        For free-text lookups the key is a SHA-256 hex digest of the
        lowercased, whitespace-normalized query text.
        """
        if ref.identifier is not None:
            return f"cite:{ref.identifier.type.value}:{ref.identifier.value}"

        normalized = ref.cleaned_text.strip().lower()
        text_hash = hashlib.sha256(normalized.encode("utf-8")).hexdigest()
        return f"cite:query:{text_hash}"

    # ── Public API ────────────────────────────────────────────────────

    def get(self, ref: CleanedReference) -> CitationRecord | None:
        """Look up a cached :class:`CitationRecord` by identifier or query text.

        Returns ``None`` on cache miss or any error (logs via structlog).
        """
        key = self._cache_key(ref)
        try:
            result = self._memory.get(key)
            if result is None:
                logger.debug("Cache miss", key=key)
                return None

            record = CitationRecord.model_validate_json(result)
            logger.debug("Cache hit", key=key)
            return record
        except Exception:
            logger.debug("Cache get failed", key=key, exc_info=True)
            return None

    def put(self, ref: CleanedReference, record: CitationRecord) -> None:
        """Store a resolved :class:`CitationRecord` in the cache.

        Silently logs and returns on any error (never raises).
        """
        key = self._cache_key(ref)
        try:
            data = record.model_dump_json()
            self._memory.put(key, data)
            logger.debug("Cache put", key=key)
        except Exception:
            logger.debug("Cache put failed", key=key, exc_info=True)
