# Path: django_sharding_library/exceptions.py
# class DjangoShardingException(Exception):
#     pass
# 
# class InvalidMigrationException(DjangoShardingException):
#     pass
# 
# Path: django_sharding_library/utils.py
# def is_model_class_on_database(model, database):
#     specific_database = getattr(model, 'django_sharding__database', None)
#     is_sharded = getattr(model, 'django_sharding__is_sharded', False)
# 
#     if specific_database and is_sharded:
#         raise DjangoShardingException('Model marked as both sharded and on a single database, unable to determine where to run migrations for {}.'.format(model.__class__.__name__))
# 
#     if specific_database:
#         return getattr(model, 'django_sharding__database') == database
# 
#     if is_sharded:
#         shard_group = getattr(model, 'django_sharding__shard_group', None)
#         if shard_group:
#             return settings.DATABASES[database]['SHARD_GROUP'] == shard_group
#         raise DjangoShardingException("Unable to determine what database the model is on as the shard group of {} is unknown.".format(model.__class__.__name__))
# 
#     return database == "default"
# 
# def get_database_for_model_instance(instance):
#     if instance._state.db:
#         return instance._state.db
# 
#     model = instance._meta.model
#     possible_databases = get_possible_databases_for_model(model=model)
#     if len(possible_databases) == 1:
#         return possible_databases[0]
#     elif len(possible_databases) == 0:
#         pass
#     else:
#         return instance.get_shard()
# 
#     raise DjangoShardingException("Unable to deduce datbase for model instance")
# 
# def get_possible_databases_for_model(model):
#     return [
#         database for database in settings.DATABASES
#         if is_model_class_on_database(model=model, database=database)
#     ]


from django.apps import apps
from django.conf import settings
from django_sharding_library.exceptions import DjangoShardingException, InvalidMigrationException
from django_sharding_library.utils import (
    is_model_class_on_database,
    get_database_for_model_instance,
    get_possible_databases_for_model,
)


class ShardedRouter(object):
    """
    A router that is shard-aware and will prevent running migrations on
    the wrong database as well as infer, when possible, which database to
    read or write from.
    """

    def get_shard_for_instance(self, instance):
        return instance._state.db or instance.get_shard()

    def get_read_db_routing_strategy(self, shard_group):
        app_config_app_label = getattr(settings, 'DJANGO_SHARDING_SETTINGS', {}).get('APP_CONFIG_APP', 'django_sharding')
        return apps.get_app_config(app_config_app_label).get_routing_strategy(shard_group)

    def _get_shard(self, model, **hints):
        shard = None
        #####
        #
        # This is setup as multiple IF statements on purpose. If any attempt to get a shard fails, the function that
        # tried to get the shard should return None that way the NEXT possible attempt to grab a shard can be run.
        # In this way, future ways to automatically get the correct shard can be added, and at any point in the logic,
        # and the first one to pick a valid shard will return the valid shard.
        #
        # Always return None if no valid shard was found so the default Django router will use the using() database if
        # these all fail.
        #
        #####
        if hints.get("instance", None):

# Path: django_sharding_library/exceptions.py
# class DjangoShardingException(Exception):
#     pass
# 
# class InvalidMigrationException(DjangoShardingException):
#     pass
# 
# Path: django_sharding_library/utils.py
# def is_model_class_on_database(model, database):
#     specific_database = getattr(model, 'django_sharding__database', None)
#     is_sharded = getattr(model, 'django_sharding__is_sharded', False)
# 
#     if specific_database and is_sharded:
#         raise DjangoShardingException('Model marked as both sharded and on a single database, unable to determine where to run migrations for {}.'.format(model.__class__.__name__))
# 
#     if specific_database:
#         return getattr(model, 'django_sharding__database') == database
# 
#     if is_sharded:
#         shard_group = getattr(model, 'django_sharding__shard_group', None)
#         if shard_group:
#             return settings.DATABASES[database]['SHARD_GROUP'] == shard_group
#         raise DjangoShardingException("Unable to determine what database the model is on as the shard group of {} is unknown.".format(model.__class__.__name__))
# 
#     return database == "default"
# 
# def get_database_for_model_instance(instance):
#     if instance._state.db:
#         return instance._state.db
# 
#     model = instance._meta.model
#     possible_databases = get_possible_databases_for_model(model=model)
#     if len(possible_databases) == 1:
#         return possible_databases[0]
#     elif len(possible_databases) == 0:
#         pass
#     else:
#         return instance.get_shard()
# 
#     raise DjangoShardingException("Unable to deduce datbase for model instance")
# 
# def get_possible_databases_for_model(model):
#     return [
#         database for database in settings.DATABASES
#         if is_model_class_on_database(model=model, database=database)
#     ]


# Path: django_sharding_library/router.py
from django.apps import apps
from django.conf import settings
from django_sharding_library.exceptions import DjangoShardingException, InvalidMigrationException
from django_sharding_library.utils import (
    is_model_class_on_database,
    get_database_for_model_instance,
    get_possible_databases_for_model,
)



class ShardedRouter(object):
    """
    A router that is shard-aware and will prevent running migrations on
    the wrong database as well as infer, when possible, which database to
    read or write from.
    """

    def get_shard_for_instance(self, instance):
        return instance._state.db or instance.get_shard()

    def get_read_db_routing_strategy(self, shard_group):
        app_config_app_label = getattr(settings, 'DJANGO_SHARDING_SETTINGS', {}).get('APP_CONFIG_APP', 'django_sharding')
        return apps.get_app_config(app_config_app_label).get_routing_strategy(shard_group)

    def _get_shard(self, model, **hints):
        shard = None
        #####
        #
        # This is setup as multiple IF statements on purpose. If any attempt to get a shard fails, the function that
        # tried to get the shard should return None that way the NEXT possible attempt to grab a shard can be run.
        # In this way, future ways to automatically get the correct shard can be added, and at any point in the logic,
        # and the first one to pick a valid shard will return the valid shard.
        #
        # Always return None if no valid shard was found so the default Django router will use the using() database if
        # these all fail.
        #
        #####
        if hints.get("instance", None):

            shard = get_database_for_model_instance(instance=hints["instance"])