from sqlalchemy import engine_from_config, event
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import configure_mappers
import zope.sqlalchemy

# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines.
from .product import *
from .shop import *
from .shop_location import *
from .user import *
from .user_shop import *
from .user_product import *
from .user_address import *
from .price import *
from .cart import *
from .cart_coupon import *
from .market import *
from .coupon import *
from .coupon_redemption import *
from .invoice import *
from .inventory import *
from .crypto_payment import *
from .crypto_processor import *
from .user_crypto_refund_address import *

from .stripe_user_shop import *
from .paypal_user_shop import *

from .shop_search_request import *
from .comment import *
from .shop_subscription import *
from .page_session import *

from .gift_card import *
from .gift_card_transaction import *
from .cart_gift_card import *

from .api_key import *

from .auction import *
from .offer import *
from .cart_auction import *
from .cart_offer import *

# run configure_mappers after defining all of the models
# to ensure all relationships can be setup.
configure_mappers()


def get_engine(settings, prefix="sqlalchemy."):
    # SQLite requires check_same_thread=False for multi-threaded access
    # (e.g. background reforge threads sharing the engine's connection pool)
    url = settings.get(f"{prefix}url", "")
    kwargs = {}
    if "sqlite" in url:
        kwargs["connect_args"] = {"check_same_thread": False}
    engine = engine_from_config(settings, prefix, **kwargs)
    if engine.url.get_backend_name() == "sqlite":

        @event.listens_for(engine, "connect")
        def set_sqlite_pragma(dbapi_conn, connection_record):
            cursor = dbapi_conn.cursor()
            cursor.execute("PRAGMA busy_timeout = 30000")
            cursor.close()

    return engine


def get_session_factory(engine):
    factory = sessionmaker()
    factory.configure(bind=engine)
    return factory


def get_tm_session(session_factory, transaction_manager):
    """
    Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.

    This function will hook the session to the transaction manager which
    will take care of committing any changes.

    - When using pyramid_tm it will automatically be committed or aborted
      depending on whether an exception is raised.

    - When using scripts you should wrap the session in a manager yourself.
      For example::

          import transaction

          engine = get_engine(settings)
          session_factory = get_session_factory(engine)
          with transaction.manager:
              dbsession = get_tm_session(session_factory, transaction.manager)

    """
    dbsession = session_factory()
    zope.sqlalchemy.register(dbsession, transaction_manager=transaction_manager)
    return dbsession


def includeme(config):
    """
    Initialize the model for a Pyramid app.

    Activate this setup using ``config.include('make_post_sell.models')``.

    """
    settings = config.get_settings()
    settings["tm.manager_hook"] = "pyramid_tm.explicit_manager"

    # use pyramid_tm to hook the transaction lifecycle to the request
    config.include("pyramid_tm")

    # use pyramid_retry to retry a request when transient exceptions occur
    config.include("pyramid_retry")

    session_factory = get_session_factory(get_engine(settings))
    config.registry["dbsession_factory"] = session_factory

    # make request.dbsession available for use in Pyramid
    config.add_request_method(
        # r.tm is the transaction manager used by pyramid_tm
        lambda r: get_tm_session(session_factory, r.tm),
        "dbsession",
        reify=True,
    )
