"""Rename equipment tables to machines

Plugin half of the approved equipment -> machines rename (the core half is
migrations/versions/7d17_machines_rename.py):

  equipment      -> machines      (equipmentid -> machineid,
                                   equipmenttypeid -> machinetypeid)
  equipmenttypes -> machinetypes  (equipmenttypeid -> machinetypeid,
                                   equipmenttype -> machinetype)

Runs on BOTH upgraded installs and fresh installs: the core baseline creates
these tables under their old names (frozen DDL), so this revision always does
the physical rename. Idempotent: skips when the tables already carry the new
names (e.g. test DBs built by db.create_all() from current models). Refuses
to run while the core machinetypes table still exists - `flask db upgrade`
(which renames it to modeltypes) must land first.

Revision ID: machines0002rename
Revises: equipment0001anchor
"""
from alembic import op
import sqlalchemy as sa


revision = 'machines0002rename'
down_revision = 'equipment0001anchor'
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())

    if 'equipment' not in tables and 'equipmenttypes' not in tables:
        # already renamed, or fresh schema built straight from current models
        return

    if 'machinetypes' in tables:
        # core chain still owns the machinetypes name (not yet renamed to
        # modeltypes) - renaming equipmenttypes onto it would collide
        raise RuntimeError(
            "Core migration 7d17_machines_rename has not run: the legacy "
            "machinetypes table still exists. Run `flask db upgrade` before "
            "`flask plugin upgrade-all`."
        )

    if bind.dialect.name == 'mysql':
        _upgrade_mysql(bind, insp)
    else:
        _upgrade_generic(insp)


def _upgrade_mysql(bind, insp):
    bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=0")

    # snapshot index names before the renames invalidate the table name
    machine_indexes = {ix['name'] for ix in insp.get_indexes('equipment')}

    # drop the equipment -> equipmenttypes FK before renaming the parent cols
    for name in _fk_names(insp, 'equipment', 'equipmenttypes'):
        bind.exec_driver_sql(f"ALTER TABLE equipment DROP FOREIGN KEY {name}")

    bind.exec_driver_sql("RENAME TABLE equipmenttypes TO machinetypes")
    bind.exec_driver_sql("RENAME TABLE equipment TO machines")

    bind.exec_driver_sql(
        "ALTER TABLE machinetypes "
        "CHANGE equipmenttypeid machinetypeid INT NOT NULL AUTO_INCREMENT, "
        "CHANGE equipmenttype machinetype VARCHAR(100) NOT NULL, "
        "DROP KEY equipmenttype, "
        "ADD UNIQUE KEY machinetype (machinetype)"
    )

    parts = [
        "CHANGE equipmentid machineid INT NOT NULL AUTO_INCREMENT",
        "CHANGE equipmenttypeid machinetypeid INT NULL",
    ]
    # rename the model-declared index names to match the Machine model
    if 'idx_equipment_type' in machine_indexes:
        parts += ["DROP KEY idx_equipment_type",
                  "ADD KEY idx_machine_type (machinetypeid)"]
    if 'idx_equipment_vendor' in machine_indexes:
        parts += ["DROP KEY idx_equipment_vendor",
                  "ADD KEY idx_machine_vendor (vendorid)"]
    if 'ix_equipment_assetid' in machine_indexes:
        parts += ["DROP KEY ix_equipment_assetid",
                  "ADD UNIQUE KEY ix_machines_assetid (assetid)"]
    parts += [
        "ADD CONSTRAINT fk_machines_machinetypeid "
        "FOREIGN KEY (machinetypeid) REFERENCES machinetypes (machinetypeid)"
    ]
    bind.exec_driver_sql("ALTER TABLE machines " + ", ".join(parts))

    bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=1")


def _upgrade_generic(insp):
    # SQLite (and anything else): batch rename via ALTER ... RENAME
    op.rename_table('equipmenttypes', 'machinetypes')
    op.rename_table('equipment', 'machines')
    with op.batch_alter_table('machinetypes') as batch_op:
        batch_op.alter_column('equipmenttypeid', new_column_name='machinetypeid',
                              existing_type=sa.Integer(), existing_nullable=False)
        batch_op.alter_column('equipmenttype', new_column_name='machinetype',
                              existing_type=sa.String(100),
                              existing_nullable=False)
    with op.batch_alter_table('machines') as batch_op:
        batch_op.alter_column('equipmentid', new_column_name='machineid',
                              existing_type=sa.Integer(), existing_nullable=False)
        batch_op.alter_column('equipmenttypeid', new_column_name='machinetypeid',
                              existing_type=sa.Integer(), existing_nullable=True)


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

    if 'machines' not in tables and 'machinetypes' not in tables:
        return

    if bind.dialect.name != 'mysql':
        raise NotImplementedError(
            "downgrade implemented for MySQL only (dev/prod dialect)")

    bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=0")
    machine_indexes = {ix['name'] for ix in insp.get_indexes('machines')}
    for name in _fk_names(insp, 'machines', 'machinetypes'):
        bind.exec_driver_sql(f"ALTER TABLE machines DROP FOREIGN KEY {name}")

    bind.exec_driver_sql("RENAME TABLE machinetypes TO equipmenttypes")
    bind.exec_driver_sql("RENAME TABLE machines TO equipment")

    bind.exec_driver_sql(
        "ALTER TABLE equipmenttypes "
        "CHANGE machinetypeid equipmenttypeid INT NOT NULL AUTO_INCREMENT, "
        "CHANGE machinetype equipmenttype VARCHAR(100) NOT NULL, "
        "DROP KEY machinetype, "
        "ADD UNIQUE KEY equipmenttype (equipmenttype)"
    )

    parts = [
        "CHANGE machineid equipmentid INT NOT NULL AUTO_INCREMENT",
        "CHANGE machinetypeid equipmenttypeid INT NULL",
    ]
    if 'idx_machine_type' in machine_indexes:
        parts += ["DROP KEY idx_machine_type",
                  "ADD KEY idx_equipment_type (equipmenttypeid)"]
    if 'idx_machine_vendor' in machine_indexes:
        parts += ["DROP KEY idx_machine_vendor",
                  "ADD KEY idx_equipment_vendor (vendorid)"]
    if 'ix_machines_assetid' in machine_indexes:
        parts += ["DROP KEY ix_machines_assetid",
                  "ADD UNIQUE KEY ix_equipment_assetid (assetid)"]
    parts += [
        "ADD CONSTRAINT equipment_ibfk_2 "
        "FOREIGN KEY (equipmenttypeid) REFERENCES equipmenttypes (equipmenttypeid)"
    ]
    bind.exec_driver_sql("ALTER TABLE equipment " + ", ".join(parts))
    bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=1")
