from nawah.base_module import BaseModule
from nawah.enums import Event, NAWAH_VALUES
from nawah.classes import ATTR, PERM, EXTN, METHOD
from nawah.config import Config
from nawah.registry import Registry
from nawah.utils import validate_attr, encode_attr_type

from bson import ObjectId

import copy


class User(BaseModule):
	'''`User` module provides data type and controller for users in Nawah eco-system. The permissions of the module methods are designed to be as secure for exposed calls, and as flexible for privileged-access.'''

	collection = 'users'
	attrs = {
		'name': ATTR.LOCALE(desc='Name of the user as `LOCALE`.'),
		'locale': ATTR.LOCALES(desc='Default locale of the user.'),
		'create_time': ATTR.DATETIME(
			desc='Python `datetime` ISO format of the doc creation.'
		),
		'login_time': ATTR.DATETIME(desc='Python `datetime` ISO format of the last login.'),
		'groups': ATTR.LIST(
			desc='List of `_id` for every group the user is member of.',
			list=[ATTR.ID(desc='`_id` of Group doc the user is member of.')],
		),
		'privileges': ATTR.KV_DICT(
			desc='Privileges of the user. These privileges are always available to the user regardless of whether groups user is part of have them or not.',
			key=ATTR.STR(),
			val=ATTR.LIST(list=[ATTR.STR()]),
		),
		'status': ATTR.LITERAL(
			desc='Status of the user to determine whether user has access to the app or not.',
			literal=['active', 'banned', 'deleted', 'disabled_password'],
		),
	}
	defaults = {'login_time': None, 'status': 'active', 'groups': [], 'privileges': {}}
	unique_attrs = []
	methods = {
		'read': METHOD(
			permissions=[
				PERM(privilege='admin'),
				PERM(privilege='read', query_mod={'_id': '$__user'}),
			]
		),
		'create': METHOD(permissions=[PERM(privilege='admin')]),
		'update': METHOD(
			permissions=[
				PERM(privilege='admin', doc_mod={'groups': None}),
				PERM(
					privilege='update',
					query_mod={'_id': '$__user'},
					doc_mod={'groups': None, 'privileges': None},
				),
			],
			query_args={'_id': ATTR.ID()},
		),
		'delete': METHOD(
			permissions=[
				PERM(privilege='admin'),
				PERM(privilege='delete', query_mod={'_id': '$__user'}),
			],
			query_args={'_id': ATTR.ID()},
		),
		'read_privileges': METHOD(
			permissions=[
				PERM(privilege='admin'),
				PERM(privilege='read', query_mod={'_id': '$__user'}),
			],
			query_args={'_id': ATTR.ID()},
		),
		'add_group': METHOD(
			permissions=[PERM(privilege='admin')],
			query_args={'_id': ATTR.ID()},
			doc_args=[{'group': ATTR.ID()}, {'group': ATTR.LIST(list=[ATTR.ID()])}],
		),
		'delete_group': METHOD(
			permissions=[PERM(privilege='admin')],
			query_args={'_id': ATTR.ID(), 'group': ATTR.ID()},
		),
		'retrieve_file': METHOD(permissions=[PERM(privilege='__sys')], get_method=True),
		'create_file': METHOD(permissions=[PERM(privilege='__sys')]),
		'delete_file': METHOD(permissions=[PERM(privilege='__sys')]),
	}

	async def on_read(self, results, skip_events, env, query, doc, payload):
		for i in range(len(results['docs'])):
			user = results['docs'][i]
			for auth_attr in Config.user_attrs.keys():
				del user[f'{auth_attr}_hash']
			if len(Config.user_doc_settings):
				setting_results = await Registry.module('setting').read(
					skip_events=[Event.PERM, Event.ARGS],
					env=env,
					query=[{'user': user._id, 'var': {'$in': Config.user_doc_settings}}],
				)
				user_doc_settings = copy.copy(Config.user_doc_settings)
				if setting_results.args.count:
					for setting_doc in setting_results.args.docs:
						user_doc_settings.remove(setting_doc['var'])
						user[setting_doc['var']] = setting_doc['val']
				# [DOC] Forward-compatibility: If user was created before presence of any user_doc_settings, add them with default value
				for setting_attr in user_doc_settings:
					user[setting_attr] = Config.user_settings[setting_attr].default
					# [DOC] Set NAWAH_VALUES.NONE_VALUE to None if it was default
					if user[setting_attr] == NAWAH_VALUES.NONE_VALUE:
						user[setting_attr] = None
		return (results, skip_events, env, query, doc, payload)

	async def pre_create(self, skip_events, env, query, doc, payload):
		if Event.ARGS not in skip_events:
			doc['groups'] = [ObjectId('f00000000000000000000013')]
		user_settings = {}
		for attr in Config.user_settings.keys():
			if Config.user_settings[attr].type == 'user_sys':
				user_settings[attr] = copy.deepcopy(Config.user_settings[attr].default)
			else:
				if attr in doc.keys():
					try:
						await validate_attr(
							mode='create',
							attr_name=attr,
							attr_type=Config.user_settings[attr].val_type,
							attr_val=doc[attr],
						)
						user_settings[attr] = doc[attr]
					except:
						raise self.exception(
							status=400,
							msg=f'Invalid settings attr \'{attr}\' for \'create\' request on module \'CORE_USER\'',
							args={'code': 'INVALID_ATTR'},
						)

				else:
					if Config.user_settings[attr].default == NAWAH_VALUES.NONE_VALUE:
						raise self.exception(
							status=400,
							msg=f'Missing settings attr \'{attr}\' for \'create\' request on module \'CORE_USER\'',
							args={'code': 'MISSING_ATTR'},
						)

					else:
						user_settings[attr] = copy.deepcopy(Config.user_settings[attr].default)
		payload['user_settings'] = user_settings
		return (skip_events, env, query, doc, payload)

	async def on_create(self, results, skip_events, env, query, doc, payload):
		if 'user_settings' in payload.keys():
			for setting in payload['user_settings'].keys():
				setting_results = await Registry.module('setting').create(
					skip_events=[Event.PERM, Event.ARGS],
					env=env,
					doc={
						'user': results['docs'][0]._id,
						'var': setting,
						'val_type': encode_attr_type(attr_type=Config.user_settings[setting].val_type),
						'val': payload['user_settings'][setting],
						'type': Config.user_settings[setting].type,
					},
				)
				if setting_results.status != 200:
					return setting_results
		return (results, skip_events, env, query, doc, payload)

	async def read_privileges(self, skip_events=[], env={}, query=[], doc={}):
		# [DOC] Confirm _id is valid
		results = await self.read(
			skip_events=[Event.PERM], env=env, query=[{'_id': query['_id'][0]}]
		)
		if not results.args.count:
			raise self.exception(
				status=400, msg='User is invalid.', args={'code': 'INVALID_USER'}
			)
		user = results.args.docs[0]
		for group in user.groups:
			group_results = await Registry.module('group').read(
				skip_events=[Event.PERM], env=env, query=[{'_id': group}]
			)
			group = group_results.args.docs[0]
			for privilege in group.privileges.keys():
				if privilege not in user.privileges.keys():
					user.privileges[privilege] = []
				for i in range(len(group.privileges[privilege])):
					if group.privileges[privilege][i] not in user.privileges[privilege]:
						user.privileges[privilege].append(group.privileges[privilege][i])
		return results

	async def add_group(self, skip_events=[], env={}, query=[], doc={}):
		# [DOC] Check for list group attr
		if type(doc['group']) == list:
			for i in range(0, len(doc['group']) - 1):
				await self.add_group(
					skip_events=skip_events,
					env=env,
					query=query,
					doc={'group': doc['group'][i]},
				)
			doc['group'] = doc['group'][-1]
		# [DOC] Confirm all basic args are provided
		doc['group'] = ObjectId(doc['group'])
		# [DOC] Confirm group is valid
		results = await Registry.module('group').read(
			skip_events=[Event.PERM], env=env, query=[{'_id': doc['group']}]
		)
		if not results.args.count:
			raise self.exception(
				status=400, msg='Group is invalid.', args={'code': 'INVALID_GROUP'}
			)
		# [DOC] Get user details
		results = await self.read(skip_events=[Event.PERM], env=env, query=query)
		if not results.args.count:
			raise self.exception(
				status=400, msg='User is invalid.', args={'code': 'INVALID_USER'}
			)
		user = results.args.docs[0]
		# [DOC] Confirm group was not added before
		if doc['group'] in user.groups:
			raise self.exception(
				status=400,
				msg='User is already a member of the group.',
				args={'code': 'GROUP_ADDED'},
			)
		user.groups.append(doc['group'])
		# [DOC] Update the user
		results = await self.update(
			skip_events=[Event.PERM], env=env, query=query, doc={'groups': user.groups}
		)
		# [DOC] if update fails, return update results
		if results.status != 200:
			return results
		# [DOC] Check if the updated User doc belongs to current session and update it
		if env['session'].user._id == user._id:
			user_results = await self.read_privileges(
				skip_events=[Event.PERM], env=env, query=[{'_id': user._id}]
			)
			env['session']['user'] = user_results.args.docs[0]

		return results

	async def delete_group(self, skip_events=[], env={}, query=[], doc={}):
		# [DOC] Confirm group is valid
		results = await Registry.module('group').read(
			skip_events=[Event.PERM], env=env, query=[{'_id': query['group'][0]}]
		)
		if not results.args.count:
			raise self.exception(
				status=400, msg='Group is invalid.', args={'code': 'INVALID_GROUP'}
			)
		# [DOC] Get user details
		results = await self.read(
			skip_events=[Event.PERM], env=env, query=[{'_id': query['_id'][0]}]
		)
		if not results.args.count:
			raise self.exception(
				status=400, msg='User is invalid.', args={'code': 'INVALID_USER'}
			)
		user = results.args.docs[0]
		# [DOC] Confirm group was not added before
		if query['group'][0] not in user.groups:
			raise self.exception(
				status=400,
				msg='User is not a member of the group.',
				args={'code': 'GROUP_NOT_ADDED'},
			)
		# [DOC] Update the user
		results = await self.update(
			skip_events=[Event.PERM],
			env=env,
			query=[{'_id': query['_id'][0]}],
			doc={'groups': {'$del_val': [query['group'][0]]}},
		)
		# [DOC] if update fails, return update results
		if results.status != 200:
			return results
		# [DOC] Check if the updated User doc belongs to current session and update it
		if env['session'].user._id == user._id:
			user_results = await self.read_privileges(
				skip_events=[Event.PERM], env=env, query=[{'_id': user._id}]
			)
			env['session']['user'] = user_results.args.docs[0]

		return results
