"""Package manifest reading and compatibility checks."""

from __future__ import annotations

import json
import os
import platform
import struct

from mira.crypto import open_mira_zip
from mira.keys import resolve_secret
from mira.manifest_schema import validate_manifest


class PackageResolver:
    """Read manifests and validate compatibility of .mira packages."""

    def read_manifest(self, mira_path: str, key: str | None = None) -> dict:
        """Read manifest.json from a .mira archive.

        Args:
            mira_path: Path to a .mira file.
            key: Optional encryption key for protected archives.

        Returns:
            Manifest dictionary.
        """
        if not os.path.isfile(mira_path):
            raise RuntimeError(f".mira file not found: {mira_path}")
        key = resolve_secret(key, None, "MIRA_KEY", "MIRA_KEY_FILE")
        with open_mira_zip(mira_path, key) as zip_file:
            try:
                data = zip_file.read("manifest.json")
            except KeyError as exc:
                raise RuntimeError("manifest.json not found in archive") from exc
        manifest = json.loads(data.decode("utf-8"))
        validate_manifest(manifest)
        return manifest

    def check_compatibility(self, manifest: dict) -> tuple[bool, list[str]]:
        """Check whether a manifest is compatible with the current environment.

        Returns:
            (is_compatible, warnings)
        """
        warnings: list[str] = []

        required_python = manifest.get("python_version", "")
        current_python = platform.python_version()
        if required_python:
            required_major_minor = ".".join(required_python.split(".")[:2])
            current_major_minor = ".".join(current_python.split(".")[:2])
            if required_major_minor != current_major_minor:
                warnings.append(
                    f"Python version mismatch: required {required_major_minor}, current {current_major_minor}"
                )

        required_os = str(manifest.get("os", "")).lower()
        current_os = self._normalize_os(platform.system())
        if required_os and required_os != current_os:
            warnings.append(f"OS mismatch: required {required_os}, current {current_os}")

        required_arch = str(manifest.get("arch", "")).lower()
        current_arch = self._normalize_arch(platform.machine())
        if required_arch and required_arch != current_arch:
            warnings.append(
                f"Architecture mismatch: required {required_arch}, current {current_arch}"
            )

        required_ptr = manifest.get("pointer_size")
        if required_ptr:
            current_ptr = struct.calcsize("P") * 8
            if int(required_ptr) != current_ptr:
                warnings.append(
                    f"Pointer size mismatch: required {required_ptr}, current {current_ptr}"
                )

        return len(warnings) == 0, warnings

    @staticmethod
    def _normalize_os(os_name: str) -> str:
        """Normalize OS names to stable identifiers for compatibility checks."""
        os_name_lower = os_name.lower()
        if os_name_lower.startswith("win"):
            return "windows"
        if os_name_lower.startswith("darwin") or os_name_lower.startswith("mac"):
            return "macos"
        return "linux"

    @staticmethod
    def _normalize_arch(arch: str) -> str:
        """Normalize CPU architecture names to common identifiers."""
        arch_lower = arch.lower()
        if arch_lower in {"amd64", "x86_64"}:
            return "x86_64"
        if arch_lower in {"x86", "i386", "i686"}:
            return "x86"
        if arch_lower in {"arm64", "aarch64"}:
            return "arm64"
        return arch_lower
