"""Equipment -> machines rename, core half

Part 1 of the approved equipment -> machines rename (the plugin half lives in
plugins/machines/migrations/versions/0002_rename_from_equipment.py):

  1. machinetypes -> modeltypes (+ machinetypeid -> modeltypeid,
     machinetype -> modeltype, models.machinetypeid -> models.modeltypeid).
     The table types the vendor MODELS catalog, not machine instances, so
     the new name is role-accurate and frees "machinetypes" for the plugin.
  2. Data flips: assettypes 'equipment' -> 'machine' (incl pluginname and
     tablename), auditlogs.entitytype 'Equipment' -> 'Machine', settings keys
     identifier_*_equipment_enabled -> identifier_*_machine_enabled,
     search_equipment_enabled -> search_machine_enabled, and permission
     rows equipment.* -> machines.* (role links survive).
  3. alembic_version_equipment -> alembic_version_machines (if present) so
     the renamed plugin's migration chain resumes seamlessly.

Idempotent via inspector guards, following the 7c01 pattern. MySQL is the
only dialect the core chain runs against in practice.

Revision ID: 7d17_machines_rename
Revises: 7d16_directoryemployees
Create Date: 2026-07-11

"""
from alembic import op
import sqlalchemy as sa


revision = '7d17_machines_rename'
down_revision = '7d16_directoryemployees'
branch_labels = None
depends_on = None


def _fk_names(insp, table, referred_table):
    # find FK constraint names on table pointing at referred_table
    return [fk['name'] for fk in insp.get_foreign_keys(table)
            if fk.get('referred_table') == referred_table and fk.get('name')]


def upgrade():
    bind = op.get_bind()
    insp = sa.inspect(bind)
    tables = set(insp.get_table_names())

    # -- 1. machinetypes -> modeltypes ------------------------------------
    if 'machinetypes' in tables and 'modeltypes' not in tables:
        bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=0")

        # drop the models -> machinetypes FK and its index before renaming
        for name in _fk_names(insp, 'models', 'machinetypes'):
            bind.exec_driver_sql(f"ALTER TABLE models DROP FOREIGN KEY {name}")
        model_indexes = {ix['name'] for ix in insp.get_indexes('models')}
        if 'machinetypeid' in model_indexes:
            bind.exec_driver_sql("ALTER TABLE models DROP KEY machinetypeid")

        bind.exec_driver_sql("RENAME TABLE machinetypes TO modeltypes")
        bind.exec_driver_sql(
            "ALTER TABLE modeltypes "
            "CHANGE machinetypeid modeltypeid INT NOT NULL AUTO_INCREMENT, "
            "CHANGE machinetype modeltype VARCHAR(100) NOT NULL, "
            "DROP KEY machinetype, "
            "ADD UNIQUE KEY modeltype (modeltype)"
        )
        bind.exec_driver_sql(
            "ALTER TABLE models "
            "CHANGE machinetypeid modeltypeid INT NULL, "
            "ADD KEY modeltypeid (modeltypeid), "
            "ADD CONSTRAINT fk_models_modeltypeid "
            "FOREIGN KEY (modeltypeid) REFERENCES modeltypes (modeltypeid)"
        )
        bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=1")

    # -- 2. data flips ------------------------------------------------------
    if 'assettypes' in tables:
        bind.exec_driver_sql(
            "UPDATE assettypes SET assettype='machine', pluginname='machines', "
            "tablename='machines' WHERE assettype='equipment'"
        )
    if 'auditlogs' in tables:
        bind.exec_driver_sql(
            "UPDATE auditlogs SET entitytype='Machine' "
            "WHERE entitytype='Equipment'"
        )
    if 'settings' in tables:
        _rename_setting_keys(bind, old_marker='_equipment_',
                             new_marker='_machine_')
    if 'permissions' in tables:
        _rename_permissions(bind, old_prefix='equipment.',
                            new_prefix='machines.',
                            old_category='equipment', new_category='machines')

    # -- 3. plugin version table --------------------------------------------
    if ('alembic_version_equipment' in tables
            and 'alembic_version_machines' not in tables):
        bind.exec_driver_sql(
            "RENAME TABLE alembic_version_equipment TO alembic_version_machines"
        )


def _rename_setting_keys(bind, old_marker, new_marker):
    """Rename settings keys embedding the assettype value, collision-safe.

    Covers identifier_<name>_equipment_enabled and search_equipment_enabled.
    If a row with the new key already exists (fresh seed ran on new code),
    the old row is dropped instead of renamed.
    """
    # broad fetch; the marker replace below filters non-matches
    rows = bind.exec_driver_sql(
        "SELECT settingid, `key` FROM settings"
    ).fetchall()
    for settingid, key in rows:
        newkey = key.replace(old_marker, new_marker)
        if newkey == key:
            continue
        exists = bind.exec_driver_sql(
            "SELECT 1 FROM settings WHERE `key` = %s", (newkey,)
        ).fetchone()
        if exists:
            bind.exec_driver_sql(
                "DELETE FROM settings WHERE settingid = %s", (settingid,))
        else:
            bind.exec_driver_sql(
                "UPDATE settings SET `key` = %s WHERE settingid = %s",
                (newkey, settingid))


def _rename_permissions(bind, old_prefix, new_prefix, old_category,
                        new_category):
    """Rename permission rows in place so role links survive, collision-safe."""
    rows = bind.exec_driver_sql(
        "SELECT permissionid, name FROM permissions"
    ).fetchall()
    for permissionid, name in rows:
        if not name.startswith(old_prefix):
            continue
        newname = new_prefix + name[len(old_prefix):]
        exists = bind.exec_driver_sql(
            "SELECT 1 FROM permissions WHERE name = %s", (newname,)
        ).fetchone()
        if exists:
            bind.exec_driver_sql(
                "DELETE FROM permissions WHERE permissionid = %s",
                (permissionid,))
        else:
            bind.exec_driver_sql(
                "UPDATE permissions SET name = %s, category = %s "
                "WHERE permissionid = %s",
                (newname, new_category, permissionid))


def downgrade():
    bind = op.get_bind()
    insp = sa.inspect(bind)
    tables = set(insp.get_table_names())

    if ('alembic_version_machines' in tables
            and 'alembic_version_equipment' not in tables):
        bind.exec_driver_sql(
            "RENAME TABLE alembic_version_machines TO alembic_version_equipment"
        )

    if 'settings' in tables:
        _rename_setting_keys(bind, old_marker='_machine_',
                             new_marker='_equipment_')
    if 'permissions' in tables:
        _rename_permissions(bind, old_prefix='machines.',
                            new_prefix='equipment.',
                            old_category='machines', new_category='equipment')
    if 'auditlogs' in tables:
        bind.exec_driver_sql(
            "UPDATE auditlogs SET entitytype='Equipment' "
            "WHERE entitytype='Machine'"
        )
    if 'assettypes' in tables:
        bind.exec_driver_sql(
            "UPDATE assettypes SET assettype='equipment', "
            "pluginname='equipment', tablename='equipment' "
            "WHERE assettype='machine'"
        )

    if 'modeltypes' in tables and 'machinetypes' not in tables:
        bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=0")
        for name in _fk_names(insp, 'models', 'modeltypes'):
            bind.exec_driver_sql(f"ALTER TABLE models DROP FOREIGN KEY {name}")
        model_indexes = {ix['name'] for ix in insp.get_indexes('models')}
        if 'modeltypeid' in model_indexes:
            bind.exec_driver_sql("ALTER TABLE models DROP KEY modeltypeid")
        bind.exec_driver_sql("RENAME TABLE modeltypes TO machinetypes")
        bind.exec_driver_sql(
            "ALTER TABLE machinetypes "
            "CHANGE modeltypeid machinetypeid INT NOT NULL AUTO_INCREMENT, "
            "CHANGE modeltype machinetype VARCHAR(100) NOT NULL, "
            "DROP KEY modeltype, "
            "ADD UNIQUE KEY machinetype (machinetype)"
        )
        bind.exec_driver_sql(
            "ALTER TABLE models "
            "CHANGE modeltypeid machinetypeid INT NULL, "
            "ADD KEY machinetypeid (machinetypeid), "
            "ADD CONSTRAINT models_ibfk_1 "
            "FOREIGN KEY (machinetypeid) REFERENCES machinetypes (machinetypeid)"
        )
        bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=1")
