import re
import sys
import json
import base64
from pathlib import Path

FILE = "rphost.exe_16460.dmp"
OUT = Path("decoded_1c_dump_full")

MIN_LEN = 20

# ---------------------------
# utils
# ---------------------------

def ensure_utf8():
    try:
        sys.stdout.reconfigure(encoding="utf-8")
    except:
        pass

def safe_text(s):
    return s.replace("\x00", "")

# ---------------------------
# FIX ENCODINGS (главное)
# ---------------------------

def try_variants(text):
    variants = [text]

    try:
        variants.append(text.encode("cp1251").decode("utf-8"))
    except:
        pass

    try:
        variants.append(text.encode("latin1").decode("utf-8"))
    except:
        pass

    try:
        raw = text.encode("latin1", errors="ignore")
        variants.append(raw.decode("utf-16le", errors="ignore"))
    except:
        pass

    return variants

def russian_score(s):
    if not s:
        return 0
    letters = sum(c.isalpha() for c in s)
    rus = sum("а" <= c.lower() <= "я" or c.lower() == "ё" for c in s)
    return rus / max(letters, 1)

KEYWORDS = [
    "Процедура", "Функция", "КонецПроцедуры", "КонецФункции",
    "Если", "Тогда", "Иначе", "Цикл", "Пока",
    "Запрос", "ВЫБРАТЬ", "Соответствие",
    "ТаблицаЗначений", "Регистр", "Документ",
    "Справочник", "ОбщийМодуль", "ПараметрыСеанса"
]

def score_text(s):
    score = len(s) // 50

    for k in KEYWORDS:
        if k in s:
            score += 20

    score += int(russian_score(s) * 50)

    if "<" in s and ">" in s:
        score += 10

    return score

def normalize_text(text):
    text = safe_text(text)
    best = text
    best_score = score_text(text)

    for v in try_variants(text):
        sc = score_text(v)
        if sc > best_score:
            best = v
            best_score = sc

    return best

# ---------------------------
# extraction
# ---------------------------

RAW_RE = re.compile(rb'[\x20-\x7E\x80-\xFF]{20,}')
UTF16_RE = re.compile(rb'(?:[\x20-\x7E\x80-\xFF]\x00){10,}')

def extract(data):
    results = []

    # RAW
    for m in RAW_RE.finditer(data):
        raw = m.group()
        offset = m.start()

        for enc in ["utf-8", "cp1251", "latin1"]:
            try:
                txt = raw.decode(enc, errors="ignore")
                txt = normalize_text(txt)
                if len(txt) > MIN_LEN:
                    results.append((offset, txt))
            except:
                pass

    # UTF16
    for m in UTF16_RE.finditer(data):
        raw = m.group()
        offset = m.start()

        try:
            txt = raw.decode("utf-16le", errors="ignore")
            txt = normalize_text(txt)
            if len(txt) > MIN_LEN:
                results.append((offset, txt))
        except:
            pass

    return results

# ---------------------------
# procedures
# ---------------------------

PROC_START = re.compile(r"(Процедура|Функция)\s+[\wА-Яа-я_]+", re.I)
PROC_END = re.compile(r"(КонецПроцедуры|КонецФункции)", re.I)

def build_procedures(chunks):
    chunks = sorted(chunks, key=lambda x: x[0])

    procs = []
    current = None

    for offset, text in chunks:

        if PROC_START.search(text):
            if current:
                procs.append(current)
            current = {"offset": offset, "text": text}
            continue

        if current:
            current["text"] += "\n" + text

            if PROC_END.search(text):
                procs.append(current)
                current = None

    return procs

# ---------------------------
# base64
# ---------------------------

BASE64_RE = re.compile(r'[A-Za-z0-9+/]{80,}={0,2}')

def decode_base64(text):
    results = []

    for m in BASE64_RE.findall(text):
        try:
            data = base64.b64decode(m)
            try:
                decoded = data.decode("utf-8", errors="ignore")
                results.append(decoded)
            except:
                pass
        except:
            pass

    return results

# ---------------------------
# main
# ---------------------------

def main():
    ensure_utf8()

    data = Path(FILE).read_bytes()

    print("Extracting...")
    chunks = extract(data)
    print("Total chunks:", len(chunks))

    OUT.mkdir(exist_ok=True)

    # manifest (ВСЁ)
    with open(OUT / "00_manifest.jsonl", "w", encoding="utf-8") as f:
        for off, txt in chunks:
            f.write(json.dumps({
                "offset": off,
                "text": txt[:5000]
            }, ensure_ascii=False) + "\n")

    # сортировка
    ranked = sorted(chunks, key=lambda x: score_text(x[1]), reverse=True)

    # summary
    with open(OUT / "01_summary.txt", "w", encoding="utf-8") as f:
        for i, (off, txt) in enumerate(ranked[:200], 1):
            f.write(f"[{i}] off=0x{off:X} score={score_text(txt)}\n")
            f.write(txt[:500].replace("\n", " ") + "\n\n")

    # все интересные
    with open(OUT / "02_all.txt", "w", encoding="utf-8") as f:
        for off, txt in ranked[:3000]:
            f.write("="*80 + "\n")
            f.write(f"off=0x{off:X}\n")
            f.write(txt + "\n\n")

    # процедуры
    procs = build_procedures(ranked)

    with open(OUT / "03_procedures.txt", "w", encoding="utf-8") as f:
        for i, p in enumerate(procs, 1):
            f.write("="*100 + "\n")
            f.write(f"[PROC {i}] off=0x{p['offset']:X}\n")
            f.write(p["text"] + "\n\n")

    # base64
    with open(OUT / "04_base64.txt", "w", encoding="utf-8") as f:
        for _, txt in ranked[:500]:
            decoded = decode_base64(txt)
            for d in decoded:
                f.write("="*60 + "\n")
                f.write(d + "\n\n")

    print("Done.")
    print("Saved to:", OUT.resolve())

if __name__ == "__main__":
    main()