from collections.abc import Generator
from typing import (
    TYPE_CHECKING,
)

import pytest
from sqlalchemy import (
    select,
    text,
)

from galaxy import model as m
from galaxy.model import (
    GroupRoleAssociation,
    User,
    UserGroupAssociation,
    UserRoleAssociation,
)
from galaxy.model.unittest_utils.migration_scripts_testing_utils import run_command

if TYPE_CHECKING:
    from sqlalchemy.engine import Engine

COMMAND = "manage_db.sh"


@pytest.fixture(autouse=True)
def clear_database(engine: "Engine") -> "Generator":
    """Delete all rows from all tables. Called after each test."""
    yield
    with engine.begin() as conn:
        for table in m.mapper_registry.metadata.tables:
            # Unless db is sqlite, disable foreign key constraints to delete out of order
            if engine.name != "sqlite":
                conn.execute(text(f"ALTER TABLE {table} DISABLE TRIGGER ALL"))
            conn.execute(text(f"DELETE FROM {table}"))


@pytest.fixture(autouse=True)
def upgrade_database_after_test():
    """Run after each test for proper cleanup"""
    yield
    run_command(f"{COMMAND} upgrade")


def test_1cf595475b58(monkeypatch, session, make_user, make_history):
    # Initialize db and migration environment
    dburl = str(session.bind.url)
    monkeypatch.setenv("GALAXY_CONFIG_OVERRIDE_DATABASE_CONNECTION", dburl)
    monkeypatch.setenv("GALAXY_INSTALL_CONFIG_OVERRIDE_INSTALL_DATABASE_CONNECTION", dburl)
    run_command(f"{COMMAND} init")

    # STEP 0: Load pre-migration state
    run_command(f"{COMMAND} downgrade d619fdfa6168")

    # STEP 1: Load users with duplicate emails

    # Duplicate group 1: users have no histories
    # Expect: oldest user preserved
    u1_1 = make_user(email="a")
    u1_2 = make_user(email="a")
    u1_3 = make_user(email="a")
    original_email1 = u1_1.email
    assert u1_1.email == u1_2.email == u1_3.email
    assert u1_1.create_time < u1_2.create_time < u1_3.create_time  # u1_1 is oldest user

    # Duplicate group 2: oldest user does NOT have a history, another user has a history
    # Expect: user with history preserved
    u2_1 = make_user(email="b")
    u2_2 = make_user(email="b")
    u2_3 = make_user(email="b")
    original_email2 = u2_1.email
    assert u2_1.email == u2_2.email == u2_3.email
    assert u2_1.create_time < u2_2.create_time < u2_3.create_time  # u2_1 is oldest user

    make_history(user=u2_2)  # u2_2 has a history

    # Duplicate group 3: oldest user does NOT have a history, 2 users have a history
    # Expect: oldest user with history preserved
    u3_1 = make_user(email="c")
    u3_2 = make_user(email="c")
    u3_3 = make_user(email="c")
    original_email3 = u3_1.email
    assert u3_1.email == u3_2.email == u3_3.email
    assert u3_1.create_time < u3_2.create_time < u3_3.create_time  # u2_1 is oldest user

    make_history(user=u3_2)  # u3_2 has a history
    make_history(user=u3_3)  # u3_3 has a history

    # User w/o duplicate email
    u4 = make_user()
    original_email4 = u4.email

    # STEP 2: Run migration

    run_command(f"{COMMAND} upgrade")
    session.expire_all()

    # STEP 3: Verify deduplicated results

    # Duplicate group 1:
    u1_1_fixed = session.get(User, u1_1.id)
    u1_2_fixed = session.get(User, u1_2.id)
    u1_3_fixed = session.get(User, u1_3.id)

    # oldest user's email is preserved; the rest are deduplicated
    assert u1_1.email == original_email1
    assert u1_1.email != u1_2.email != u1_3.email
    # deduplicated users are marked as deleted
    assert u1_1_fixed.deleted is False
    assert u1_2_fixed.deleted is True
    assert u1_3_fixed.deleted is True

    # Duplicate group 2:
    u2_1_fixed = session.get(User, u2_1.id)
    u2_2_fixed = session.get(User, u2_2.id)
    u2_3_fixed = session.get(User, u2_3.id)

    # the email of the user with a history is preserved; the rest are deduplicated
    assert u2_2.email == original_email2
    assert u2_1.email != u1_2.email != u1_3.email
    # deduplicated users are marked as deleted
    assert u2_1_fixed.deleted is True
    assert u2_2_fixed.deleted is False
    assert u2_3_fixed.deleted is True

    # Duplicate group 3:
    u3_1_fixed = session.get(User, u3_1.id)
    u3_2_fixed = session.get(User, u3_2.id)
    u3_3_fixed = session.get(User, u3_3.id)

    # the email of the oldest user with a history is preserved; the rest are deduplicated
    assert u3_2.email == original_email3
    assert u3_1.email != u3_2.email != u3_3.email
    # deduplicated users are marked as deleted
    assert u3_1_fixed.deleted is True
    assert u3_2_fixed.deleted is False
    assert u3_3_fixed.deleted is True

    # User w/o duplicate email
    u4_no_change = session.get(User, u4.id)
    assert u4_no_change.email == original_email4
    assert u4_no_change.deleted is False


def test_d619fdfa6168(monkeypatch, session, make_user):
    # Initialize db and migration environment
    dburl = str(session.bind.url)
    monkeypatch.setenv("GALAXY_CONFIG_OVERRIDE_DATABASE_CONNECTION", dburl)
    monkeypatch.setenv("GALAXY_INSTALL_CONFIG_OVERRIDE_INSTALL_DATABASE_CONNECTION", dburl)
    run_command(f"{COMMAND} init")

    # STEP 0: Load pre-migration state
    run_command(f"{COMMAND} downgrade d2d8f51ebb7e")

    # STEP 1: Load users with duplicate usernames

    # Expect: oldest user preserved
    u1 = make_user(username="a")
    u2 = make_user(username="a")
    u3 = make_user(username="a")
    original_username = u3.username
    assert u1.username == u2.username == u3.username
    assert u1.create_time < u2.create_time < u3.create_time  # u3 is newest user

    # STEP 2: Run migration
    run_command(f"{COMMAND} upgrade")
    session.expire_all()

    # STEP 3: Verify deduplicated results
    u1_fixed = session.get(User, u1.id)
    u2_fixed = session.get(User, u2.id)
    u3_fixed = session.get(User, u3.id)

    # oldest user's username is preserved; the rest are deduplicated
    assert u3_fixed.username == original_username
    assert u1.username != u2.username != u3.username
    # deduplicated users are marked as deleted
    assert u1_fixed.deleted is True
    assert u2_fixed.deleted is True
    assert u3_fixed.deleted is False


def test_349dd9d9aac9(monkeypatch, session, make_user, make_role, make_user_role_association):
    # Initialize db and migration environment
    dburl = str(session.bind.url)
    monkeypatch.setenv("GALAXY_CONFIG_OVERRIDE_DATABASE_CONNECTION", dburl)
    monkeypatch.setenv("GALAXY_INSTALL_CONFIG_OVERRIDE_INSTALL_DATABASE_CONNECTION", dburl)
    run_command(f"{COMMAND} init")

    # Load pre-migration state
    run_command(f"{COMMAND} downgrade 1cf595475b58")

    # Load duplicate records
    u1, u2 = make_user(), make_user()
    r1, r2 = make_role(), make_role()
    make_user_role_association(user=u1, role=r1)
    make_user_role_association(user=u1, role=r2)
    make_user_role_association(user=u1, role=r2)  # duplicate
    make_user_role_association(user=u2, role=r1)
    make_user_role_association(user=u2, role=r1)  # duplicate

    # Verify duplicates
    assert len(u1.roles) == 3
    assert len(u2.roles) == 2
    all_associations = session.execute(select(UserRoleAssociation)).all()
    assert len(all_associations) == 5

    # Run migration
    run_command(f"{COMMAND} upgrade")
    session.expire_all()

    # Verify clean data
    assert len(u1.roles) == 2
    assert len(u2.roles) == 1
    all_associations = session.execute(select(UserRoleAssociation)).all()
    assert len(all_associations) == 3


def test_56ddf316dbd0(monkeypatch, session, make_user, make_group, make_user_group_association):
    # Initialize db and migration environment
    dburl = str(session.bind.url)
    monkeypatch.setenv("GALAXY_CONFIG_OVERRIDE_DATABASE_CONNECTION", dburl)
    monkeypatch.setenv("GALAXY_INSTALL_CONFIG_OVERRIDE_INSTALL_DATABASE_CONNECTION", dburl)
    run_command(f"{COMMAND} init")

    # Load pre-migration state
    run_command(f"{COMMAND} downgrade 1fdd615f2cdb")

    # Load duplicate records
    u1, u2 = make_user(), make_user()
    g1, g2 = make_group(), make_group()
    make_user_group_association(user=u1, group=g1)
    make_user_group_association(user=u1, group=g2)
    make_user_group_association(user=u1, group=g2)  # duplicate
    make_user_group_association(user=u2, group=g1)
    make_user_group_association(user=u2, group=g1)  # duplicate

    # Verify duplicates
    assert len(u1.groups) == 3
    assert len(u2.groups) == 2
    all_associations = session.execute(select(UserGroupAssociation)).all()
    assert len(all_associations) == 5

    # Run migration
    run_command(f"{COMMAND} upgrade")
    session.expire_all()

    # Verify clean data
    assert len(u1.groups) == 2
    assert len(u2.groups) == 1
    all_associations = session.execute(select(UserGroupAssociation)).all()
    assert len(all_associations) == 3


def test_9ef6431f3a4e(monkeypatch, session, make_group, make_role, make_group_role_association):
    # Initialize db and migration environment
    dburl = str(session.bind.url)
    monkeypatch.setenv("GALAXY_CONFIG_OVERRIDE_DATABASE_CONNECTION", dburl)
    monkeypatch.setenv("GALAXY_INSTALL_CONFIG_OVERRIDE_INSTALL_DATABASE_CONNECTION", dburl)
    run_command(f"{COMMAND} init")

    # Load pre-migration state
    run_command(f"{COMMAND} downgrade 13fe10b8e35b")

    # Load duplicate records
    g1, g2 = make_group(), make_group()
    r1, r2 = make_role(), make_role()
    make_group_role_association(group=g1, role=r1)
    make_group_role_association(group=g1, role=r2)
    make_group_role_association(group=g1, role=r2)  # duplicate
    make_group_role_association(group=g2, role=r1)
    make_group_role_association(group=g2, role=r1)  # duplicate

    # Verify duplicates
    assert len(g1.roles) == 3
    assert len(g2.roles) == 2
    all_associations = session.execute(select(GroupRoleAssociation)).all()
    assert len(all_associations) == 5

    # Run migration
    run_command(f"{COMMAND} upgrade")
    session.expire_all()

    # Verify clean data
    assert len(g1.roles) == 2
    assert len(g2.roles) == 1
    all_associations = session.execute(select(GroupRoleAssociation)).all()
    assert len(all_associations) == 3


def test_1fdd615f2cdb(monkeypatch, session, make_user, make_role, make_user_role_association):
    # Initialize db and migration environment
    dburl = str(session.bind.url)
    monkeypatch.setenv("GALAXY_CONFIG_OVERRIDE_DATABASE_CONNECTION", dburl)
    monkeypatch.setenv("GALAXY_INSTALL_CONFIG_OVERRIDE_INSTALL_DATABASE_CONNECTION", dburl)
    run_command(f"{COMMAND} init")

    # Load pre-migration state
    run_command(f"{COMMAND} downgrade 349dd9d9aac9")

    # Load records w/nulls
    ura1 = make_user_role_association(user=make_user(), role=make_role())
    ura2 = make_user_role_association(user=make_user(), role=make_role())
    ura3 = make_user_role_association(user=make_user(), role=make_role())
    ura1.user_id = None
    ura2.role_id = None
    ura3.user_id = None
    ura3.role_id = None
    session.add_all([ura1, ura2, ura3])
    session.commit()

    # Load record w/o nulls
    make_user_role_association(user=make_user(), role=make_role())

    # Verify data
    all_associations = session.execute(select(UserRoleAssociation)).all()
    assert len(all_associations) == 4

    # Run migration
    run_command(f"{COMMAND} upgrade")
    session.expire_all()

    # Verify clean data
    all_associations = session.execute(select(UserRoleAssociation)).all()
    assert len(all_associations) == 1


def test_13fe10b8e35b(monkeypatch, session, make_user, make_group, make_user_group_association):
    # Initialize db and migration environment
    dburl = str(session.bind.url)
    monkeypatch.setenv("GALAXY_CONFIG_OVERRIDE_DATABASE_CONNECTION", dburl)
    monkeypatch.setenv("GALAXY_INSTALL_CONFIG_OVERRIDE_INSTALL_DATABASE_CONNECTION", dburl)
    run_command(f"{COMMAND} init")

    # Load pre-migration state
    run_command(f"{COMMAND} downgrade 56ddf316dbd0")

    # Load records w/nulls
    uga1 = make_user_group_association(user=make_user(), group=make_group())
    uga2 = make_user_group_association(user=make_user(), group=make_group())
    uga3 = make_user_group_association(user=make_user(), group=make_group())
    uga1.user_id = None
    uga2.group_id = None
    uga3.user_id = None
    uga3.group_id = None
    session.add_all([uga1, uga2, uga3])
    session.commit()

    # Load record w/o nulls
    make_user_group_association(user=make_user(), group=make_group())

    # Verify data
    all_associations = session.execute(select(UserGroupAssociation)).all()
    assert len(all_associations) == 4

    # Run migration
    run_command(f"{COMMAND} upgrade")
    session.expire_all()

    # Verify clean data
    all_associations = session.execute(select(UserGroupAssociation)).all()
    assert len(all_associations) == 1


def test_25b092f7938b(monkeypatch, session, make_group, make_role, make_group_role_association):
    # Initialize db and migration environment
    dburl = str(session.bind.url)
    monkeypatch.setenv("GALAXY_CONFIG_OVERRIDE_DATABASE_CONNECTION", dburl)
    monkeypatch.setenv("GALAXY_INSTALL_CONFIG_OVERRIDE_INSTALL_DATABASE_CONNECTION", dburl)
    run_command(f"{COMMAND} init")

    # Load pre-migration state
    run_command(f"{COMMAND} downgrade 9ef6431f3a4e")

    # Load records w/nulls
    gra1 = make_group_role_association(group=make_group(), role=make_role())
    gra2 = make_group_role_association(group=make_group(), role=make_role())
    gra3 = make_group_role_association(group=make_group(), role=make_role())
    gra1.group_id = None
    gra2.role_id = None
    gra3.group_id = None
    gra3.role_id = None
    session.add_all([gra1, gra2, gra3])
    session.commit()

    # Load record w/o nulls
    make_group_role_association(group=make_group(), role=make_role())

    # Verify data
    all_associations = session.execute(select(GroupRoleAssociation)).all()
    assert len(all_associations) == 4

    # Run migration
    run_command(f"{COMMAND} upgrade")
    session.expire_all()

    # Verify clean data
    all_associations = session.execute(select(GroupRoleAssociation)).all()
    assert len(all_associations) == 1
