"""APK Parser - extracts DEX, manifest, native libs, resources.arsc from APK (ZIP)."""
import hashlib
import zipfile
from pathlib import Path
from apk_analyzer.models import APKInfo

_REQUIRED_32 = "armeabi-v7a"
_REQUIRED_64 = "arm64-v8a"
_ABI_ALIASES = {
    "arm64_v8a": "arm64-v8a",
    "armeabi_v7a": "armeabi-v7a",
    "x86-64": "x86_64",
    "aarch64": "arm64-v8a",
}


def _normalize_zip_name(name: str) -> str:
    normalized = name.replace("\\", "/")
    while normalized.startswith("./"):
        normalized = normalized[2:]
    return normalized.lstrip("/")


def _normalize_abi_name(abi: str) -> str:
    normalized = abi.strip().lower()
    return _ABI_ALIASES.get(normalized, normalized)


def _classify_abi_support(lib_abis: dict[str, set[str]]) -> str:
    if not lib_abis:
        return "Other"

    all_have_32 = all(_REQUIRED_32 in abis for abis in lib_abis.values())
    all_have_64 = all(_REQUIRED_64 in abis for abis in lib_abis.values())

    if all_have_32 and all_have_64:
        return "32/64-bit"
    if all_have_64:
        return "64-bit"
    if all_have_32:
        return "32-bit"
    return "Other"


class APKParser:
    def __init__(self, apk_path: Path):
        self.apk_path = apk_path

    def parse(self) -> APKInfo:
        info = APKInfo()
        info.file_size_bytes = self.apk_path.stat().st_size
        sha = hashlib.sha256()
        with open(self.apk_path, "rb") as f:
            for chunk in iter(lambda: f.read(65536), b""):
                sha.update(chunk)
        info.signature_sha256 = sha.hexdigest()

        with zipfile.ZipFile(self.apk_path, "r") as zf:
            names = zf.namelist()
            dex_names = sorted(n for n in names if n.endswith(".dex"))
            info.dex_count = len(dex_names)
            info.dex_files = [zf.read(dn) for dn in dex_names]

            if "AndroidManifest.xml" in names:
                info.manifest_data = zf.read("AndroidManifest.xml")

            if "resources.arsc" in names:
                info.resources_arsc = zf.read("resources.arsc")

            seen_libs = set()
            lib_abis: dict[str, set[str]] = {}
            for raw_name in names:
                n = _normalize_zip_name(raw_name)
                if not n.startswith("lib/"):
                    continue

                parts = n.split("/")
                if len(parts) < 3 or not parts[1] or not parts[-1]:
                    continue

                if not n.endswith(".so"):
                    continue

                abi_name = _normalize_abi_name(parts[1])
                lib_name = parts[-1]
                if lib_name not in seen_libs:
                    seen_libs.add(lib_name)
                    info.native_libs.append(lib_name)
                    lib_abis[lib_name] = set()
                lib_abis[lib_name].add(abi_name)

            info.native_lib_arches = {k: sorted(v) for k, v in lib_abis.items()}
            info.native_abis = sorted({abi for abis in lib_abis.values() for abi in abis})
            info.abi_support = _classify_abi_support(lib_abis)

            for n in names:
                if n.startswith("META-INF/") and n.endswith(".version"):
                    key = n[len("META-INF/"):-len(".version")]
                    try:
                        val = zf.read(n).decode("utf-8", errors="ignore").strip()
                        if val:
                            info.metainf_versions[key] = val
                    except Exception:
                        pass
        return info
