from nawah.base_module import BaseModule
from nawah.classes import ATTR, EXTN, METHOD, PERM, InvalidAttrException
from nawah.config import Config
from nawah.enums import Event
from nawah.utils import generate_dynamic_attr, validate_doc


async def attr_extn_val(
    *,
    mode,
    attr_name,
    attr_type,
    attr_val,
    skip_events,
    env,
    query,
    doc,
    scope,
):
    if type(doc['val']) == dict and '__extn' in doc['val'].keys():
        return {
            '__extn': EXTN(
                module=doc['val']['__extn']['__module'],
                attrs=doc['val']['__extn']['__attrs'],
                force=doc['val']['__extn']['__force'],
            ),
            '__val': doc['val']['__extn']['__val'],
        }

    raise InvalidAttrException(
        attr_name=attr_name, attr_type=attr_type, val_type=type(doc['val'])
    )


async def attr_query_mod_type(
    *,
    mode,
    attr_name,
    attr_type,
    attr_val,
    skip_events,
    env,
    query,
    doc,
    scope,
):
    if 'type' not in query or query['type'][0] == 'user_sys':
        raise InvalidAttrException(
            attr_name='type',
            attr_type=ATTR.LITERAL(literal=['global', 'user']),
            val_type=str,
        )


class Setting(BaseModule):
    '''`Setting` module module provides data type and controller for settings in Nawah eco-system. This is used by `User` module tp provide additional user-wise settings. It also allows for global-typed settings.'''

    collection = 'settings'
    attrs = {
        'user': ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'),
        'var': ATTR.STR(
            desc='Name of the setting. This is unique for every `user` in the module.'
        ),
        'val': ATTR.ANY(desc='Value of the setting.'),
        'val_type': ATTR.DYNAMIC_ATTR(),
        'type': ATTR.LITERAL(
            desc='Type of the setting. This sets whether setting is global, or belong to user, and whether use can update it or not.',
            literal=['global', 'user', 'user_sys'],
        ),
    }
    diff = True
    unique_attrs = [('user', 'var', 'type')]
    extns = {
        'val': ATTR.TYPE(type=attr_extn_val),
    }
    methods = {
        'read': METHOD(
            permissions=[
                PERM(privilege='admin', query_mod={'$limit': 1}),
                PERM(
                    privilege='read',
                    query_mod={
                        'user': '$__user',
                        # 'type': ATTR.TYPE(type=attr_query_mod_type),
                        '$limit': 1,
                    },
                ),
            ],
            query_args=[
                {
                    '_id': ATTR.ID(),
                    'type': ATTR.LITERAL(literal=['global', 'user', 'user_sys']),
                },
                {
                    'var': ATTR.STR(),
                    'type': ATTR.LITERAL(literal=['global']),
                },
                {
                    'var': ATTR.STR(),
                    'user': ATTR.ID(),
                    'type': ATTR.LITERAL(literal=['user', 'user_sys']),
                },
            ],
        ),
        'create': METHOD(
            permissions=[
                PERM(privilege='admin'),
                PERM(privilege='create', doc_mod={'type': 'user'}),
            ]
        ),
        'update': METHOD(
            permissions=[
                PERM(privilege='admin', query_mod={'$limit': 1}),
                PERM(
                    privilege='update',
                    query_mod={'type': 'user', 'user': '$__user', '$limit': 1},
                    doc_mod={'var': None, 'val_type': None, 'type': None},
                ),
            ],
            query_args=[
                {
                    '_id': ATTR.ID(),
                    'type': ATTR.LITERAL(literal=['global', 'user', 'user_sys']),
                },
                {
                    'var': ATTR.STR(),
                    'type': ATTR.LITERAL(literal=['global']),
                },
                {
                    'var': ATTR.STR(),
                    'user': ATTR.ID(),
                    'type': ATTR.LITERAL(literal=['user', 'user_sys']),
                },
            ],
        ),
        'delete': METHOD(
            permissions=[PERM(privilege='admin', query_mod={'$limit': 1})],
            query_args=[{'_id': ATTR.ID()}, {'var': ATTR.STR()}],
        ),
        'retrieve_file': METHOD(
            permissions=[PERM(privilege='*', query_mod={'type': 'global'})],
            get_method=True,
        ),
    }

    async def on_create(self, results, skip_events, env, query, doc, payload):
        if doc['type'] in ['user', 'user_sys']:
            if (
                doc['user'] == env['session']['user']['_id']
                and doc['var'] in Config.user_doc_settings
            ):
                env['session']['user'][doc['var']] = doc['val']
        return (results, skip_events, env, query, doc, payload)

    async def pre_update(self, skip_events, env, query, doc, payload):
        for attr in doc.keys():
            if attr == 'val' or attr.startswith('val.'):
                val_attr = attr
                break
        else:
            raise self.exception(
                status=400,
                msg='Could not match doc with any of the required doc_args. Failed sets:[\'val\': Missing]',
                args={'code': 'INVALID_DOC'},
            )

        setting_results = await self.read(
            skip_events=[Event.PERM], env=env, query=query
        )
        if not setting_results['args']['count']:
            raise self.exception(
                status=400, msg='Invalid Setting doc', args={'code': 'INVALID_SETTING'}
            )

        setting = setting_results['args']['docs'][0]
        # [DOC] Attempt to validate val against Setting val_type
        try:
            exception_raised: Exception = None
            setting_val_type, _ = generate_dynamic_attr(
                dynamic_attr=setting['val_type']
            )
            await validate_doc(
                mode='update',
                doc=doc,
                attrs={'val': setting_val_type},
                skip_events=skip_events,
                env=env,
                query=query,
            )
        except Exception as e:
            exception_raised = e

        if exception_raised or doc[val_attr] == None:
            raise self.exception(
                status=400,
                msg=f'Invalid value for for Setting doc of type \'{type(doc[val_attr])}\' with required type \'{setting["val_type"]}\'',
                args={'code': 'INVALID_ATTR'},
            )

        return (skip_events, env, query, doc, payload)

    async def on_update(self, results, skip_events, env, query, doc, payload):
        # [TODO] Update according to the changes of Doc Opers
        try:
            if (
                query['type'][0] in ['user', 'user_sys']
                and query['user'][0] == env['session']['user']['_id']
                and query['var'][0] in Config.user_doc_settings
            ):
                if type(doc['val']) == dict and '$add' in doc['val'].keys():
                    env['session']['user'][query['var'][0]] += doc['val']['$add']
                elif type(doc['val']) == dict and '$multiply' in doc['val'].keys():
                    env['session']['user'][query['var'][0]] *= doc['val']['$multiply']
                elif type(doc['val']) == dict and '$append' in doc['val'].keys():
                    env['session']['user'][query['var'][0]].append(
                        doc['val']['$append']
                    )
                elif type(doc['val']) == dict and '$set_index' in doc['val'].keys():
                    env['session']['user'][query['var'][0]][doc['val']['$index']] = doc[
                        'val'
                    ]['$set_index']
                elif type(doc['val']) == dict and '$del_val' in doc['val'].keys():
                    for val in doc['val']['$del_val']:
                        env['session']['user'][query['var'][0]].remove(val)
                elif type(doc['val']) == dict and '$del_index' in doc['val'].keys():
                    del env['session']['user'][query['var'][0]][doc['val']['$index']]
                else:
                    env['session']['user'][query['var'][0]] = doc['val']
        except:
            pass
        return (results, skip_events, env, query, doc, payload)
