"""Disk-based SHA-256 cache for IR bundles.

Cache key is a hash of (page_node fields, api_manifest, user_prompt, model_name).
Cache files live at <project_root>/.cache/ir_pages/<key>.json.

IRPage is a plain dataclass whose two fields (page_node, ir_bundle) are both
Pydantic models, so we serialize them individually.
"""
from __future__ import annotations

import hashlib
import json
import logging
from pathlib import Path

from shared.schemas.app_ir import IRPage
from shared.schemas.app_plan import PageNode
from shared.schemas.ir_bundle import IRBundle

logger = logging.getLogger(__name__)

_CACHE_DIR = Path(__file__).resolve().parents[1] / ".cache" / "ir_pages"


def _cache_key(
    page_node: object,
    api_manifest: dict | None,
    user_prompt: str | None,
    model_name: str,
) -> str:
    manifest_hash = hashlib.sha256(
        json.dumps(api_manifest or {}, sort_keys=True).encode()
    ).hexdigest()[:16]
    payload = json.dumps(
        {
            "page_id":       getattr(page_node, "page_id", ""),
            "route_path":    getattr(page_node, "route_path", ""),
            "page_type":     getattr(page_node, "page_type", ""),
            "components":    getattr(page_node, "components", []),
            "user_prompt":   (user_prompt or "")[:500],
            "model_name":    model_name,
            "manifest_hash": manifest_hash,
        },
        sort_keys=True,
    )
    return hashlib.sha256(payload.encode()).hexdigest()


def load_cached_ir(
    page_node: object,
    api_manifest: dict | None,
    user_prompt: str | None,
    model_name: str,
) -> IRPage | None:
    """Return a cached IRPage if available, else None."""
    key = _cache_key(page_node, api_manifest, user_prompt, model_name)
    p = _CACHE_DIR / f"{key}.json"
    if not p.exists():
        return None
    try:
        data = json.loads(p.read_text(encoding="utf-8"))
        ir_page = IRPage(
            page_node=PageNode.model_validate(data["page_node"]),
            ir_bundle=IRBundle.model_validate(data["ir_bundle"]),
        )
        logger.info(
            "  IR cache HIT  | page='%s' | key=%s…",
            getattr(page_node, "page_id", "?"), key[:12],
        )
        return ir_page
    except Exception as exc:
        logger.warning(
            "  IR cache corrupt for page='%s', ignoring | error=%s",
            getattr(page_node, "page_id", "?"), exc,
        )
        return None


def save_ir_to_cache(
    ir_page: IRPage,
    page_node: object,
    api_manifest: dict | None,
    user_prompt: str | None,
    model_name: str,
) -> None:
    """Persist an IRPage to the disk cache."""
    key = _cache_key(page_node, api_manifest, user_prompt, model_name)
    _CACHE_DIR.mkdir(parents=True, exist_ok=True)
    p = _CACHE_DIR / f"{key}.json"
    payload = json.dumps(
        {
            "page_node": json.loads(ir_page.page_node.model_dump_json()),
            "ir_bundle": json.loads(ir_page.ir_bundle.model_dump_json()),
        },
        indent=2,
    )
    p.write_text(payload + "\n", encoding="utf-8")
    logger.info(
        "  IR cache SAVED | page='%s' | key=%s…",
        getattr(page_node, "page_id", "?"), key[:12],
    )
