import re
import sys
import logging
import pkg_resources
from contextlib import contextmanager

from sqlalchemy.engine import create_engine
from sqlalchemy.orm.session import Session

from .version import __version__


logger = logging.getLogger(__name__)


class Marcotti(object):

    def __init__(self, config):
        logger.info("Marcotti v{0}: Python {1} on {2}".format(
            __version__, sys.version, sys.platform))
        logger.info("Opened connection to {0}".format(self._public_db_uri(config.database_uri)))
        self.engine = create_engine(config.database_uri)
        self.connection = self.engine.connect()

    @staticmethod
    def _public_db_uri(uri):
        """
        Strip out database username/password from database URI.

        :param uri: Database URI string.
        :return: Database URI with username/password removed.
        """
        return re.sub(r"//.*@", "//", uri)

    def create_db(self, base):
        """
        Create database models from database schema object.
        
        :param base: Base schema object that contains data model objects.
        """
        logger.info("Creating data models")
        base.metadata.create_all(self.connection)

    @contextmanager
    def create_session(self):
        """
        Open transaction session with an active database object.
        
        If an error occurs during the session, roll back uncommitted changes
        and report error to log file and user.
        
        If session is no longer needed, commit remaining transactions before closing it.
        """
        session = Session(self.connection)
        logger.info("Create session {0} with {1}".format(
            id(session), self._public_db_uri(str(self.engine.url))))
        try:
            yield session
            session.commit()
            logger.info("Committing remaining transactions to database")
        except Exception as ex:
            session.rollback()
            logger.exception("Database transactions rolled back")
            raise ex
        finally:
            logger.info("Session {0} with {1} closed".format(
                id(session), self._public_db_uri(str(self.engine.url))))
            session.close()


class MarcottiConfig(object):
    """
    Base configuration class for Marcotti.  Contains one method that defines the database URI.

    This class is to be subclassed and its attributes defined therein.
    """

    @property
    def database_uri(self):
        if getattr(self, 'DIALECT') == 'sqlite':
            uri = r'sqlite://{p.DBNAME}'.format(p=self)
        else:
            uri = r'{p.DIALECT}://{p.DBUSER}:{p.DBPASSWD}@{p.HOSTNAME}:{p.PORT}/{p.DBNAME}'.format(p=self)
        return uri