"""reindex_attribute_positions

Revision ID: 0262
Revises: 0261
Create Date: 2025-01-06 21:42:07.772751

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '0262'
down_revision = '0261'
branch_labels = None
depends_on = None


def upgrade():
    # Deleting any overloaded positions on project and org attributes (deprecated feature)
    op.execute("""
        DELETE FROM attribute_positions WHERE attribute_positions.attribute_id IN (
            SELECT attributes.id FROM attributes
            WHERE group_id IN (SELECT attribute_group_id FROM projects)
            OR group_id IN (SELECT attribute_group_id FROM organizations)
        )
    """)

    # Reindexing positions for projects with models having overloaded positions
    conn = op.get_bind()
    projects_with_overloads = conn.execute(sa.text("""
        SELECT id, attribute_group_id, slug, (SELECT count(*) FROM attributes WHERE group_id = projects.attribute_group_id)
        FROM projects
        WHERE 0 < (
            SELECT count(*)
            FROM attribute_positions
            JOIN attributes ON attributes.id = attribute_positions.attribute_id
            JOIN card_models ON card_models.attribute_group_id = attributes.group_id
            JOIN project_enabled_models ON project_enabled_models.model_id = card_models.id
            WHERE project_enabled_models.project_id = projects.id AND attribute_positions.namespace = projects.id::text
        )
        ORDER BY projects.id
    """))

    for r in projects_with_overloads:
        # Reindexing positions for the project
        project_id = r[0]
        attr_group_id = r[1]
        containers = [row[0] for row in conn.execute(sa.text("""
            SELECT DISTINCT container FROM attributes WHERE group_id = :group_id
        """), {'group_id': attr_group_id})] + [None, '__top__']
        attr_count = r[3]

        if attr_count == 0:
            # No attributes in project, deleting any overloaded positions
            conn.execute(sa.text("""
                DELETE FROM attribute_positions
                USING attributes, card_models, project_enabled_models
                WHERE attributes.id = attribute_positions.attribute_id
                AND card_models.attribute_group_id = attributes.group_id
                AND project_enabled_models.model_id = card_models.id
                AND project_enabled_models.project_id = :project_id
                AND attribute_positions.namespace = :namespace
            """), {'project_id': project_id, 'namespace': str(project_id)})
            continue

        # Deleting any overloaded positions in non-project containers
        conn.execute(sa.text("""
            DELETE FROM attribute_positions
            USING attributes, card_models, project_enabled_models
            WHERE attributes.id = attribute_positions.attribute_id
            AND card_models.attribute_group_id = attributes.group_id
            AND project_enabled_models.model_id = card_models.id
            AND project_enabled_models.project_id = :project_id
            AND attribute_positions.namespace = :namespace
            AND attribute_positions.container NOT IN :containers
        """), {'project_id': project_id, 'namespace': str(project_id), 'containers': tuple(containers)})

        for container in containers:
            # Reindexing container
            position_map = {}

            attributes = conn.execute(sa.text("""
                SELECT id, position FROM attributes
                WHERE group_id = :group_id AND container = :container
                ORDER BY position
            """), {'group_id': attr_group_id, 'container': container})

            for i, attribute in enumerate(attributes):
                position_map[attribute.position] = i
                conn.execute(sa.text("""
                    UPDATE attributes SET position = :position WHERE id = :id
                """), {'position': i, 'id': attribute.id})

            if len(position_map) == 0:
                # No attributes in container, deleting any overloaded positions
                conn.execute(sa.text("""
                    DELETE FROM attribute_positions
                    USING attributes, card_models, project_enabled_models
                    WHERE attributes.id = attribute_positions.attribute_id
                    AND card_models.attribute_group_id = attributes.group_id
                    AND project_enabled_models.model_id = card_models.id
                    AND project_enabled_models.project_id = :project_id
                    AND attribute_positions.namespace = :namespace                 
                    AND attribute_positions.container = :container
                """), {'project_id': project_id, 'namespace': str(project_id), 'container': container})
            else:
                # Remapping relative positions in overloaded positions
                positions = conn.execute(sa.text("""
                    SELECT attribute_positions.id, attribute_positions.relative_to, attribute_positions.position, attribute_positions.container
                    FROM attribute_positions
                    JOIN attributes ON attributes.id = attribute_positions.attribute_id
                    JOIN card_models ON card_models.attribute_group_id = attributes.group_id
                    JOIN project_enabled_models ON project_enabled_models.model_id = card_models.id
                    WHERE project_enabled_models.project_id = :project_id
                    AND attribute_positions.namespace = :namespace                 
                    AND attribute_positions.container = :container
                    ORDER BY attribute_positions.position
                """), {'project_id': project_id, 'namespace': str(project_id), 'container': container})
                for position in positions:
                    if position.relative_to not in position_map:
                        conn.execute(sa.text("""
                            DELETE FROM attribute_positions WHERE id = :id
                        """), {'id': position.id})
                    else:
                        conn.execute(sa.text("""
                            UPDATE attribute_positions SET relative_to = :relative_to WHERE id = :id
                        """), {'relative_to': position_map[position.relative_to], 'id': position.id})
                            
    # Reindexing positions for project attributes across all projects without overloaded positions
    op.execute("""
        WITH
            projects_without_overloads as (
                SELECT attribute_group_id FROM projects
                WHERE 0 = (
                    SELECT count(*)
                    FROM attribute_positions
                    JOIN attributes ON attributes.id = attribute_positions.attribute_id
                    JOIN card_models ON card_models.attribute_group_id = attributes.group_id
                    JOIN project_enabled_models ON project_enabled_models.model_id = card_models.id
                    WHERE project_enabled_models.project_id = projects.id AND attribute_positions.namespace = projects.id::text
                )
            ),
            positions as (
                SELECT id, row_number() OVER (partition by group_id, container order by position) AS p
                FROM attributes
                JOIN projects_without_overloads ON attributes.group_id = projects_without_overloads.attribute_group_id
            )
        UPDATE attributes SET position = positions.p - 1 FROM positions
        WHERE positions.id = attributes.id
    """)

    # Reindexing all positions for attributes in models
    op.execute("""
        WITH positions as (
            SELECT attributes.id, row_number() OVER (partition by attributes.group_id, attributes.container order by position) AS p
            FROM attributes JOIN card_models ON card_models.attribute_group_id = attributes.group_id
        ) UPDATE attributes SET position = positions.p - 1 FROM positions
        WHERE positions.id = attributes.id
    """)

    # Reindexing all overloaded positions
    op.execute("""
        WITH positions as (
            SELECT ap.id, row_number() OVER (partition by attr.group_id, ap.namespace, ap.container, ap.relative_to order by ap.position) AS p
            FROM attribute_positions ap JOIN attributes attr ON attr.id = ap.attribute_id
        ) UPDATE attribute_positions SET position = positions.p - 1 FROM positions
        WHERE positions.id = attribute_positions.id
    """)

    op.execute("""UPDATE card_models SET cache_id = uuid_generate_v4()""")


def downgrade():
    pass
