"""TEM-1625050954: hard-удалить CmfRelationExt-поля, у которых _id-колонка
осталась в main вместо ext-таблицы.

    python3 -m patch.fix_broken_relation_ext                 # dry-run
    python3 -m patch.fix_broken_relation_ext --apply         # реально чинит
    python3 -m patch.fix_broken_relation_ext --model CmfPerson --apply
"""
import argparse
import subprocess
import sys

from patch.include import *  # noqa: F401, F403


FK_DEPENDENTS = [
    ("cmf_cust_field_conf_field", "cust_field_id"),
    ("cmf_datasource_field_query", "parent_id"),
    ("cmf_ui_form_field", "cust_field_id"),
]


def _parse_args(argv):
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--apply", action="store_true",
                        help="Реально применить изменения (default — dry-run).")
    parser.add_argument("--model", default="CmfTask",
                        help="Имя CMF-модели (default CmfTask).")
    return parser.parse_args(argv)


ARGS = _parse_args(sys.argv[1:])


@app_context(commit=True)
def patch():
    print(f"[SCAN] model={ARGS.model} mode={'APPLY' if ARGS.apply else 'DRY-RUN'}")

    model_cls = getattr(models, ARGS.model)
    s = model_cls.dp.data_driver.Session()

    broken = _scan_broken_fields(s, model_cls)
    if not broken:
        print("[SCAN] no broken-state found")
        return

    _print_broken(broken)
    if not ARGS.apply:
        print("[SCAN] dry-run mode, nothing changed. Re-run with --apply to fix.")
        return

    for case in broken:
        _fix_one(s, case)

    _run_autogen()
    cmf_emit_server_event("CmfCustomClass:reload_model", {})
    print("[DONE]")


def _scan_broken_fields(s, model_cls):
    tablename = model_cls.tablename
    rows = s.execute(
        "SELECT id, name, custom_table_no, cmf_deleted "
        "FROM cmf_cust_field "
        "WHERE cmf_model_name=:m AND field_custom_type='choice_ext' AND custom_table_no > 0",
        {"m": ARGS.model},
    ).fetchall()

    broken = []
    for field_id, name, custom_table_no, cmf_deleted in rows:
        phys_col = f"{name}_id"
        ext_tablename = f"{tablename}_ext{int(custom_table_no):02d}"
        if _column_exists(s, tablename, phys_col) and not _column_exists(s, ext_tablename, phys_col):
            broken.append({
                "id": field_id,
                "name": name,
                "custom_table_no": int(custom_table_no),
                "cmf_deleted": bool(cmf_deleted),
                "tablename": tablename,
                "phys_col": phys_col,
            })
    return broken


def _column_exists(s, table_name, column_name):
    return s.execute(
        "SELECT 1 FROM information_schema.columns WHERE table_name=:t AND column_name=:c",
        {"t": table_name, "c": column_name},
    ).fetchone() is not None


def _table_exists(s, table_name):
    return s.execute(
        "SELECT 1 FROM information_schema.tables WHERE table_name=:t",
        {"t": table_name},
    ).fetchone() is not None


def _print_broken(broken):
    print(f"[SCAN] found {len(broken)} broken field(s):")
    for case in broken:
        soft = " (soft-deleted)" if case["cmf_deleted"] else ""
        print(f"  - id={case['id']} name={case['name']}{soft} ct={case['custom_table_no']}")


def _fix_one(s, case):
    print(f"[APPLY] field {case['id']} (name={case['name']})")
    s.execute(f'ALTER TABLE "{case["tablename"]}" DROP COLUMN IF EXISTS "{case["phys_col"]}"')
    for fk_table, fk_col in FK_DEPENDENTS:
        if not _table_exists(s, fk_table):
            continue
        s.execute(f'DELETE FROM "{fk_table}" WHERE "{fk_col}"=:fid', {"fid": case["id"]})
    # Hard, а не soft: reload_model читает PostgreSQL и вернул бы soft-deleted запись назад.
    s.execute("DELETE FROM cmf_cust_field WHERE id=:fid", {"fid": case["id"]})


def _run_autogen():
    subprocess.run(["python3", "manage.py", "autogen"], cwd="/opt/eva-app", check=True)


if __name__ == "__main__":
    patch()
