from __future__ import annotations
import base64, os, random, subprocess, sys, sysconfig, urllib.request
from pathlib import Path
from . import wb_aes

_HERE = Path(__file__).parent
_WB_HDR = _HERE / "_wb_key.h"
_C_URL = "https://raw.githubusercontent.com/Therealtobu/Pyrph-Api/main/pyrph/native/runtime.c"

def _get_runtime_c() -> Path:
    local = _HERE / "runtime.c"
    if not local.exists():
        try:
            urllib.request.urlretrieve(_C_URL, local)
        except Exception as e:
            raise RuntimeError(f"Cannot download runtime.c: {e}")
    return local

def _random_key() -> bytes:
    return bytes(random.randint(1,254) for _ in range(16))

def _write_wb_header(aes_key: bytes, rng: random.Random):
    kd,kp,km = wb_aes.generate(aes_key, rng)
    assert wb_aes.verify(aes_key, kd, kp, km)
    _WB_HDR.write_text(wb_aes.to_c_header(kd, kp, km))

def _has(cmd):
    try: return subprocess.run(cmd, capture_output=True, timeout=5).returncode==0
    except: return False

def _has_clang(): return _has(["clang","--version"])
def _has_gcc():   return _has(["gcc","--version"])

def _mutate_elf(data: bytes, rng: random.Random) -> bytes:
    import struct
    if len(data)<64 or data[:4]!=b"\x7fELF" or data[4]!=2: return data
    mutated = bytearray(data)
    marker = b"GNU\x00\x14\x00\x00\x00\x03\x00\x00\x00"
    idx = data.find(marker)
    if idx!=-1:
        off = idx+len(marker)
        for i in range(min(20,len(data)-off)):
            mutated[off+i] = rng.randint(0,255)
    return bytes(mutated)

def build(aes_key: bytes=None, seed: int=None) -> tuple:
    if aes_key is None: aes_key = _random_key()
    rng = random.Random(seed)
    _write_wb_header(aes_key, rng)
    src = _get_runtime_c()
    pyinc = sysconfig.get_path("include")
    pylib = sysconfig.get_config_var("LIBDIR") or ""
    ver   = f"{sys.version_info.major}.{sys.version_info.minor}"
    suffix = sysconfig.get_config_var("EXT_SUFFIX") or ".so"
    out = _HERE / f"_pyrph_build{suffix}"
    compiler = "clang" if _has_clang() else "gcc"
    flags = [compiler,"-shared","-fPIC","-O2","-s",
             "-fvisibility=hidden","-fstack-protector-strong",
             "-D_FORTIFY_SOURCE=2","-fno-asynchronous-unwind-tables",
             f"-include{_WB_HDR}","-DPYRPH_V2=1",
             "-o",str(out),str(src),f"-I{pyinc}",
             "-Wno-unused-result","-Wno-pointer-sign","-Wl,--strip-all"]
    libpython = os.path.join(pylib,f"libpython{ver}.so")
    if os.path.exists(libpython): flags+=[f"-L{pylib}",f"-lpython{ver}"]
    result = subprocess.run(flags, capture_output=True, text=True)
    if result.returncode!=0:
        flags[0] = "gcc" if compiler=="clang" else "clang"
        result = subprocess.run(flags, capture_output=True, text=True)
        if result.returncode!=0:
            raise RuntimeError(f"Native compile failed:\n{result.stderr}")
    so_bytes = out.read_bytes()
    so_bytes = _mutate_elf(so_bytes, rng)
    try: out.unlink()
    except: pass
    try: _WB_HDR.unlink()
    except: pass
    return so_bytes, aes_key

def build_b64(seed: int=None) -> tuple:
    so, key = build(seed=seed)
    return base64.b64encode(so).decode("ascii"), key

def is_available() -> bool:
    if not (_has_gcc() or _has_clang()): return False
    pyinc = sysconfig.get_path("include")
    return (Path(pyinc)/"Python.h").exists()
