from __future__ import annotations

import hashlib
import os
import shutil
import stat
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional


class QuarantineVault:
    """Secure local quarantine vault for suspicious objects."""

    def __init__(self, root: str | Path):
        self.root = Path(root)
        self.objects_dir = self.root / "objects"
        self.metadata_dir = self.root / "metadata"
        self.objects_dir.mkdir(parents=True, exist_ok=True)
        self.metadata_dir.mkdir(parents=True, exist_ok=True)

    @staticmethod
    def _utc_now() -> str:
        return datetime.now(timezone.utc).isoformat(timespec="milliseconds")

    @staticmethod
    def _sha256(path: str | Path) -> str:
        digest = hashlib.sha256()
        with open(path, "rb") as handle:
            for chunk in iter(lambda: handle.read(65536), b""):
                digest.update(chunk)
        return digest.hexdigest()

    @staticmethod
    def _safe_parent(path: str | Path) -> Path:
        return Path(path).resolve().parent

    def quarantine_file(
        self,
        original_path: str,
        finding_id: str,
        transaction_id: str,
        reason: str,
    ) -> Dict[str, Any]:
        src = Path(original_path)
        if not src.exists():
            raise FileNotFoundError(f"Source not found: {original_path}")

        digest = self._sha256(src)
        quarantine_id = f"Q-{digest[:8].upper()}-{len(list(self.objects_dir.iterdir())):04d}"
        dest = self.objects_dir / f"{quarantine_id}_{src.name}"

        shutil.copy2(str(src), str(dest))
        source_stat = src.stat()
        metadata = {
            "quarantine_id": quarantine_id,
            "original_path": str(src),
            "quarantine_path": str(dest),
            "sha256": digest,
            "original_size": source_stat.st_size,
            "created": datetime.fromtimestamp(source_stat.st_ctime, tz=timezone.utc).isoformat(),
            "modified": datetime.fromtimestamp(source_stat.st_mtime, tz=timezone.utc).isoformat(),
            "owner": None,
            "acl": None,
            "signer": None,
            "finding_id": finding_id,
            "transaction_id": transaction_id,
            "timestamp": self._utc_now(),
            "reason": reason,
            "restore_allowed": True,
        }

        metadata_path = self.metadata_dir / f"{quarantine_id}.json"
        metadata_path.write_text(__import__("json").dumps(metadata, indent=2, sort_keys=True), encoding="utf-8")

        if self._sha256(dest) != digest:
            raise RuntimeError("Quarantine verification failed")

        try:
            os.remove(str(src))
        except PermissionError:
            pass

        return metadata

    def verify_quarantine(self, quarantine_id: str) -> bool:
        metadata_path = self.metadata_dir / f"{quarantine_id}.json"
        if not metadata_path.exists():
            return False
        metadata = __import__("json").loads(metadata_path.read_text(encoding="utf-8"))
        copy_path = Path(metadata["quarantine_path"])
        return copy_path.exists() and (self._sha256(copy_path) == metadata["sha256"])

    def restore_file(self, quarantine_id: str, destination: str | Path) -> str:
        metadata_path = self.metadata_dir / f"{quarantine_id}.json"
        if not metadata_path.exists():
            raise FileNotFoundError(f"metadata for quarantine {quarantine_id!r} not found")

        metadata = __import__("json").loads(metadata_path.read_text(encoding="utf-8"))
        source = Path(metadata["quarantine_path"])
        target = Path(destination)

        if target.exists():
            raise FileExistsError(f"Destination already exists: {target}")

        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(str(source), str(target))
        return str(target)

    def list_quarantine(self) -> list[Dict[str, Any]]:
        items = []
        for metadata_path in sorted(self.metadata_dir.glob("*.json")):
            try:
                items.append(__import__("json").loads(metadata_path.read_text(encoding="utf-8")))
            except Exception:
                continue
        return items
