#!/usr/bin/env python3
"""golden_diff.py — сравнить вывод функций ДО и ПОСЛЕ упрощения, байт-в-байт.

Грузит две версии одного Python-модуля (старую из git HEAD, новую из рабочего дерева),
прогоняет заданные функции на общих входах и сверяет результат. Если хоть один символ
разошёлся — упрощение изменило поведение, откатывай.

Модуль загружается ИЗОЛИРОВАННО: exec исходника с застабленными импортами, поэтому
тяжёлые зависимости (sqlalchemy, aiogram и т.п.) ставить не нужно — их подсовывают стабами.

Usage:
    golden_diff.py <repo_dir> <module_path_in_repo> <cases.py>

где cases.py определяет:
    STUBS = {"pkg.db.models": {"Repair": object, "RepairStatus": <enum>}}  # опц.
    CALLS = [("fmt_history", (car, repairs)), ...]   # (имя_функции, args_tuple)
    # объекты для args строятся прямо в cases.py

Пример cases.py — в examples/ рядом со скиллом.
"""
import sys, types, subprocess, importlib.util, pathlib

def _install_stubs(stubs):
    for modname, attrs in (stubs or {}).items():
        parts = modname.split(".")
        for i in range(1, len(parts) + 1):
            name = ".".join(parts[:i])
            if name not in sys.modules:
                m = types.ModuleType(name)
                if i < len(parts):
                    m.__path__ = []
                sys.modules[name] = m
        for k, v in attrs.items():
            setattr(sys.modules[modname], k, v)

def _build(src, name):
    m = types.ModuleType(name)
    exec(compile(src, name, "exec"), m.__dict__)
    return m

def main():
    if len(sys.argv) != 4:
        print(__doc__); sys.exit(2)
    repo, modpath, cases_path = sys.argv[1], sys.argv[2], sys.argv[3]
    repo = pathlib.Path(repo).resolve()

    # загрузить cases.py (он определяет STUBS/CALLS и строит входные объекты)
    spec = importlib.util.spec_from_file_location("cases", cases_path)
    cases = importlib.util.module_from_spec(spec); spec.loader.exec_module(cases)

    _install_stubs(getattr(cases, "STUBS", {}))

    new_src = (repo / modpath).read_text()
    old_src = subprocess.check_output(["git", "-C", str(repo), "show", f"HEAD:{modpath}"]).decode()
    NEW = _build(new_src, "new_mod")
    OLD = _build(old_src, "old_mod")

    all_ok = True
    for fn, args in cases.CALLS:
        a = getattr(OLD, fn)(*args)
        b = getattr(NEW, fn)(*args)
        ok = a == b
        all_ok &= ok
        print(f"[{fn:24}] identical={ok}")
        if not ok:
            print("--- OLD ---\n" + repr(a) + "\n--- NEW ---\n" + repr(b))
    print("\nALL IDENTICAL:", all_ok)
    sys.exit(0 if all_ok else 1)

if __name__ == "__main__":
    main()
