# -*- coding: utf-8 -*-
import logging

from collections import defaultdict
from django.conf import settings
from django.core.cache import caches
from django.contrib.auth.models import Group
from django.db import transaction
from django.db.utils import IntegrityError
from guardian.shortcuts import assign_perm, remove_perm
from requests.exceptions import RequestException

from events.accounts.models import User, GrantTransitionInfo
from events.accounts.utils import get_external_user_uid_by_login
from events.common_app.models import PermissionLog
from events.idm.client import IdmClient
from events.staff.new_client import StaffPersonClient
from events.surveyme.models import Survey, SurveyGroup


logger = logging.getLogger(__name__)


def find_users_by_login(*logins):
    if not logins:
        return []
    client = StaffPersonClient()
    return list(client.list(params={'login': ','.join(logins)}, fields='uid,login'))


def find_users_by_uid(*uids):
    if not uids:
        return []
    client = StaffPersonClient()
    return list(client.list(params={'uid': ','.join(uids)}, fields='uid,login'))


def find_user(uid, login=None, first_name=None):
    user = None
    try:
        if not user and uid:
            user = User.objects.get(uid=uid)
    except User.DoesNotExist:
        pass

    try:
        if not user and login:
            user = User.objects.get(username=login)
    except User.DoesNotExist:
        pass

    if user and first_name and user.first_name != first_name:
        user.first_name = first_name
        user.save(update_fields=['first_name'])

    return user


def get_user_args(uid, login):
    assert uid or login, 'User uid or login should be known'

    if not uid:
        users = find_users_by_login(login)
        if not users:
            raise ValueError(f'User with login {login} does not exist')
        user = users[0]
        return user['uid'], user['login']

    if not login:
        users = find_users_by_uid(uid)
        if not users:
            raise ValueError(f'User with uid {uid} does not exist')
        user = users[0]
        return user['uid'], user['login']

    return uid, login


def create_user(uid, login=None, first_name=None):
    uid, login = get_user_args(uid, login)
    email = f'{login}@yandex.ru' if settings.IS_BUSINESS_SITE else f'{login}@yandex-team.ru'
    first_name = first_name or ''
    user = User(uid=uid, username=login, email=email, first_name=first_name)
    try:
        with transaction.atomic():
            user.save()
    except IntegrityError:
        user = None
    return user or find_user(uid, first_name=first_name)


def get_user(uid, login=None, first_name=None, create=True):
    user = find_user(uid, login=login, first_name=first_name)
    if create and not user:
        user = create_user(uid, login, first_name)
    return user


def get_group_id(group_name):
    if group_name.startswith('group:'):
        try:
            return int(group_name[6:])
        except ValueError:
            pass


def find_group(group_id):
    try:
        return Group.objects.get(name=f'group:{group_id}')
    except Group.DoesNotExist:
        pass


def create_group(group_id):
    group = Group(name=f'group:{group_id}')
    try:
        with transaction.atomic():
            group.save()
    except IntegrityError:
        group = None
    return group or find_group(group_id)


def get_group(group_id, create=True):
    group = find_group(group_id)
    if create and not group:
        group = create_group(group_id)
    return group


def get_chief_from_staff(uid):
    client = StaffPersonClient()
    person = client.get(staff_id=None, params={'uid': uid}, fields='chief.uid,chief.login')
    if person:
        return {
            'uid': person['chief']['uid'],
            'login': person['chief']['login'],
        }


def get_chief(uid):
    cache = caches['default']
    key = f'staff:chief:{uid}'
    person = cache.get(key)
    if person is None:
        person = get_chief_from_staff(uid)
        cache.set(key, person or {})
    if person and person.get('login') != 'volozh':
        return get_user(person.get('uid'), login=person.get('login'))


def get_survey(object_pk):
    try:
        return Survey.objects.get(pk=object_pk)
    except Survey.DoesNotExist:
        pass


def get_survey_group(object_pk):
    try:
        return SurveyGroup.objects.get(pk=object_pk)
    except SurveyGroup.DoesNotExist:
        pass


def assign_perm_with_log(request_user, role_name, user_or_group, obj=None):
    params = {
        'request_user_id': request_user.pk,
        'role_name': role_name,
        'change_type': PermissionLog.ASSIGN,
    }
    if obj:
        params['object_pk'] = str(obj.pk)
    if isinstance(user_or_group, User):
        params['user_id'] = user_or_group.pk
    if isinstance(user_or_group, Group):
        params['group_id'] = user_or_group.pk
    PermissionLog.objects.create(**params)
    if obj:
        return assign_perm(role_name, user_or_group, obj)
    return True


def remove_perm_with_log(request_user, role_name, user_or_group, obj=None):
    params = {
        'request_user_id': request_user.pk,
        'role_name': role_name,
        'change_type': PermissionLog.REMOVE,
    }
    if obj:
        params['object_pk'] = str(obj.pk)
    if isinstance(user_or_group, User):
        params['user_id'] = user_or_group.pk
    if isinstance(user_or_group, Group):
        params['group_id'] = user_or_group.pk
    PermissionLog.objects.create(**params)
    if obj:
        return remove_perm(role_name, user_or_group, obj)
    return True


def save_permission_log(request_user, role_name, change_type, users=None, groups=None, object_pk=None):
    logs = []
    if users:
        for user in users:
            logs.append(PermissionLog(
                request_user=request_user, role_name=role_name,
                change_type=change_type, user=user, object_pk=object_pk,
            ))
    if groups:
        for group in groups:
            logs.append(PermissionLog(
                request_user=request_user, role_name=role_name,
                change_type=change_type, group=group, object_pk=object_pk,
            ))
    if logs:
        PermissionLog.objects.bulk_create(logs)


def add_user_role(request_user, role, uid, login=None, object_pk=None, first_name=None):
    result = None
    user = get_user(uid, login=login, first_name=first_name)
    if role == settings.ROLE_SUPERUSER:
        user.is_superuser = True
        user.save(update_fields=['is_superuser'])
        result = assign_perm_with_log(request_user, role, user)
    elif role == settings.ROLE_SUPPORT:
        user.is_staff = True
        user.save(update_fields=['is_staff'])
        result = assign_perm_with_log(request_user, role, user)
    elif role in (settings.ROLE_FORM_MANAGER, settings.ROLE_FORM_FILEDOWNLOAD):
        survey = get_survey(object_pk)
        if survey:
            result = assign_perm_with_log(request_user, role, user, survey)
    elif role in (settings.ROLE_GROUP_MANAGER, settings.ROLE_GROUP_FILEDOWNLOAD):
        survey_group = get_survey_group(object_pk)
        if survey_group:
            result = assign_perm_with_log(request_user, role, user, survey_group)
    return bool(result)


def add_group_role(request_user, role, group, object_pk=None):
    result = None
    group_obj = get_group(group)
    if role in (settings.ROLE_FORM_MANAGER, settings.ROLE_FORM_FILEDOWNLOAD):
        survey = get_survey(object_pk)
        if survey:
            result = assign_perm_with_log(request_user, role, group_obj, survey)
    elif role in (settings.ROLE_GROUP_MANAGER, settings.ROLE_GROUP_FILEDOWNLOAD):
        survey_group = get_survey_group(object_pk)
        if survey_group:
            result = assign_perm_with_log(request_user, role, group_obj, survey_group)
    return bool(result)


def remove_user_role(request_user, role, uid, login=None, object_pk=None, fired=None):
    result = None
    user = find_user(uid, login=login)
    if not user:
        return False
    if role == settings.ROLE_SUPERUSER:
        user.is_superuser = False
        user.save(update_fields=['is_superuser'])
        result = remove_perm_with_log(request_user, role, user)
    elif role == settings.ROLE_SUPPORT:
        user.is_staff = False
        user.save(update_fields=['is_staff'])
        result = remove_perm_with_log(request_user, role, user)
    elif role in (settings.ROLE_FORM_MANAGER, settings.ROLE_FORM_FILEDOWNLOAD):
        survey = get_survey(object_pk)
        if survey:
            result = remove_perm_with_log(request_user, role, user, survey)
            if fired and survey.user_id == user.pk:
                # при удалении автора, добавляем в доступы его руководителя
                chief = get_chief(uid)
                if chief:
                    request_transition_role(request_user, role, chief, user, survey)
    elif role in (settings.ROLE_GROUP_MANAGER, settings.ROLE_GROUP_FILEDOWNLOAD):
        survey_group = get_survey_group(object_pk)
        if survey_group:
            result = remove_perm_with_log(request_user, role, user, survey_group)
            if fired and survey_group.user_id == user.pk:
                # при удалении автора, добавляем в доступы его руководителя
                chief = get_chief(uid)
                if chief:
                    request_transition_role(request_user, role, chief, user, survey_group)
    return bool(result)


def request_transition_role(request_user, role, new_user, old_user, obj):
    if new_user.has_perm(role, obj):
        return
    assign_perm_with_log(request_user, role, new_user, obj)
    GrantTransitionInfo.objects.create(new_user=new_user, old_user=old_user, content_object=obj)
    IdmUtils().request_roles(role, users=[new_user.username], object_pk=str(obj.pk))


def remove_group_role(request_user, role, group, object_pk=None):
    result = None
    group_obj = find_group(group)
    if not group_obj:
        return False
    if role in (settings.ROLE_FORM_MANAGER, settings.ROLE_FORM_FILEDOWNLOAD):
        survey = get_survey(object_pk)
        if survey:
            result = remove_perm_with_log(request_user, role, group_obj, survey)
    elif role in (settings.ROLE_GROUP_MANAGER, settings.ROLE_GROUP_FILEDOWNLOAD):
        survey_group = get_survey_group(object_pk)
        if survey_group:
            result = remove_perm_with_log(request_user, role, group_obj, survey_group)
    return bool(result)


def add_role(request_user, role, uid, group, login, object_pk, passport_login):
    first_name = None
    if settings.IS_BUSINESS_SITE:
        uid = get_external_user_uid_by_login(passport_login)
        login, first_name = passport_login, login
        if not uid:
            return False
    if uid or login:
        return add_user_role(request_user, role, uid, login=login, object_pk=object_pk, first_name=first_name)
    elif group:
        return add_group_role(request_user, role, group, object_pk=object_pk)
    return False


def remove_role(request_user, role, uid, group, login, object_pk, passport_login, fired):
    if settings.IS_BUSINESS_SITE:
        if not passport_login:
            passport_login = f'yndx-{login}'
        uid = get_external_user_uid_by_login(passport_login)
        login = passport_login
        if not uid:
            return False
    if uid or login:
        return remove_user_role(request_user, role, uid, login=login, object_pk=object_pk, fired=fired)
    elif group:
        return remove_group_role(request_user, role, group, object_pk=object_pk)
    return False


def get_users_from_staff(logins, create=True):
    users = []
    for it in find_users_by_login(*logins):
        user = get_user(it['uid'], login=it['login'], create=create)
        if user:
            users.append(user)
    return users


def get_users(logins, create=True):
    users = list(User.objects.filter(username__in=logins))
    found_logins = [it.username for it in users]
    not_found_logins = set(logins).difference(found_logins)
    if not_found_logins and create:
        users.extend(get_users_from_staff(not_found_logins, create=create))
    return users


def add_batch_memberships(data):
    groups = defaultdict(list)
    logins = set()
    for it in data or []:
        groups[it['group']].append(it['login'])
        logins.add(it['login'])

    users = {
        user.username: user
        for user in get_users(logins, create=True)
    }
    for group_id in groups:
        group = get_group(group_id, create=True)
        if group:
            for login in groups[group_id]:
                user = users.get(login)
                if user:
                    group.user_set.add(user)


def remove_batch_memberships(data):
    groups = defaultdict(list)
    logins = set()
    for it in data or []:
        groups[it['group']].append(it['login'])
        logins.add(it['login'])

    users = {
        user.username: user
        for user in get_users(logins, create=False)
    }
    for group_id in groups:
        group = get_group(group_id, create=False)
        if group:
            for login in groups[group_id]:
                user = users.get(login)
                if user:
                    group.user_set.remove(user)


class _IdmUtils:
    def __init__(self):
        self.client = IdmClient()

    def reject_roles(self, role, users=None, groups=None, object_pk=None):
        params = {
            'system': settings.IDM_SYSTEM,
            'path': f'/{role}/',
            'state': 'granted,requested',
        }
        if object_pk:
            params['fields_data'] = {'object_pk': str(object_pk)}
        if users:
            params['users'] = users
        if groups:
            params['groups'] = groups
        try:
            self.client.reject_roles(**params)
        except RequestException as e:
            logger.error('Idm Error: %s', e)

    def request_roles(self, role, users=None, groups=None, object_pk=None):
        params = {
            'system': settings.IDM_SYSTEM,
            'path': f'/{role}/',
            'silent': True,
        }
        if object_pk:
            params['fields_data'] = {'object_pk': str(object_pk)}
        if users:
            params['users'] = users
        if groups:
            params['groups'] = groups
        try:
            self.client.request_roles(**params)
        except RequestException as e:
            logger.error('Idm Error: %s', e)


class _MockIdmUtils(_IdmUtils):
    def reject_roles(self, role, users=None, groups=None, object_pk=None):
        pass

    def request_roles(self, role, users=None, groups=None, object_pk=None):
        pass


IdmUtils = _MockIdmUtils if settings.IS_TEST else _IdmUtils
