"""Archive creation operation."""

from __future__ import annotations

import shutil
import tempfile
from pathlib import Path
from typing import Any

from cascade_fm.core.cache.cache_artifact import ArtifactCacheMixin
from cascade_fm.operations.base import Operation

_ARCHIVE_EXTENSION_BY_FORMAT = {
    "zip": ".zip",
    "tar": ".tar",
    "gztar": ".tar.gz",
    "bztar": ".tar.bz2",
    "xztar": ".tar.xz",
}


class CreateArchive(ArtifactCacheMixin, Operation):
    """Create a single archive from selected input files and directories."""

    @property
    def name(self) -> str:
        return "create_archive"

    @property
    def label(self) -> str:
        return "Create Archive"

    @property
    def description(self) -> str:
        return "Pack selected files and folders into one archive"

    def _execute_impl(self, files: list[Path], **params: Any) -> list[Path]:
        archive_name_raw = str(params.get("archive_name", "archive")).strip() or "archive"
        archive_name = Path(archive_name_raw).stem
        archive_format = str(params.get("format", "zip")).lower()

        output_dir = Path(tempfile.mkdtemp(prefix="cascade_archive_"))
        staging_dir = output_dir / "staging"
        staging_dir.mkdir(parents=True, exist_ok=True)

        total = len(files)
        used_names: set[str] = set()
        for index, source in enumerate(files, start=1):
            self.report_progress(index - 1, total, f"Preparing {source.name}")
            if not source.exists():
                continue

            destination_name = _unique_name(source.name, used_names)
            destination = staging_dir / destination_name

            if source.is_dir():
                shutil.copytree(source, destination)
            else:
                shutil.copy2(source, destination)

            used_names.add(destination_name)
            self.report_progress(index, total, f"Prepared {source.name}")

        self.report_progress(total, total, "Creating archive")

        archive_base = output_dir / archive_name
        archive_path_str = shutil.make_archive(
            base_name=str(archive_base),
            format=archive_format,
            root_dir=str(staging_dir),
            base_dir=".",
        )
        archive_path = Path(archive_path_str)

        expected_suffix = _ARCHIVE_EXTENSION_BY_FORMAT[archive_format]
        if not archive_path.name.endswith(expected_suffix):
            archive_path = archive_path.with_name(f"{archive_name}{expected_suffix}")

        self.report_progress(total, total, "Archive created")
        return [archive_path]

    def validate_params(self, **params: Any) -> tuple[bool, str | None]:
        archive_name = params.get("archive_name", "archive")
        if not isinstance(archive_name, str) or not archive_name.strip():
            return False, "archive_name must be a non-empty string"

        archive_format = params.get("format", "zip")
        if not isinstance(archive_format, str):
            return False, "format must be a string"
        if archive_format.lower() not in _ARCHIVE_EXTENSION_BY_FORMAT:
            allowed = ", ".join(sorted(_ARCHIVE_EXTENSION_BY_FORMAT))
            return False, f"format must be one of: {allowed}"

        return True, None


def _unique_name(name: str, used_names: set[str]) -> str:
    """Return a unique filename for archive staging."""
    if name not in used_names:
        return name

    path = Path(name)
    index = 1
    while True:
        candidate = f"{path.stem}_{index}{path.suffix}"
        if candidate not in used_names:
            return candidate
        index += 1
