"""Gate 13B packaging and installation audit helpers."""

from __future__ import annotations

from dataclasses import dataclass
import json
from pathlib import Path
import subprocess
import sys
import tomllib
import zipfile

from .cli_ux import build_doctor_payload


GATE13B_EVAL_FILE = "gate13b_packaging_eval.json"
GATE13B_MANIFEST_FILE = "gate13b_release_manifest.json"
GATE13B_ARCHIVE_NAME = "ra-v1-gate13b-source.zip"


FORBIDDEN_ARCHIVE_PARTS = {
    ".git",
    ".venv",
    ".pytest_cache",
    "__pycache__",
    "dist",
    "build",
    "tmp",
}
FORBIDDEN_ARCHIVE_SUFFIXES = {
    ".pyc",
    ".pyo",
    ".sqlite",
    ".sqlite3",
    ".db",
    ".pt",
    ".pth",
    ".onnx",
    ".pkl",
    ".joblib",
    ".log",
}


@dataclass(slots=True)
class Gate13BPackagingReport:
    packaging_metadata_present: bool
    dependency_list_present: bool
    clean_install_instructions_present: bool
    optional_extras_documented: bool
    release_archive_created: bool
    archive_excludes_caches_states_models: bool
    clean_clone_install_passes: bool
    doctor_passes_after_install: bool
    manifest_created: bool
    archive_path: str
    manifest_path: str
    clean_clone_dir: str
    forbidden_archive_entries: list[str]
    dependency_count: int
    optional_extra_names: list[str]

    def to_dict(self) -> dict[str, object]:
        return {
            "packaging_metadata_present": self.packaging_metadata_present,
            "dependency_list_present": self.dependency_list_present,
            "clean_install_instructions_present": self.clean_install_instructions_present,
            "optional_extras_documented": self.optional_extras_documented,
            "release_archive_created": self.release_archive_created,
            "archive_excludes_caches_states_models": self.archive_excludes_caches_states_models,
            "clean_clone_install_passes": self.clean_clone_install_passes,
            "doctor_passes_after_install": self.doctor_passes_after_install,
            "manifest_created": self.manifest_created,
            "archive_path": self.archive_path,
            "manifest_path": self.manifest_path,
            "clean_clone_dir": self.clean_clone_dir,
            "forbidden_archive_entries": list(self.forbidden_archive_entries),
            "dependency_count": self.dependency_count,
            "optional_extra_names": list(self.optional_extra_names),
        }

    @classmethod
    def from_dict(cls, data: dict[str, object]) -> "Gate13BPackagingReport":
        return cls(
            packaging_metadata_present=bool(data["packaging_metadata_present"]),
            dependency_list_present=bool(data["dependency_list_present"]),
            clean_install_instructions_present=bool(data["clean_install_instructions_present"]),
            optional_extras_documented=bool(data["optional_extras_documented"]),
            release_archive_created=bool(data["release_archive_created"]),
            archive_excludes_caches_states_models=bool(data["archive_excludes_caches_states_models"]),
            clean_clone_install_passes=bool(data["clean_clone_install_passes"]),
            doctor_passes_after_install=bool(data["doctor_passes_after_install"]),
            manifest_created=bool(data["manifest_created"]),
            archive_path=str(data["archive_path"]),
            manifest_path=str(data["manifest_path"]),
            clean_clone_dir=str(data["clean_clone_dir"]),
            forbidden_archive_entries=[str(item) for item in data.get("forbidden_archive_entries", [])],
            dependency_count=int(data["dependency_count"]),
            optional_extra_names=[str(item) for item in data.get("optional_extra_names", [])],
        )


def run_gate13b_packaging_probe(state_dir: str | Path) -> Gate13BPackagingReport:
    base_dir = Path(state_dir).resolve()
    base_dir.mkdir(parents=True, exist_ok=True)
    release_dir = base_dir / "release"
    clone_dir = base_dir / "clean_clone"
    install_state_dir = base_dir / "installed_doctor_state"
    release_dir.mkdir(parents=True, exist_ok=True)
    clone_dir.mkdir(parents=True, exist_ok=True)

    pyproject_path = Path("pyproject.toml")
    pyproject = tomllib.loads(pyproject_path.read_text(encoding="utf-8")) if pyproject_path.is_file() else {}
    project = pyproject.get("project", {}) if isinstance(pyproject, dict) else {}
    optional_dependencies = project.get("optional-dependencies", {}) if isinstance(project, dict) else {}
    dependencies = project.get("dependencies", []) if isinstance(project, dict) else []

    archive_path = release_dir / GATE13B_ARCHIVE_NAME
    archive_created = _create_git_archive(archive_path)
    forbidden_entries: list[str] = []
    if archive_created:
        forbidden_entries = _forbidden_archive_entries(archive_path)
        _extract_archive(archive_path, clone_dir)

    clean_clone_install_passes = _clean_clone_install_probe(clone_dir)
    doctor_payload = build_doctor_payload(install_state_dir) if clean_clone_install_passes else {"passes": False}
    doctor_passes = bool(doctor_payload.get("passes"))

    manifest_path = release_dir / GATE13B_MANIFEST_FILE
    manifest_payload = {
        "gate": "13B",
        "archive": archive_path.name,
        "package_name": str(project.get("name", "")) if isinstance(project, dict) else "",
        "version": str(project.get("version", "")) if isinstance(project, dict) else "",
        "dependencies": list(dependencies) if isinstance(dependencies, list) else [],
        "optional_extras": sorted(str(name) for name in optional_dependencies.keys()) if isinstance(optional_dependencies, dict) else [],
        "forbidden_archive_entries": forbidden_entries,
        "clean_clone_install_passes": clean_clone_install_passes,
        "doctor_passes_after_install": doctor_passes,
    }
    manifest_path.write_text(json.dumps(manifest_payload, indent=2, sort_keys=True), encoding="utf-8")

    readme_text = Path("README.md").read_text(encoding="utf-8") if Path("README.md").is_file() else ""
    return Gate13BPackagingReport(
        packaging_metadata_present=_metadata_present(project),
        dependency_list_present=isinstance(dependencies, list),
        clean_install_instructions_present="pip install -e" in readme_text and "python -m ra_cpu.cli" in readme_text,
        optional_extras_documented=isinstance(optional_dependencies, dict) and "dev" in optional_dependencies,
        release_archive_created=archive_created and archive_path.is_file() and archive_path.stat().st_size > 0,
        archive_excludes_caches_states_models=not forbidden_entries,
        clean_clone_install_passes=clean_clone_install_passes,
        doctor_passes_after_install=doctor_passes,
        manifest_created=manifest_path.is_file(),
        archive_path=str(archive_path),
        manifest_path=str(manifest_path),
        clean_clone_dir=str(clone_dir),
        forbidden_archive_entries=forbidden_entries,
        dependency_count=len(dependencies) if isinstance(dependencies, list) else 0,
        optional_extra_names=sorted(str(name) for name in optional_dependencies.keys()) if isinstance(optional_dependencies, dict) else [],
    )


def _metadata_present(project: object) -> bool:
    if not isinstance(project, dict):
        return False
    required = ("name", "version", "description", "readme", "requires-python")
    return all(bool(project.get(key)) for key in required)


def _create_git_archive(path: Path) -> bool:
    result = subprocess.run(
        ["git", "archive", "--format=zip", "--output", str(path), "HEAD"],
        capture_output=True,
        text=True,
        check=False,
    )
    return result.returncode == 0 and path.is_file()


def _extract_archive(archive_path: Path, target_dir: Path) -> None:
    with zipfile.ZipFile(archive_path) as archive:
        archive.extractall(target_dir)


def _forbidden_archive_entries(archive_path: Path) -> list[str]:
    forbidden: list[str] = []
    with zipfile.ZipFile(archive_path) as archive:
        for name in archive.namelist():
            path = Path(name)
            parts = set(path.parts)
            suffix = path.suffix.lower()
            if parts & FORBIDDEN_ARCHIVE_PARTS or suffix in FORBIDDEN_ARCHIVE_SUFFIXES:
                forbidden.append(name)
            if "state" in parts and suffix in {".json", ".jsonl", ".sqlite", ".sqlite3", ".db"}:
                forbidden.append(name)
    return sorted(set(forbidden))


def _clean_clone_install_probe(clone_dir: Path) -> bool:
    pyproject = clone_dir / "pyproject.toml"
    package_dir = clone_dir / "src" / "ra_cpu"
    cli_path = package_dir / "cli.py"
    if not pyproject.is_file() or not cli_path.is_file():
        return False
    result = subprocess.run(
        [
            sys.executable,
            "-c",
            "import ra_cpu.cli; print('ok')",
        ],
        cwd=clone_dir,
        env={"PYTHONPATH": str(clone_dir / "src")},
        capture_output=True,
        text=True,
        check=False,
    )
    return result.returncode == 0 and "ok" in result.stdout
