"""Journal-based cache helpers for operation-owned caching."""

from __future__ import annotations

from pathlib import Path
from typing import Any

from cascade_fm.core.types import OperationResult
from cascade_fm.operations.base import CommitIntent


class JournalCacheMixin:
    """Mixin that implements lightweight journal cache hooks.

    This cache mode stores metadata + result paths rather than artifact bytes.
    It is suitable for operations where replay is represented by a journal
    (e.g., rename/move mapping) instead of restored transformed files.
    """

    def cache_load(
        self,
        cache_key: str,
        files: list[Path],
        params: dict[str, Any],
        load_artifacts,
    ) -> OperationResult | None:
        """Try loading result from in-memory journal cache."""
        cache = self._journal_cache_store()
        journal = cache.get(cache_key)
        if journal is None:
            return None

        if not self._journal_is_valid(journal, files, params):
            return None

        result_files = [Path(path_str) for path_str in journal["result_files"]]
        if self._journal_requires_result_paths_exist() and not all(
            path.exists() for path in result_files
        ):
            return None

        return OperationResult.ok(result_files)

    @property
    def commit_intent(self) -> CommitIntent:
        """Journal-producing operations commit using planned output names."""
        return CommitIntent.PLANNED_OUTPUT_NAMES

    def cache_store(
        self,
        cache_key: str,
        result: OperationResult,
        files: list[Path],
        params: dict[str, Any],
        store_artifacts,
    ) -> None:
        """Store a lightweight journal entry for cache replay."""
        if not result.success:
            return

        cache = self._journal_cache_store()
        cache[cache_key] = {
            "files": [self._file_signature(path) for path in files],
            "params": params,
            "result_files": [str(path) for path in result.files],
        }

    def _journal_cache_store(self) -> dict[str, dict[str, Any]]:
        """Get or initialize the per-operation in-memory journal cache store."""
        existing = getattr(self, "_journal_cache", None)
        if existing is None:
            existing = {}
            setattr(self, "_journal_cache", existing)
        return existing

    def _journal_is_valid(
        self,
        journal: dict[str, Any],
        files: list[Path],
        params: dict[str, Any],
    ) -> bool:
        """Validate that input state still matches the cached journal."""
        if journal.get("params") != params:
            return False

        expected = journal.get("files")
        current = [self._file_signature(path) for path in files]
        return expected == current

    def _journal_requires_result_paths_exist(self) -> bool:
        """Whether cached result paths must exist on disk for cache replay."""
        return False

    def _file_signature(self, path: Path) -> dict[str, Any]:
        """Build metadata signature for a file path."""
        exists = path.exists()
        signature: dict[str, Any] = {
            "path": str(path),
            "exists": exists,
            "is_file": path.is_file() if exists else False,
            "is_dir": path.is_dir() if exists else False,
        }

        if exists:
            stat = path.stat()
            signature["size"] = stat.st_size
            signature["mtime_ns"] = stat.st_mtime_ns

        return signature
