"""Scan cache in ~/.complyform/cache/ keyed by state content hash.

Cache entries older than 1 hour are considered expired.
"""

from __future__ import annotations

import hashlib
import json
import shutil
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

from complyform import __version__
from complyform.core.scanner.state_parser import ResourceRecord

CACHE_MAX_AGE_SECONDS = 3600  # 1 hour


class ScanCache:
    """Manage scan result cache."""

    def __init__(self, cache_dir: Path | None = None) -> None:
        if cache_dir is None:
            cache_dir = Path.home() / ".complyform" / "cache"
        self._cache_dir = cache_dir

    @staticmethod
    def _hash_content(content: bytes) -> str:
        return hashlib.sha256(content).hexdigest()

    def _entry_dir(self, hash_prefix: str) -> Path:
        return self._cache_dir / hash_prefix

    def write(
        self,
        state_content: bytes,
        state_path: str,
        cloud: str,
        resources: list[ResourceRecord],
        metadata: Any,
    ) -> str:
        """Write scan results to cache. Returns cache key."""
        full_hash = self._hash_content(state_content)
        prefix = full_hash[:16]
        entry_dir = self._entry_dir(prefix)
        entry_dir.mkdir(parents=True, exist_ok=True)

        inventory = [r.model_dump(mode="json") for r in resources]
        (entry_dir / "inventory.json").write_text(
            json.dumps(inventory, indent=2), encoding="utf-8"
        )

        cache_meta: dict[str, Any] = {
            "cache_version": 1,
            "state_hash": f"sha256:{full_hash}",
            "state_path": state_path,
            "cloud": cloud,
            "scanned_at": datetime.now(UTC).isoformat(),
            "resource_count": len(resources),
            "complyform_version": __version__,
        }
        (entry_dir / "metadata.json").write_text(
            json.dumps(cache_meta, indent=2), encoding="utf-8"
        )

        return prefix

    def read(
        self, state_hash_prefix: str
    ) -> tuple[list[ResourceRecord], dict[str, Any]] | None:
        """Read cached scan. None if not found or expired (>1h)."""
        entry_dir = self._entry_dir(state_hash_prefix)
        meta_path = entry_dir / "metadata.json"
        inv_path = entry_dir / "inventory.json"

        if not meta_path.exists() or not inv_path.exists():
            return None

        meta: dict[str, Any] = json.loads(meta_path.read_text(encoding="utf-8"))

        scanned_at = meta.get("scanned_at", "")
        try:
            scanned_dt = datetime.fromisoformat(scanned_at)
            age = (datetime.now(UTC) - scanned_dt).total_seconds()
            if age > CACHE_MAX_AGE_SECONDS:
                return None
        except ValueError, TypeError:
            return None

        raw_inv: list[dict[str, Any]] = json.loads(inv_path.read_text(encoding="utf-8"))
        resources = [ResourceRecord.model_validate(r) for r in raw_inv]
        return resources, meta

    def read_by_state_content(
        self, state_content: bytes
    ) -> tuple[list[ResourceRecord], dict[str, Any]] | None:
        """Compute hash from content, then read."""
        full_hash = self._hash_content(state_content)
        return self.read(full_hash[:16])

    def latest(self) -> tuple[list[ResourceRecord], dict[str, Any]] | None:
        """Return the most recently written non-expired cache entry."""
        if not self._cache_dir.exists():
            return None

        best: tuple[Path, datetime] | None = None
        for entry_dir in self._cache_dir.iterdir():
            if not entry_dir.is_dir():
                continue
            meta_path = entry_dir / "metadata.json"
            if not meta_path.exists():
                continue
            try:
                meta: dict[str, Any] = json.loads(meta_path.read_text(encoding="utf-8"))
                scanned_dt = datetime.fromisoformat(meta["scanned_at"])
                age = (datetime.now(UTC) - scanned_dt).total_seconds()
                if age > CACHE_MAX_AGE_SECONDS:
                    continue
                if best is None or scanned_dt > best[1]:
                    best = (entry_dir, scanned_dt)
            except ValueError, TypeError, KeyError:
                continue

        if best is None:
            return None

        prefix = best[0].name
        return self.read(prefix)

    def clear(self, state_hash_prefix: str) -> None:
        """Delete a single cache entry."""
        entry_dir = self._entry_dir(state_hash_prefix)
        if entry_dir.exists():
            shutil.rmtree(entry_dir)

    def clear_all(self) -> None:
        """Delete entire cache directory."""
        if self._cache_dir.exists():
            shutil.rmtree(self._cache_dir)
