from dataclasses import dataclass
from pathlib import Path

import typer
from alembic import command as alembic_command
from alembic.config import Config as AlembicConfig
from alembic.util.exc import AutogenerateDiffsDetected, CommandError
from sqlalchemy.engine.url import URL

from .. import defaults as _defaults
from .. import env as _env
from ..alembic_utils import HitfactorpyAlembicConfig

cli = typer.Typer()


@dataclass(frozen=True)
class CtxDbConnection:
    scheme: str
    username: str
    password: str
    host: str
    port: int
    database: str


@dataclass(frozen=True)
class CtxObj:
    alembic_dir: Path
    db_connection: CtxDbConnection
    sqlalchemy_url: str
    alembic_cfg: AlembicConfig


@cli.callback()
def inject_shared_options(
    ctx: typer.Context,
    alembic_dir: Path = typer.Option(_defaults.HITFACTORPY_DB_ALEMBIC_DIR, envvar=_env.HITFACTORPY_DB_ALEMBIC_DIR),
    username: str = typer.Option(
        _defaults.HITFACTORPY_DB_USERNAME, help="Database username", envvar=_env.HITFACTORPY_DB_USERNAME
    ),
    password: str = typer.Option(
        _defaults.HITFACTORPY_DB_PASSWORD, help="Database password", envvar=_env.HITFACTORPY_DB_USERNAME
    ),
    host: str = typer.Option(_defaults.HITFACTORPY_DB_HOST, help="Database host", envvar=_env.HITFACTORPY_DB_HOST),
    port: int = typer.Option(_defaults.HITFACTORPY_DB_PORT, help="Database port", envvar=_env.HITFACTORPY_DB_PORT),
    database: str = typer.Option(
        _defaults.HITFACTORPY_DB_DATABASE_NAME, help="Database name", envvar=_env.HITFACTORPY_DB_DATABASE_NAME
    ),
):
    db_conn = CtxDbConnection(
        scheme="postgresql",
        username=username,
        password=password,
        host=host,
        port=port,
        database=database,
    )
    sqlalchemy_url = str(
        URL.create(  # type: ignore
            "postgresql+asyncpg",  # must use an async driver with this alembic config
            username=db_conn.username,
            password=db_conn.password,
            host=db_conn.host,
            port=db_conn.port,
            database=db_conn.database,
        )
    )
    alembic_cfg = HitfactorpyAlembicConfig(script_location=alembic_dir, sqlalchemy_url=sqlalchemy_url)
    ctx.obj = CtxObj(
        alembic_dir=alembic_dir,
        db_connection=db_conn,
        alembic_cfg=alembic_cfg,
        sqlalchemy_url=sqlalchemy_url,
    )


@cli.command()
def check(ctx: typer.Context):
    """alembic check"""
    config: CtxObj = ctx.obj

    try:
        alembic_command.check(config.alembic_cfg)
    except (AutogenerateDiffsDetected, CommandError) as e:
        typer.echo(e)
        raise typer.Exit(1)
    typer.echo("OK: DB and code are in sync")


@cli.command()
def upgrade(ctx: typer.Context, revision: str = "head"):
    """alembic upgrade <revision>"""
    config: CtxObj = ctx.obj

    try:
        alembic_command.upgrade(config.alembic_cfg, revision)
    except (AutogenerateDiffsDetected, CommandError) as e:
        typer.echo(e)
        raise typer.Exit(1)
    typer.echo("OK: migration upgrade applied")


@cli.command()
def downgrade(ctx: typer.Context, revision: str = "-1"):
    """alembic downgrade <revision>"""
    config: CtxObj = ctx.obj
    typer.confirm("Are you sure you want to downgrade?", abort=True)

    try:
        alembic_command.downgrade(config.alembic_cfg, revision)
    except (AutogenerateDiffsDetected, CommandError) as e:
        typer.echo(e)
        raise typer.Exit(1)

    typer.echo("OK: migration downgrade applied")


if __name__ == "__main__":
    cli()
