import os
import tempfile
from pathlib import Path

from asyncpg import Connection
from piou import Option, Derived, CommandGroup, Password

from padmy.env import SQL_DIR, MIGRATION_DIR
from padmy.logs import logs
from padmy.utils import get_pg, PgHost, PgPort, PgUser, PgDatabase, PgPassword

MIGRATION_DESCRIPTION = """
Utilities to handle schema migrations with PostgresSQL.  
If it's the first time running theses scripts please run 
**`poetry run cli migrate setup`** to setup the database's table
"""

migration = CommandGroup("migrate", help="Migration utilities", description=MIGRATION_DESCRIPTION)

MigrationDir = Option(MIGRATION_DIR or ..., "--sql-dir", help="Directory containing the migration files")
SQLDir = Option(SQL_DIR or ..., "--sql-dir", help="Directory containing the table definitions")


@migration.command(cmd="new-sql")
def new_sql_file(
    sql_dir: Path = SQLDir,
    position: int = Option(1, help="Position where to insert the sql file"),
):
    """
    Utility to insert a new sql file between other sql files.
    """
    from .new_sql import create_sql_file

    create_sql_file(sql_dir, position)


@migration.command(cmd="apply-sql")
async def apply_sql_files(
    pg_conn: Connection = Derived(get_pg),
    sql_dir: Path = SQLDir,
):
    """
    Utility to insert a new sql file between other sql files.
    """
    from ..utils import exec_file

    for file in sorted(sql_dir.glob("*.sql")):
        logs.info(f"Applying {file.name!r}")
        await exec_file(pg_conn, file)


@migration.command(cmd="new", help="Creates 2 new files for a migration (up and down)")
def new_migrate_file(migration_folder: Path = MigrationDir):
    """
    Creates 2 new files for a migration (up and down).
    When applying *up.sql* then *down.sql*, the database must be
    in the state it was previously.
    To check if your **down** file works as expected, you can use the `verify` command.
    """
    from .create_files import create_new_migration

    create_new_migration(migration_folder)


@migration.command(cmd="up", help="Migrate database to the new schema")
async def migrate_up_main(
    pg_conn: Connection = Derived(get_pg),
    sql_dir: Path = MigrationDir,
    nb_migrations: int = Option(1, "-n", "--nb-migrations", help="Number of migrations to apply"),
):
    from .migration import migrate_up, NoSetupTableError

    try:
        await migrate_up(pg_conn, folder=sql_dir, nb_migrations=nb_migrations)
    except NoSetupTableError as e:
        logs.critical(e, exc_info=False)


@migration.command(cmd="down", help="Rollback the database to the specified migration")
async def migrate_down_main(
    conn: Connection = Derived(get_pg),
    sql_dir: Path = MigrationDir,
    nb_rollbacks: int = Option(1, "-n", "--nb-rollbacks", help="Number of rollbacks to apply"),
):
    from .migration import migrate_down, NoSetupTableError

    try:
        await migrate_down(conn, folder=sql_dir, nb_migrations=nb_rollbacks)
    except NoSetupTableError as e:
        logs.critical(e, exc_info=False)


@migration.command(cmd="setup", help="Create the tables that will contain the migration metadata")
async def migrate_setup_main(pg_conn: Connection = Derived(get_pg)):
    from .migration import migrate_setup

    await migrate_setup(pg_conn)


@migration.command(cmd="verify", help="Verify that a migration is valid")
def migrate_verify_main(
    db: str = PgDatabase,
    pg_host: str = PgHost,
    pg_port: int = PgPort,
    pg_user: str = PgUser,
    pg_password: Password = PgPassword,
    schemas: list[str] = Option(..., "--schemas", help="Schemas impacted by the migration"),
    sql_dir: Path = MigrationDir,
):
    os.environ["PG_HOST"] = pg_host
    os.environ["PG_PORT"] = str(pg_port)
    os.environ["PG_USER"] = pg_user
    os.environ["PG_PASSWORD"] = pg_password

    from .migration import migrate_verify, MigrationError

    try:
        with tempfile.TemporaryDirectory() as dump_dir:
            migrate_verify(
                database=db,
                migration_folder=sql_dir,
                schemas=schemas,
                dump_dir=Path(dump_dir),
            )
    except MigrationError as e:
        logs.error(e.msg)
        logs.debug(e.diff)
