import logging
from pathlib import Path
from typing import Set

import alembic.command
import alembic.config
import alembic.runtime.environment
import alembic.script

from ...configuration import config

log = logging.getLogger(__name__)

HERE = Path(__file__).parent


class AlembicMigration:
    """Glue Duffy (mainly its config) and Alembic DB migrations together."""

    @property
    def config(self):
        if not hasattr(self, "_config"):
            self._config = alembic.config.Config()
            self._config.set_main_option("script_location", str(HERE.absolute()).replace("%", "%%"))
            self._config.set_main_option(
                "sqlalchemy.url", config["database"]["sqlalchemy"]["sync_url"].replace("%", "%%")
            )
        return self._config

    def create(self, comment: str, autogenerate: bool):
        alembic.command.revision(config=self.config, message=comment, autogenerate=autogenerate)

        if autogenerate:
            log.warning(
                ">>> Remember to edit the autogenerated migration script! Unedited, it would drop"
                " any support table not registered with your SQLAlchemy metadata."
            )

    def _get_current(self) -> Set[str]:
        script = alembic.script.ScriptDirectory.from_config(self.config)

        current_revs = set()

        def _get_rev_current(rev, context):
            current_revs.update(
                _rev.cmd_format(verbose=False) for _rev in script.get_all_current(rev)
            )
            return []

        with alembic.runtime.environment.EnvironmentContext(
            self.config, script, fn=_get_rev_current, dont_mutate=True
        ):
            script.run_env()

        return current_revs

    def db_version(self):
        print("\n".join(self._get_current()))

    def upgrade(self, version: str):
        pre_revs = self._get_current()
        alembic.command.upgrade(self.config, version)
        post_revs = self._get_current()
        if pre_revs == post_revs:
            print("Nothing to upgrade.")
        else:
            print("Upgraded to:", ", ".join(post_revs))

    def downgrade(self, version: str):
        pre_revs = self._get_current()
        alembic.command.downgrade(self.config, version)
        post_revs = self._get_current()
        if pre_revs == post_revs:
            print("Nothing to downgrade.")
        else:
            print("Downgraded to:", ", ".join(post_revs) if post_revs else "<base>")


alembic_migration = AlembicMigration()
