from bson import ObjectId

from nawah.base_module import BaseModule
from nawah.classes import ATTR, METHOD, NAWAH_DOC, PERM
from nawah.enums import Event
from nawah.registry import Registry


class Diff(BaseModule):
    '''`Diff` module provides data type and controller for `Diff Workflow`. It is meant for use by internal calls only.'''

    collection = 'diff'
    attrs = {
        'user': ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'),
        'module': ATTR.STR(desc='Name of the module the original doc is part of.'),
        'doc': ATTR.ID(desc='`_id` of the original doc.'),
        'vars': ATTR.KV_DICT(
            desc='Key-value `dict` containing all attrs that have been updated from the original doc.',
            key=ATTR.STR(),
            val=ATTR.ANY(),
        ),
        'remarks': ATTR.STR(
            desc='Human-readable remarks of the doc. This is introduced to allow developers to add log messages to diff docs.'
        ),
        'create_time': ATTR.DATETIME(
            desc='Python `datetime` ISO format of the doc creation.'
        ),
    }
    defaults = {'doc': None, 'remarks': ''}
    methods = {
        'read': METHOD(permissions=[PERM(privilege='read')]),
        'create': METHOD(permissions=[PERM(privilege='__sys')]),
        'delete': METHOD(permissions=[PERM(privilege='delete')]),
    }

    async def pre_create(self, skip_events, env, query, doc, payload):
        # [DOC] format Doc Oper with prefixed underscores to avoid data errors
        doc = self.format_doc_oper(doc=doc)
        # [DOC] Detect non-_id update query:
        if '_id' not in query:
            results = await Registry.module(doc['module']).read(
                skip_events=[Event.PERM], env=env, query=query
            )
            if results.args.count > 1:
                query.append({'_id': {'$in': [doc._id for doc in results.args.docs]}})
            elif results.args.count == 1:
                query.append({'_id': results.args.docs[0]._id})
            else:
                raise self.exception(
                    status=400, msg='No update docs matched.', args={'code': 'NO_MATCH'}
                )
        if '_id' in query and type(query['_id'][0]) == list:
            for i in range(len(query['_id'][0]) - 1):
                await self.create(
                    skip_events=[Event.PERM],
                    env=env,
                    query=[{'_id': query['_id'][0][i]}],
                    doc=doc,
                )
            query['_id'][0] = query['_id'][0][-1]
        doc['doc'] = ObjectId(query['_id'][0])
        return (skip_events, env, query, doc, payload)

    def format_doc_oper(self, *, doc: NAWAH_DOC):
        shadow_doc: NAWAH_DOC = {}
        for attr in doc.keys():
            if attr[0] == '$':
                shadow_doc[f'__{attr}'] = doc[attr]
            elif type(doc[attr]) == dict:
                shadow_doc[attr] = self.format_doc_oper(doc=doc[attr])
            else:
                shadow_doc[attr] = doc[attr]
        return shadow_doc
