# (c) cavaliba.com - app_home - cavaliba_3to4.py
# One-time migration from v3 IAM models (SireneUser/SireneGroup) to v4 Schema/Instance design.

import logging
import time

from app_data.loader import load_broker
from app_data.models import DataClass, DataEAV, DataSchema
from app_data.permissions import permission_all_keynames
from app_home.cavaliba import reload_builtin
from app_home.models import DashboardApp
from app_user.models import SireneGroup, SireneUser

logger = logging.getLogger(__name__)


def migrate_3to4():
    logger.info("[3to4] Starting migration v3 → v4 ...")
    _reload_init_schemas()
    _migrate_roles()
    _migrate_groups()
    _migrate_users()
    _convert_schema_formats()
    _set_user_schema_bigset()
    _update_eav_formats()
    _update_dashboard_urls()
    logger.info("[3to4] Migration complete.")


def _reload_init_schemas():
    logger.info("[3to4] Reloading builtin init schemas...")
    reload_builtin("permissions", "00_permission.yml", ["*"], "create")
    reload_builtin("user schema", "010_user.yml", ["*"], "create")
    reload_builtin("role schema", "012_role.yml", ["*"], "create")
    reload_builtin("group schema", "014_group.yml", ["*"], "create")
    logger.info("[3to4]   done.")


def _migrate_users():
    logger.info("[3to4] Migrating users...")
    aaa = {"perms": permission_all_keynames()}
    users = list(SireneUser.objects.all())
    total = len(users)
    count_ok = 0
    count_err = 0
    last_print = time.time()

    for i, su in enumerate(users):
        try:
            last_login = su.last_login.strftime("%Y-%m-%d %H:%M:%S")
        except Exception:
            last_login = ""

        datadict = {
            "classname": "user",
            "keyname": su.login,
            "displayname": su.displayname
            or f"{su.firstname or ''} {su.lastname or ''}".strip()
            or su.login,
            "_action": "init",
            "is_enabled": su.is_enabled,
            "last_login": last_login,
            "firstname": su.firstname or "",
            "lastname": su.lastname or "",
            "description": su.description or "",
            "email": su.email or "",
            "mobile": su.mobile or "",
            "external_id": su.external_id or "",
            "want_notifications": su.want_notifications,
            "want_24": su.want_24,
            "want_sms": su.want_sms,
            "want_email": su.want_email,
            "secondary_email": su.secondary_email or "",
            "secondary_mobile": su.secondary_mobile or "",
        }
        try:
            load_broker([datadict], aaa=aaa)
            su.delete()
            count_ok += 1
        except Exception as e:
            logger.error(f"[3to4] user {su.login}: {e}")
            count_err += 1

        now = time.time()
        if now - last_print >= 1.0:
            pct = int((i + 1) * 100 / total) if total else 100
            logger.info(f"[3to4]   users {i + 1}/{total} ({pct}%) ...")
            last_print = now

    logger.info(f"[3to4]   users done: ok={count_ok} err={count_err} total={total}")


def _migrate_groups():
    logger.info("[3to4] Migrating groups...")
    aaa = {"perms": permission_all_keynames()}
    groups = list(SireneGroup.objects.filter(is_role=False))
    total = len(groups)
    count_ok = 0
    count_err = 0
    last_print = time.time()

    for i, sg in enumerate(groups):
        if sg.permissions.exists():
            perms = list(sg.permissions.values_list("keyname", flat=True))
            logger.info(
                f"[3to4]   WARN group '{sg.keyname}' had permissions {perms} — dropped (no permissions field in v4 Group)"
            )

        datadict = {
            "classname": "group",
            "keyname": sg.keyname,
            "displayname": sg.displayname or "",
            "_action": "init",
            "is_enabled": sg.is_enabled,
            "description": sg.description or "",
            "is_builtin": sg.is_builtin,
            "users": [su.login for su in sg.users.all()],
            "subgroups": [sg2.keyname for sg2 in sg.subgroups.all()],
            "autogroup": sg.autogroup or "",
            "autogroup_users": [su.login for su in sg.autogroup_users.all()],
        }
        try:
            load_broker([datadict], aaa=aaa)
            sg.delete()
            count_ok += 1
        except Exception as e:
            logger.error(f"[3to4] group {sg.keyname}: {e}")
            count_err += 1

        now = time.time()
        if now - last_print >= 1.0:
            pct = int((i + 1) * 100 / total) if total else 100
            logger.info(f"[3to4]   groups {i + 1}/{total} ({pct}%) ...")
            last_print = now

    logger.info(f"[3to4]   groups done: ok={count_ok} err={count_err} total={total}")


def _migrate_roles():
    logger.info("[3to4] Migrating roles...")
    aaa = {"perms": permission_all_keynames()}
    roles = list(SireneGroup.objects.filter(is_role=True))
    total = len(roles)
    count_ok = 0
    count_err = 0
    last_print = time.time()

    for i, sg in enumerate(roles):
        datadict = {
            "classname": "role",
            "keyname": sg.keyname,
            "displayname": sg.displayname or "",
            "_action": "init",
            "is_enabled": sg.is_enabled,
            "description": sg.description or "",
            "is_builtin": sg.is_builtin,
            "users": [su.login for su in sg.users.all()],
            "groups": [sg.keyname for sg in sg.subgroups.all()],
            "permissions": [sp.keyname for sp in sg.permissions.all()],
        }
        try:
            load_broker([datadict], aaa=aaa)
            sg.delete()
            count_ok += 1
        except Exception as e:
            logger.error(f"[3to4] role {sg.keyname}: {e}")
            count_err += 1

        now = time.time()
        if now - last_print >= 1.0:
            pct = int((i + 1) * 100 / total) if total else 100
            logger.info(f"[3to4]   roles {i + 1}/{total} ({pct}%) ...")
            last_print = now

    logger.info(f"[3to4]   roles done: ok={count_ok} err={count_err} total={total}")


def _convert_schema_formats():
    logger.info("[3to4] Converting DataSchema field formats...")
    for old in ("user", "group", "role"):
        count = DataSchema.objects.filter(dataformat=old).update(
            dataformat="schema", dataformat_ext=old
        )
        logger.info(f"[3to4]   '{old}' → schema/{old}: {count} fields")


def _set_user_schema_bigset():
    logger.info("[3to4] Setting is_bigset on user schema...")
    count = DataClass.objects.filter(keyname="user").update(is_bigset=True)
    logger.info(f"[3to4]   updated {count} DataClass record(s)")


_EAV_BATCH = 10_000


def _eav_batch_update(filter_kwargs, update_kwargs, label):
    total = 0
    last_print = time.time()
    while True:
        ids = list(
            DataEAV.objects.filter(**filter_kwargs).values_list("id", flat=True)[:_EAV_BATCH]
        )
        if not ids:
            break
        DataEAV.objects.filter(id__in=ids).update(**update_kwargs)
        total += len(ids)
        now = time.time()
        if now - last_print >= 1.0:
            logger.info(f"[3to4]   {label}: {total} ...")
            last_print = now
    logger.info(f"[3to4]   {label}: {total} records")


def _eav_batch_delete(filter_kwargs, label):
    total = 0
    last_print = time.time()
    while True:
        ids = list(
            DataEAV.objects.filter(**filter_kwargs).values_list("id", flat=True)[:_EAV_BATCH]
        )
        if not ids:
            break
        DataEAV.objects.filter(id__in=ids).delete()
        total += len(ids)
        now = time.time()
        if now - last_print >= 1.0:
            logger.info(f"[3to4]   {label}: {total} ...")
            last_print = now
    logger.info(f"[3to4]   {label}: {total} deleted")


def _update_eav_formats():
    logger.info("[3to4] Updating DataEAV format column...")
    for old, new in (("user", "schema:user"), ("group", "schema:group"), ("role", "schema:role")):
        _eav_batch_update({"format": old}, {"format": new}, f"'{old}' → '{new}'")
    _eav_batch_delete(
        {"classname__in": ["_user", "_group", "_role"]}, "stale classname _user/_group/_role"
    )
    _eav_batch_delete(
        {"format__in": ["schema:_user", "schema:_group", "schema:_role"]}, "stale format schema:_*"
    )


def _update_dashboard_urls():
    logger.info("[3to4] Updating dashboard URLs...")
    url_fixes = {
        "/user/private/users/": "/data/private/c/user/list/",
        "/data/private/c/_user/list/": "/data/private/c/user/list/",
        "/user/private/groups/": "/data/private/c/group/list/",
        "/data/private/c/_group/list/": "/data/private/c/group/list/",
        "/user/private/roles/": "/data/private/c/role/list/",
        "/data/private/c/_role/list/": "/data/private/c/role/list/",
        "/user/private/permissions/": "/data/private/permissions/",
    }
    for old_url, new_url in url_fixes.items():
        count = DashboardApp.objects.filter(url=old_url).update(url=new_url)
        logger.info(f"[3to4]   {old_url} → {new_url}: {count} updated")
    logger.info("[3to4]   done.")
