"""migrate func key paging

Revision ID: 234745874c55
Revises: 2d3d29858d6d

"""

# revision identifiers, used by Alembic.
revision = "234745874c55"
down_revision = "2d3d29858d6d"

import sqlalchemy as sa
from sqlalchemy import sql

from alembic import op

TYPE_SPEEDDIAL = "speeddial"

DESTINATION_PAGING_ID = 9

PAGING_TYPE = "paging"

phonefunckey_table = sql.table(
    "phonefunckey",
    sql.column("iduserfeatures"),
    sql.column("fknum"),
    sql.column("exten"),
    sql.column("typeextenumbers"),
    sql.column("typevalextenumbers"),
    sql.column("typeextenumbersright"),
    sql.column("typevalextenumbersright"),
    sql.column("label"),
    sql.column("supervision"),
    sql.column("progfunckey"),
)

func_key_table = sql.table(
    "func_key",
    sql.column("id"),
    sql.column("type_id"),
    sql.column("destination_type_id"),
)

func_key_type_table = sql.table("func_key_type", sql.column("id"), sql.column("name"))

destination_type_table = sql.table(
    "func_key_destination_type", sql.column("id"), sql.column("name")
)

func_key_mapping_table = sql.table(
    "func_key_mapping",
    sql.column("template_id"),
    sql.column("func_key_id"),
    sql.column("destination_type_id"),
    sql.column("label"),
    sql.column("position"),
    sql.column("blf"),
)

dest_paging_table = sql.table(
    "func_key_dest_paging",
    sql.column("func_key_id"),
    sql.column("destination_type_id"),
    sql.column("paging_id"),
)

template_table = sql.table("func_key_template", sql.column("id"))

user_table = sql.table(
    "userfeatures", sql.column("id"), sql.column("func_key_private_template_id")
)

paging_table = sql.table("paging", sql.column("id"), sql.column("number"))

func_key_columns = (
    phonefunckey_table.c.iduserfeatures.label("user_id"),
    phonefunckey_table.c.fknum.label("position"),
    phonefunckey_table.c.label,
    sql.cast(phonefunckey_table.c.supervision, sa.Boolean).label("blf"),
    paging_table.c.id.label("paging_id"),
)

old_func_key_columns = (
    func_key_mapping_table.c.func_key_id,
    func_key_mapping_table.c.template_id,
    func_key_mapping_table.c.position,
    func_key_mapping_table.c.blf,
    func_key_mapping_table.c.label,
    paging_table.c.number.label("number"),
    user_table.c.id.label("user_id"),
)

func_keys_query = sql.select(
    func_key_columns,
    from_obj=[
        phonefunckey_table.join(
            paging_table,
            phonefunckey_table.c.typevalextenumbersright == paging_table.c.number,
        )
    ],
).where(phonefunckey_table.c.typevalextenumbers == PAGING_TYPE)

old_func_keys_query = sql.select(
    old_func_key_columns,
    from_obj=[
        func_key_mapping_table.join(
            dest_paging_table,
            func_key_mapping_table.c.func_key_id == dest_paging_table.c.func_key_id,
        )
        .join(paging_table, dest_paging_table.c.paging_id == paging_table.c.id)
        .join(
            template_table, func_key_mapping_table.c.template_id == template_table.c.id
        )
        .join(
            user_table, template_table.c.id == user_table.c.func_key_private_template_id
        )
    ],
)


def upgrade():
    delete_duplicate_pagings()
    delete_empty_pagings()
    delete_invalid_pagings()
    migrate_func_keys()
    delete_old_func_keys()


def delete_duplicate_pagings():
    template = '[MIGRATE_FK] : Deleting func key for user "%s" (fk position %s with paging number %s)'
    for row in get_duplicate_func_keys():
        message = template % (
            row.iduserfeatures,
            row.fknum,
            row.typevalextenumbersright,
        )
        delete_fk(row, message)


def get_duplicate_func_keys():
    columns = (
        phonefunckey_table.c.iduserfeatures,
        phonefunckey_table.c.typevalextenumbersright,
        sa.func.min(phonefunckey_table.c.fknum).label("first_position"),
    )

    valid_fk_subq = (
        sql.select(columns)
        .where(phonefunckey_table.c.typevalextenumbers == PAGING_TYPE)
        .group_by(
            phonefunckey_table.c.iduserfeatures,
            phonefunckey_table.c.typevalextenumbersright,
        )
        .having(sa.func.count(phonefunckey_table.c.typevalextenumbersright) > 1)
        .alias()
    )

    columns = (
        phonefunckey_table.c.iduserfeatures,
        phonefunckey_table.c.typevalextenumbersright,
        phonefunckey_table.c.fknum,
    )

    join_condition = sql.and_(
        phonefunckey_table.c.typevalextenumbersright
        == valid_fk_subq.c.typevalextenumbersright,
        phonefunckey_table.c.fknum > valid_fk_subq.c.first_position,
        phonefunckey_table.c.iduserfeatures == valid_fk_subq.c.iduserfeatures,
    )

    duplicate_fk_query = sql.select(
        columns, from_obj=[phonefunckey_table.join(valid_fk_subq, join_condition)]
    )

    return op.get_bind().execute(duplicate_fk_query)


def delete_fk(row, message):
    print(message)

    query = phonefunckey_table.delete().where(
        sql.and_(
            phonefunckey_table.c.iduserfeatures == row.iduserfeatures,
            phonefunckey_table.c.fknum == row.fknum,
        )
    )

    op.get_bind().execute(query)


def delete_empty_pagings():
    template = '[MIGRATE_FK] : Deleting func key for user "%s" (fk position %s with empty paging)'

    columns = (phonefunckey_table.c.iduserfeatures, phonefunckey_table.c.fknum)

    query = sql.select(columns).where(
        sql.and_(
            phonefunckey_table.c.typevalextenumbers == PAGING_TYPE,
            phonefunckey_table.c.typevalextenumbersright == None,
        )
    )

    for row in op.get_bind().execute(query):
        message = template % (row.iduserfeatures, row.fknum)
        delete_fk(row, message)


def delete_invalid_pagings():
    template = '[MIGRATE_FK] : Deleting func key for user "%s" (fk position %s with invalid paging)'

    rows = get_invalid_pagings()

    for row in rows:
        message = template % (row.iduserfeatures, row.fknum)
        delete_fk(row, message)


def get_invalid_pagings():
    paging_query = sql.select([paging_table.c.number]).alias()

    columns = (phonefunckey_table.c.iduserfeatures, phonefunckey_table.c.fknum)

    query = sql.select(columns).where(
        sql.and_(
            phonefunckey_table.c.typevalextenumbers == PAGING_TYPE,
            phonefunckey_table.c.typevalextenumbersright.notin_(paging_query),
        )
    )

    return op.get_bind().execute(query)


def migrate_func_keys():
    for row in op.get_bind().execute(func_keys_query):
        func_key_id = find_or_create_paging(row.paging_id)
        create_mapping(func_key_id, row)


def find_or_create_paging(paging_id):
    query = sql.select([dest_paging_table.c.func_key_id]).where(
        dest_paging_table.c.paging_id == paging_id
    )

    func_key_id = op.get_bind().execute(query).scalar()

    if not func_key_id:
        func_key_id = create_func_key()
        create_paging_destination(func_key_id, paging_id)

    return func_key_id


def create_func_key():
    speeddial_id = get_speeddial_id()
    insert_query = (
        func_key_table.insert()
        .returning(func_key_table.c.id)
        .values(type_id=speeddial_id, destination_type_id=DESTINATION_PAGING_ID)
    )

    return op.get_bind().execute(insert_query).scalar()


def get_speeddial_id():
    return (
        op.get_bind()
        .execute(
            sql.select([func_key_type_table.c.id]).where(
                func_key_type_table.c.name == TYPE_SPEEDDIAL
            )
        )
        .scalar()
    )


def create_paging_destination(func_key_id, paging_id):
    destination_query = (
        dest_paging_table.insert()
        .returning(dest_paging_table.c.func_key_id)
        .values(func_key_id=func_key_id, paging_id=paging_id)
    )

    op.get_bind().execute(destination_query)


def create_mapping(func_key_id, func_key_row):
    conn = op.get_bind()

    template_id = conn.execute(
        sql.select([user_table.c.func_key_private_template_id]).where(
            user_table.c.id == func_key_row.user_id
        )
    ).scalar()

    mapping_query = (
        func_key_mapping_table.insert()
        .returning(func_key_mapping_table.c.func_key_id)
        .values(
            func_key_id=func_key_id,
            template_id=template_id,
            destination_type_id=DESTINATION_PAGING_ID,
            label=func_key_row.label,
            position=func_key_row.position,
            blf=func_key_row.blf,
        )
    )

    conn.execute(mapping_query)


def delete_old_func_keys():
    delete_query = phonefunckey_table.delete().where(
        phonefunckey_table.c.typevalextenumbers == PAGING_TYPE
    )

    op.get_bind().execute(delete_query)


def downgrade():
    for row in op.get_bind().execute(old_func_keys_query):
        create_old_func_keys(row)
        delete_mapping(row.func_key_id, row.template_id)
        delete_dest_paging(row.func_key_id)
        delete_func_key(row.func_key_id)


def create_old_func_keys(row):
    supervision = 1 if row.blf else 0

    row = {
        "iduserfeatures": row.user_id,
        "fknum": row.position,
        "typeextenumbers": "extenfeatures",
        "typevalextenumbers": "paging",
        "typeextenumbersright": "paging",
        "typevalextenumbersright": row.number,
        "label": row.label,
        "exten": None,
        "supervision": supervision,
    }

    op.bulk_insert(phonefunckey_table, [row])


def delete_mapping(func_key_id, template_id):
    query = func_key_mapping_table.delete().where(
        sql.and_(
            func_key_mapping_table.c.func_key_id == func_key_id,
            func_key_mapping_table.c.template_id == template_id,
        )
    )

    op.get_bind().execute(query)


def delete_dest_paging(func_key_id):
    query = dest_paging_table.delete()

    op.get_bind().execute(query)


def delete_func_key(func_key_id):
    query = func_key_table.delete().where(
        func_key_table.c.destination_type_id == DESTINATION_PAGING_ID
    )

    op.get_bind().execute(query)
