"""ADR-001 position contract surface

Rename Asset.mapleft -> Asset.mapx, Asset.maptop -> Asset.mapy. Add
Location.mapx / Location.mapy. Add AssetRelationship.label,
AssetRelationship.inheritsposition, RelationshipType.propagatesthroughid.
Seed the three canonical relationship types (partof, controls, connectedto)
with controls.propagatesthroughid -> partof.

Revision ID: 7a01_adr001_position
Revises: 68b3947ae14f
Create Date: 2026-05-30

"""
from alembic import op
import sqlalchemy as sa


revision = '7a01_adr001_position'
down_revision = '68b3947ae14f'
branch_labels = None
depends_on = None


def upgrade():
    # ---- Asset.mapleft/maptop -> mapx/mapy --------------------------------
    with op.batch_alter_table('assets') as batch_op:
        batch_op.alter_column('mapleft', new_column_name='mapx',
                              existing_type=sa.Integer(), existing_nullable=True,
                              comment='X coordinate on floor map (ADR-001)')
        batch_op.alter_column('maptop', new_column_name='mapy',
                              existing_type=sa.Integer(), existing_nullable=True,
                              comment='Y coordinate on floor map (ADR-001)')

    # ---- Location: add mapx/mapy for the fallback path --------------------
    with op.batch_alter_table('locations') as batch_op:
        batch_op.add_column(sa.Column('mapx', sa.Integer(), nullable=True,
                                       comment='Default X coordinate for assets at this location'))
        batch_op.add_column(sa.Column('mapy', sa.Integer(), nullable=True,
                                       comment='Default Y coordinate for assets at this location'))

    # ---- RelationshipType: add propagatesthroughid self-FK ----------------
    with op.batch_alter_table('relationshiptypes') as batch_op:
        batch_op.add_column(sa.Column('propagatesthroughid', sa.Integer(),
                                       nullable=True,
                                       comment='Sibling-propagation rail per ADR-001'))
        batch_op.create_foreign_key(
            'fk_relationshiptype_propagation',
            'relationshiptypes',
            ['propagatesthroughid'],
            ['relationshiptypeid'],
        )

    # ---- AssetRelationship: add label + inheritsposition ------------------
    with op.batch_alter_table('assetrelationships') as batch_op:
        batch_op.add_column(sa.Column('label', sa.String(length=200), nullable=True,
                                       comment='Free-text relationship description (ADR-001)'))
        batch_op.add_column(sa.Column('inheritsposition', sa.Boolean(),
                                       nullable=False, server_default='1',
                                       comment='If true, resolved-position walk follows this edge (ADR-001)'))

    # ---- Seed three canonical relationship types --------------------------
    # Idempotent: insert only if name is not already present. Then update
    # controls.propagatesthroughid to point at partof.
    relationshiptypes = sa.table(
        'relationshiptypes',
        sa.column('relationshiptype', sa.String),
        sa.column('description', sa.Text),
        sa.column('propagatesthroughid', sa.Integer),
        sa.column('isactive', sa.Boolean),
    )

    conn = op.get_bind()
    dialect_name = conn.dialect.name

    # MySQL default collation is case-insensitive, which makes a plain
    # `WHERE relationshiptype = 'controls'` match a legacy `Controls` row
    # and silently skip the insert (then accidentally wire propagation onto
    # the wrong row). Force binary comparison on MySQL so the three ADR
    # types are always distinct from legacy capitalized names. SQLite +
    # PostgreSQL are case-sensitive by default; the plain comparison is
    # safe there.
    def _eq(col_expr, value):
        if dialect_name == 'mysql':
            return f"BINARY {col_expr} = '{value}'"
        return f"{col_expr} = '{value}'"

    for rt in (
        ('partof', 'Composition / sub-assembly (ADR-001)'),
        ('controls', 'Operational authority over another asset (ADR-001)'),
        ('connectedto', 'Network or data link without authority (ADR-001)'),
    ):
        existing = conn.execute(sa.text(
            f"SELECT relationshiptypeid FROM relationshiptypes WHERE {_eq('relationshiptype', rt[0])}"
        )).first()
        if not existing:
            conn.execute(relationshiptypes.insert().values(
                relationshiptype=rt[0],
                description=rt[1],
                propagatesthroughid=None,
                isactive=True,
            ))

    partof_row = conn.execute(sa.text(
        f"SELECT relationshiptypeid FROM relationshiptypes WHERE {_eq('relationshiptype', 'partof')}"
    )).first()
    if partof_row:
        conn.execute(sa.text(
            f"UPDATE relationshiptypes SET propagatesthroughid = :p WHERE {_eq('relationshiptype', 'controls')}"
        ), {'p': partof_row[0]})


def downgrade():
    # Revert in reverse order. Drops the seeded ADR types if no rows
    # reference them; otherwise leaves them in place to avoid FK violations.
    conn = op.get_bind()

    with op.batch_alter_table('assetrelationships') as batch_op:
        batch_op.drop_column('inheritsposition')
        batch_op.drop_column('label')

    with op.batch_alter_table('relationshiptypes') as batch_op:
        batch_op.drop_constraint('fk_relationshiptype_propagation', type_='foreignkey')
        batch_op.drop_column('propagatesthroughid')

    with op.batch_alter_table('locations') as batch_op:
        batch_op.drop_column('mapy')
        batch_op.drop_column('mapx')

    with op.batch_alter_table('assets') as batch_op:
        batch_op.alter_column('mapx', new_column_name='mapleft',
                              existing_type=sa.Integer(), existing_nullable=True)
        batch_op.alter_column('mapy', new_column_name='maptop',
                              existing_type=sa.Integer(), existing_nullable=True)

    # Best-effort: drop the seeded types if nothing references them.
    for name in ('connectedto', 'controls', 'partof'):
        try:
            conn.execute(sa.text(
                "DELETE FROM relationshiptypes "
                "WHERE relationshiptype = :n "
                "AND relationshiptypeid NOT IN ("
                "  SELECT relationshiptypeid FROM assetrelationships "
                "  UNION SELECT relationshiptypeid FROM machinerelationships"
                ")"
            ), {'n': name})
        except Exception:
            pass
