"""DEX Parser - string pool, class names, BuildConfig static VERSION_NAME."""
import struct
import re
from typing import Dict, List, Tuple
import os
from apk_analyzer.models import DEXData

_VERSION_RE = re.compile(r"(BuildConfig|SdkVersion|SDKVersion|VersionInfo|SDKConst)", re.I)
_SKIP = {"1.0", "0.0", "", "1.0.0", "null", "undefined"}


def _uleb128(data: bytes, pos: int) -> Tuple[int, int]:
    result, shift = 0, 0
    while True:
        b = data[pos]; pos += 1
        result |= (b & 0x7F) << shift
        if not (b & 0x80): break
        shift += 7
    return result, pos


def _read_strings(data: bytes) -> List[str]:
    count = struct.unpack_from("<I", data, 56)[0]
    off = struct.unpack_from("<I", data, 60)[0]
    strings = []
    for i in range(min(count, 800_000)):
        soff = struct.unpack_from("<I", data, off + i * 4)[0]
        length, p = 0, soff
        shift = 0
        while True:
            b = data[p]; p += 1
            length |= (b & 0x7F) << shift
            if not (b & 0x80): break
            shift += 7
        try:
            strings.append(data[p: p + length].decode("utf-8"))
        except Exception:
            strings.append("")
    return strings


def _read_types(data: bytes, strings: List[str]) -> List[str]:
    count = struct.unpack_from("<I", data, 64)[0]
    off = struct.unpack_from("<I", data, 68)[0]
    return [strings[struct.unpack_from("<I", data, off + i*4)[0]] for i in range(count)]


def _desc_to_cls(desc: str) -> str:
    if desc.startswith("L") and desc.endswith(";"):
        return desc[1:-1].replace("/", ".")
    return desc


def _extract_versions(data: bytes, strings: List[str], types: List[str]) -> Dict[str, str]:
    field_count = struct.unpack_from("<I", data, 80)[0]
    field_off   = struct.unpack_from("<I", data, 84)[0]
    class_count = struct.unpack_from("<I", data, 96)[0]
    class_off   = struct.unpack_from("<I", data, 100)[0]

    fields = []
    for i in range(field_count):
        o = field_off + i * 8
        ci, ti, ni = struct.unpack_from("<HHI", data, o)
        fields.append((ci, ti, ni))

    results = {}
    for i in range(class_count):
        o = class_off + i * 32
        cidx     = struct.unpack_from("<I", data, o)[0]
        cd_off   = struct.unpack_from("<I", data, o + 24)[0]
        sv_off   = struct.unpack_from("<I", data, o + 28)[0]
        if cidx >= len(types): continue
        desc = types[cidx]
        if not _VERSION_RE.search(desc): continue
        if cd_off == 0 or sv_off == 0: continue
        try:
            pos = cd_off
            sf_size, pos = _uleb128(data, pos)
            _, pos = _uleb128(data, pos)
            _, pos = _uleb128(data, pos)
            _, pos = _uleb128(data, pos)
            fids, fidx = [], 0
            for _ in range(sf_size):
                diff, pos = _uleb128(data, pos)
                fidx += diff
                _, pos = _uleb128(data, pos)
                fids.append(fidx)
            sval_pos = sv_off
            arr_size, sval_pos = _uleb128(data, sval_pos)
            svals = []
            for _ in range(arr_size):
                if sval_pos >= len(data): break
                b0 = data[sval_pos]; sval_pos += 1
                vtype = b0 & 0x1F; varg = (b0 >> 5) & 0x7; sz = varg + 1
                if vtype == 0x17:
                    sidx = int.from_bytes(data[sval_pos: sval_pos + sz], "little")
                    sval_pos += sz
                    svals.append(strings[sidx] if sidx < len(strings) else "")
                else:
                    sval_pos += sz; svals.append(None)
            cname = _desc_to_cls(desc)
            for k, fid in enumerate(fids):
                if k >= len(svals): break
                if fid >= len(fields): continue
                _, _, ni = fields[fid]
                fname = strings[ni] if ni < len(strings) else ""
                val = svals[k]
                if fname in ("VERSION_NAME", "VERSION", "SDK_VERSION") and isinstance(val, str) and val not in _SKIP:
                    results[cname] = val
        except Exception:
            continue
    return results


class DEXParser:
    def __init__(self, dex_list: List[bytes]):
        self.dex_list = dex_list

    def parse(self) -> DEXData:
        result = DEXData()
        all_strings, all_classes, all_bc = [], [], {}
        for data in self.dex_list:
            if len(data) < 112: continue
            try:
                strings = _read_strings(data)
                types = _read_types(data, strings)
                all_strings.extend(strings)
                for t in types:
                    if t.startswith("L") and t.endswith(";"):
                        cn = _desc_to_cls(t)
                        if cn: all_classes.append(cn)
                all_bc.update(_extract_versions(data, strings, types))
            except Exception:
                continue
        result.string_pool = all_strings
        result.class_names = list(dict.fromkeys(all_classes))
        result.buildconfig_versions = all_bc
        result.build_index()
        return result
