"""
AndroidManifest.xml parser.
Uses pyaxmlparser if available, falls back to basic binary string extraction.
"""
import os
from apk_analyzer.models import ManifestData
from apk_analyzer.analyzer.pyaxml_logging import suppress_pyaxmlparser_warnings

_ANDROID_NS = "{http://schemas.android.com/apk/res/android}"


def _try_pyaxmlparser(data: bytes) -> ManifestData:
    with suppress_pyaxmlparser_warnings():
        import pyaxmlparser
        apk = pyaxmlparser.APK.__new__(pyaxmlparser.APK)
        from pyaxmlparser.axmlprinter import AXMLPrinter
        printer = AXMLPrinter(data)
    xml_str = printer.get_xml_obj()
    if xml_str is None:
        raise ValueError("AXMLPrinter returned None")
    root = xml_str
    md = ManifestData()
    md.package_name = root.get("package", "")
    md.version_name = root.get(f"{_ANDROID_NS}versionName", "")
    vc = root.get(f"{_ANDROID_NS}versionCode", "0")
    try: md.version_code = int(vc)
    except Exception: pass
    uses_sdk = root.find("uses-sdk")
    if uses_sdk is not None:
        try: md.min_sdk = int(uses_sdk.get(f"{_ANDROID_NS}minSdkVersion", "0"))
        except: pass
        try: md.target_sdk = int(uses_sdk.get(f"{_ANDROID_NS}targetSdkVersion", "0"))
        except: pass
    for perm in root.findall("uses-permission"):
        name = perm.get(f"{_ANDROID_NS}name", "")
        if name: md.permissions.append(name)
    app = root.find("application")
    if app is not None:
        # Capture android:label — may be plain string or resource ref like @0x7F0C0012
        label = app.get(f"{_ANDROID_NS}label", "")
        if label:
            md.app_label_ref = label
            # If it's a plain string (not a resource reference), use it directly
            if not label.startswith("@"):
                md.app_name = label
        for comp, lst in [("activity", md.activities), ("service", md.services),
                          ("receiver", md.receivers), ("provider", md.providers)]:
            for el in app.findall(comp):
                name = el.get(f"{_ANDROID_NS}name", "")
                if name:
                    lst.append(name)
                for intent_filter in el.findall("intent-filter"):
                    for action in intent_filter.findall("action"):
                        action_name = (action.get(f"{_ANDROID_NS}name", "") or "").strip()
                        if action_name:
                            md.intent_actions.append(action_name)
        for meta in app.findall("meta-data"):
            key = (meta.get(f"{_ANDROID_NS}name", "") or "").strip()
            if not key:
                continue
            value = (
                meta.get(f"{_ANDROID_NS}value")
                or meta.get(f"{_ANDROID_NS}resource")
                or ""
            ).strip()
            if value:
                md.meta_data[key] = value
    return md


def _fallback_parse(data: bytes) -> ManifestData:
    """Best-effort: extract strings from binary AXML."""
    md = ManifestData()
    i, text_buf = 0, []
    while i < len(data) - 1:
        if data[i] == 0 and data[i+1] != 0:
            i += 1; continue
        if 0x20 <= data[i] <= 0x7E:
            start = i
            while i < len(data) and 0x20 <= data[i] <= 0x7E:
                i += 1
            s = data[start:i].decode("ascii", errors="ignore")
            if len(s) > 4:
                text_buf.append(s)
        else:
            i += 1
    for s in text_buf:
        if s.startswith("android.permission."):
            md.permissions.append(s)
        elif "." in s and len(s) > 8 and not s.startswith("http") and "/" not in s:
            if md.package_name == "" and s.count(".") >= 2:
                md.package_name = s
    return md


class ManifestParser:
    def __init__(self, manifest_data: bytes):
        self.data = manifest_data

    def parse(self) -> ManifestData:
        if not self.data:
            return ManifestData()
        try:
            with suppress_pyaxmlparser_warnings():
                return _try_pyaxmlparser(self.data)
        except Exception:
            return _fallback_parse(self.data)
