import threading
from functools import wraps
from django.conf import settings

threadlocal = threading.local()

class thread_local(object):
    """ a decorator that wraps a function in a thread local definition block
    useful for passing variables down the stack w/o.

    Usage:

    @thread_local(DB_FOR_READ_OVERRIDE='foobar')
    def override(request):
        ...

    """

    def __init__(self, **kwargs):
        self.options = kwargs

    def __enter__(self):
        for attr, value in self.options.items():
            setattr(threadlocal, attr, value)

    def __exit__(self, exc_type, exc_value, traceback):
        for attr in self.options.keys():
            delattr(threadlocal, attr)

    def __call__(self, func):

        @wraps(func)
        def inner(*args, **kwargs):
            # the thread_local class is also a context manager
            # which means it will call __enter__ and __exit__
            with self:
                return func(*args, **kwargs)

        return inner


def get_thread_local(attr, default=None):
    """ use this method from lower in the stack to get the value """
    return getattr(threadlocal, attr, default)


class DynamicRouter(object):
    """A router to control all database operations base on threadlocal variables.
    """

    def db_for_read(self, model, **hints):
        return get_thread_local('DB_FOR_READ_OVERRIDE')

    def db_for_write(self, model, **hints):
        return get_thread_local('DB_FOR_WRITE_OVERRIDE')

    def allow_relation(self, obj1, obj2, **hints):
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return None


class MimasRouter(object):
    """ Mimas router. Change all question-answer related operation to mimas.
    """
    MODEL_PREFIXS = ['answer.models', 'talos.models', 'hera.viewmodels']

    def manage_by_mimas(self, obj):
        if settings.TALOS_MIMAS_ROUTER:
            return any(obj.__module__.startswith(prefix) for prefix in self.MODEL_PREFIXS)
        else:
            return obj.__module__.startswith("answer.models")

    def db_for_read(self, model, **hints):
        if self.manage_by_mimas(model):
            if get_thread_local('DB_FOR_READ_OVERRIDE'):
                return settings.MIMAS_SLAVE_DB_NAME
            return settings.MIMAS_DB_NAME
        return None

    def db_for_write(self, model, **hints):
        if self.manage_by_mimas(model):
            return settings.MIMAS_DB_NAME
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if self.manage_by_mimas(obj1) and self.manage_by_mimas(obj2):
            return True
        elif not self.manage_by_mimas(obj1) and not self.manage_by_mimas(obj2):
            return None
        return False

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return None


class MessageRouter(object):
    MODEL_PREFIXS = 'api.models.message'
    TABLES = ['api_conversation', 'api_conversationuserstatus', 'api_message',
              'api_conversationtags', 'voice_alert_record', 'voice_alert_setting',
              'voice_alert_staff', 'voice_alert_staff_duty_table']

    def validate(self, model):
        return model.__module__.startswith(self.MODEL_PREFIXS) and model._meta.db_table in self.TABLES

    def db_for_read(self, model, **hints):
        if self.validate(model):
            return getattr(settings, 'MESSAGE_SLAVE_DB_NAME', settings.MESSAGE_DB_NAME)
        return None

    def db_for_write(self, model, **hints):
        if self.validate(model):
            return settings.MESSAGE_DB_NAME
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if self.validate(obj1) and self.validate(obj2):
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return True


class DorisRouter(object):

    def db_for_read(self, model, **hints):
        if model._meta.app_label == "doris":
            return 'doris'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == "doris":
            return 'doris'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'doris' or \
                obj2._meta.app_label == 'doris':
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'doris':
            return db == 'doris'
        return None


class DiagnosisRouter(object):

    def db_for_read(self, model, **hints):
        if model._meta.app_label == "diagnosis":
            return 'diagnosis'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == "diagnosis":
            return 'diagnosis'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'diagnosis' or \
                obj2._meta.app_label == 'diagnosis':
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'diagnosis':
            return db == 'diagnosis'
        return None


class PoseidonRouter(object):

    def db_for_read(self, model, **hints):
        if model._meta.app_label == "poseidon":
            return 'poseidon'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == "poseidon":
            return 'poseidon'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'poseidon' or \
                obj2._meta.app_label == 'poseidon':
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'poseidon':
            return db == 'poseidon'
        return None


class PlasticRouter(object):

    def db_for_read(self, model, **hints):
        if model._meta.app_label == "plastic":
            return 'default'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == "plastic":
            return 'default'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'plastic' or \
                obj2._meta.app_label == 'plastic':
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'plastic':
            return db == 'default'
        return None
