import logging

from wurker.util import init_logger
from wurker.settings import conf
from wurker.models import LocalBase, Base
from wurker.db import schema_exists, make_engine, make_session


class CommandNotReady(Exception):
    """Indicate to the caller that this command is not ready for the given reason."""


class BaseCommand:
    """Parent class for CLI commands, providing common settings and method prototypes."""

    logger = None

    # Universal command attributes
    env : str = ''
    app_id : str = ''
    local_db_conf : str = ''
    remote_db_conf : str = ''

    # DB engines and sessions are on-demand & init-once
    local_db_engine = None
    local_db_session = None
    remote_db_engine = None
    remote_db_session = None

    def __init__(self, logger=None, check_schema=False):
        """Initialize common class attributes."""

        # Init logger
        self.logger = logger or init_logger()

        self.app_id = conf.app_id
        if not self.app_id:
            raise CommandNotReady("Missing valid WURKER_APP_ID")

        self.env = conf.env or "production"
        self.remote_db_conf = conf.RemoteDB
        self.local_db_conf = conf.LocalDB

        # Check shemas?
        if check_schema:
            self.check_schema()

    def log(self, msg, exception=False, level=logging.INFO, prefix=''):
        """Adds Bee-specific metadata to log entries."""

        if exception:
            level = logging.ERROR
            msg += "\n{0}".format(traceback.format_exc())

        prefix = prefix or "Wurkman"
        self.logger.log(msg, level=level, prefix=prefix)

    @property
    def LocalDB(self):
        """Returns a connection to the remote DB. Raises exceptions."""

        if not self.local_db_conf:
            raise CommandNotReady("You must configure a local database.")

        if not self.local_db_engine:
            engine = make_engine(db_conf=self.local_db_conf)
            self.local_db_engine = engine

        return self.local_db_engine.connect()

    @property
    def LocalSession(self):
        """
        Returns a Session class ready to be used with SQLAlchemy ORM. Raises exceptions.

        Usage:

            with self.LocalSession() as session:
                result = session.query(SomeModel).all()
        """

        if not self.local_db_conf:
            raise CommandNotReady("You must configure a local database.")

        if not self.local_db_session:
            session = make_session(db_conf=self.local_db_conf)
            self.local_db_session = session

        return self.local_db_session

    @property
    def RemoteDB(self):
        """Returns a connection to the remote DB. Raises exceptions."""

        if not self.remote_db_conf:
            raise CommandNotReady("You must configure a remote database. Local by default.")

        if not self.remote_db_engine:
            engine = make_engine(db_conf=self.remote_db_conf)
            self.remote_db_engine = engine

        return self.remote_db_engine.connect()

    @property
    def RemoteSession(self):
        """
        Returns a Session class ready to be used with SQLAlchemy ORM. Raises exceptions.

        Usage:

            with self.RemoteSession() as session:
                result = session.query(SomeModel).all()
        """

        if not self.remote_db_conf:
            raise CommandNotReady("You must configure a remote database.")

        if not self.remote_db_session:
            session = make_session(db_conf=self.remote_db_conf)
            self.remote_db_session = session

        return self.remote_db_session

    def check_schema(self):
        """Check the existence of the given `DBConf` object, or both local and remote DBs if `None`."""

        # Check local 1st, then remote
        if not self.local_db_conf:
            raise CommandNotReady("You must configure a local database.")

        if not schema_exists(self.local_db_conf, LocalBase, return_bool=True):
            raise CommandNotReady("The local database does not exist.")

        if not self.remote_db_conf:
            raise CommandNotReady("You must configure a remote database. Local by default.")

        if not schema_exists(self.remote_db_conf, Base, return_bool=True):
            raise CommandNotReady("The remote database does not exist.")

    def run(self, *args, **kwargs):
        """Prototype run method. Implement in child class."""
        raise NotImplementedError

    def run_drone(self, *args, **kwargs):
        """Prototype run_drone method. Implement in child class."""
        raise NotImplementedError
