"""Bundle a case directory into a self-contained tar.gz (Phase 17).

Pulls the archive logic out of the CLI body so the GUI worker can
reuse the same code path. The function refreshes the manifest before
archiving (so the archive carries the latest hashes), refuses to
overwrite an existing destination, and transitions the case through
``COMPLETED → ARCHIVED`` on success.
"""

from __future__ import annotations

import logging
import tarfile
from typing import TYPE_CHECKING

from mgf.vm.core.exceptions import OperationError

from mgf.vm.forensics.case import CaseStatus
from mgf.vm.forensics.manifest import build_manifest, save_manifest

if TYPE_CHECKING:
    from pathlib import Path

    from mgf.vm.forensics.store import CaseStore


__all__ = ["archive_case"]


LOG = logging.getLogger("mgf.vm.forensics.archive")


def archive_case(
    store: CaseStore,
    name: str,
    target_path: Path,
    *,
    refresh_manifest: bool = True,
) -> Path:
    """Bundle ``name`` into a tar.gz at ``target_path``; transition to ARCHIVED.

    Parameters:
        store: the :class:`CaseStore` that owns the case.
        name: the case name (must already exist; raises
            :class:`~mgf.vm.exceptions.VManagerError` from
            :meth:`store.load` otherwise).
        target_path: where to write the tarball. **Must not already
            exist** — refuses to overwrite, mirroring
            the app backup flow's contract.
        refresh_manifest: re-hash every artifact before tarring so
            ``case show --verify`` against the unpacked archive
            stays clean. Default ``True``; tests and headless
            scripted exports can pass ``False`` to skip the rehash
            on a known-clean case.

    Returns the resolved ``target_path`` for chaining.

    Side effects:
        - Writes the tarball at ``target_path``.
        - Refreshes ``<case>/manifest.json`` if ``refresh_manifest``.
        - Transitions the case through ``COMPLETED`` (if it was
          PREPARING / ACTIVE) and then to ``ARCHIVED``. The status
          file inside the case directory IS updated *before* the
          tar is created so the archived case.yaml shows
          ``status: archived``.
    """
    case = store.load(name)
    case_dir = store.case_dir(name)

    if target_path.exists():
        msg = f"refusing to overwrite existing tarball at {target_path}"
        raise OperationError(msg)

    if refresh_manifest:
        save_manifest(case_dir, build_manifest(case_dir))

    # Transition status BEFORE tarring so the archive contains
    # case.yaml with the final ARCHIVED status.
    if case.status in (CaseStatus.PREPARING, CaseStatus.ACTIVE):
        store.transition(name, CaseStatus.COMPLETED)
    store.transition(name, CaseStatus.ARCHIVED)

    target_path.parent.mkdir(parents=True, exist_ok=True)
    with tarfile.open(target_path, mode="w:gz") as tar:
        tar.add(case_dir, arcname=name)

    LOG.info(
        "case archived",
        extra={
            "case_name": name,
            "target_path": str(target_path),
            "size_bytes": target_path.stat().st_size,
        },
    )
    return target_path
