###############################################################################
#
# Copyright 2014 by Shoobx, Inc.
#
###############################################################################
from typing import List, Iterable, Generic, TypeVar
import logging
import importlib.metadata

from migrant import exceptions

log = logging.getLogger(__name__)


# Database name type
DBN = TypeVar("DBN")

# Database connection type
DBC = TypeVar("DBC")


class MigrantBackend(Generic[DBN, DBC]):
    """Base interface for backend implementations"""

    def generate_connections(self) -> Iterable[DBN]:
        """Generate connections to process
        """
        raise NotImplementedError  # pragma: no cover

    def generate_test_connections(self) -> Iterable[DBN]:
        """Generate connections for migration tests

        The same interface as `generate_connections`, however this should not
        return "live" database connections, as tests might harm the data.

        If no connections are generated by this method, no tests will be run by
        `migrant test`.
        """
        return []  # pargma: no cover

    def begin(self, db: DBN) -> DBC:
        """Begin the migration

        This gives the opportunity to perform actual connection to the database
        before any migration is run.

        Can raise DatabaseUnavailable exception. In this case, migration will
        be skipped for this database.
        """
        raise NotImplementedError  # pragma: no cover

    def commit(self, db: DBC) -> None:
        """Called on successful completion of a migration

        This is an opportunity to commit work and close the connection.
        """
        pass

    def abort(self, db: DBC) -> None:
        """Called when migration have failed"""
        pass

    def cleanup(self, db: DBC) -> None:
        """Complete the migration

        Called when all work on database is done.
        """
        pass

    def list_migrations(self, db: DBC) -> List[str]:
        raise NotImplementedError  # pragma: no cover

    def push_migration(self, db: DBC, migration: str) -> None:
        raise NotImplementedError  # pragma: no cover

    def pop_migration(self, db: DBC, migration: str) -> None:
        raise NotImplementedError  # pragma: no cover

    def on_new_script(self, rev_name: str) -> None:
        """Called when new script is created
        """
        pass  # pragma: no cover

    def on_repo_init(self) -> None:
        """Called when new script repository is initialized
        """
        pass  # pragma: no cover


class NoopBackend(MigrantBackend[str, str]):
    def __init__(self, cfg):
        self.cfg = cfg

    def list_migrations(self, db: str) -> List[str]:
        return ["INITIAL"]

    def push_migration(self, db: str, migration: str) -> None:
        log.info("NOOP: pushing migration %s" % migration)

    def pop_migration(self, db: str, migration: str) -> None:
        log.info("NOOP: popping migration %s" % migration)

    def generate_connections(self):
        yield "NOOP"

    def begin(self, db: str) -> str:
        return db


def create_backend(cfg):
    name = cfg["backend"]

    factory = get_backend(name)
    return factory(cfg)


def get_backend(name):
    backends = importlib.metadata.entry_points(group="migrant").select(name=name)
    if not backends:
        raise exceptions.BackendNotRegistered(name)
    if len(backends) > 1:
        raise exceptions.BackendNameConflict(backends)
    pcls = backends[name].load()
    return pcls
