# coding: utf-8


import logging
from collections import defaultdict
from itertools import zip_longest, repeat, chain

from constance import config
from django.conf import settings
from django.conf.urls import url
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.encoding import force_text
from django.utils.functional import SimpleLazyObject
from django.utils.translation import ugettext as _
from tastypie import fields
from tastypie.utils.urls import trailing_slash

from idm.api.exceptions import Forbidden, BadRequest
from idm.api.frontend import forms
from idm.api.frontend.apifields import ApiUserField, ServiceForeignKey, AllButRelated
from idm.api.frontend.base import FrontendApiResource
from idm.api.frontend.utils import OrderingAlias
from idm.core import exceptions
from idm.core.constants.action import ACTION
from idm.core.constants.rolefield import FIELD_STATE
from idm.core.constants.system import SYSTEM_PLUGIN_TYPE, IDM_SYSTEM_TREE, SYSTEM_GROUP_POLICY, SYSTEM_NODE_PLUGIN_TYPE
from idm.core.models import System, SystemMetainfo, Workflow, InternalRole, Action, SystemRolePush, RoleField
from idm.core.queues import RoleNodeQueue
from idm.core.tasks import RequestNewSystemResponsibles, SyncGroupMembershipSystemRelations, SystemShutdown
from idm.nodes.queue import HashUpdate
from idm.sync import everysync
from idm.users.models import User
from idm.utils import json, http, curl
from idm.utils.attributes import get_attribute
from idm.utils.diff import get_diff
from idm.utils.i18n import get_localized_fieldname

log = logging.getLogger(__name__)


class SystemResource(FrontendApiResource):
    """
    Ресурс системы
    """

    service = ServiceForeignKey(use_in=AllButRelated)
    state = fields.CharField(attribute='get_human_state', readonly=True)

    class Meta(FrontendApiResource.Meta):
        abstract = False
        @property
        def ordering_aliases(self):
            return {
                'name': OrderingAlias(get_localized_fieldname('name')),
                'service': OrderingAlias(
                    'service__root__{fieldname}'.format(fieldname=get_localized_fieldname('name')),
                    'service__{fieldname}'.format(fieldname=get_localized_fieldname('name'))
                ),
            }

        object_class = System
        queryset = (
            System.objects
            .select_related(
                'service',
                'service__parent',
                'service__root',
                'creator',
                'metainfo',
            )
        )
        resource_name = 'systems'
        list_allowed_methods = ['get', 'post']
        detail_allowed_methods = ['get', 'post', 'put', 'patch']
        fields = [
            'description',
            'endpoint_timeout',
            'endpoint_long_timeout',
            'group_policy',
            'is_active',
            'is_broken',
            'is_favorite',
            'is_sox',
            'name',
            'service',
            'slug',
            'state',
            'use_mini_form',
            'permissions',
            'use_webauth',
        ]

        additional_fields_for_details = [
            'can_be_broken',
            'has_review',
            'use_workflow_for_deprive',
            'passport_policy',
            'request_policy',
            'role_grant_policy',
            'roletree_policy',
            'review_on_relocate_policy',
            'inconsistency_policy',
            'workflow_approve_policy',
            'auth_factor',
            'tvm_id',
            'check_certificate',
            'base_url',
            'plugin_type',
            'node_plugin_type',
            'roles_tree_url',
            'sync_interval',
            ('last_sync_start_at', ('metainfo', 'last_sync_nodes_start')),  # (display_name, attributes_path)
            ('last_sync_at', ('metainfo', 'last_sync_nodes_finish')),  # (display_name, attributes_path)
            ('last_check_at', ('metainfo', 'last_check_inconsistencies_finish')),  # (display_name, attributes_path)
            'roles_review_days',
            'max_approvers',
            'use_tvm_role',
            'audit_method',
            'retry_failed_roles',
            'export_to_tirole',
            'tvm_tirole_ids',
        ]
        ordering = ['name']
        detail_uri_name = 'slug'
        insensitive_keys = {'slug'}
        limit = 200

    def prepend_urls(self):
        url_template = r'^(?P<resource_name>{resource_name})/(?P<slug>[-a-zA-Z0-9_]+)/{url_part}{slash}$'

        def prefix_url(url_part):
            result = url_template.format(
                resource_name=self._meta.resource_name,
                slash=trailing_slash(),
                url_part=url_part
            )
            return result

        return [
            # просмотр workflow
            url(prefix_url('workflow'), self.wrap_view('get_system_workflow'), name='api_get_system_workflow'),
            # создание рабочей копии workflow
            url(prefix_url('clone_workflow'), self.wrap_view('clone_system_workflow'), name='api_clone_system_workflow'),
            # получение списка доступных для редактирования полей системы
            url(prefix_url('editable_fields'), self.wrap_view('get_editable_fields'), name='api_get_editable_fields'),
            # получение полей, по которым можно фильтровать список ролей
            url(prefix_url('filter_fields'), self.wrap_view('get_filter_fields'), name='api_get_filter_fields'),
        ]

    def build_filters(self, request, filters=None):
        requester = self.get_requester(request)

        form = forms.SystemForm(filters)
        if not form.is_valid():
            raise BadRequest(form.errors)

        query = form.cleaned_data
        qset_filters = Q()

        if query['is_broken'] is not None:
            qset_filters &= Q(is_broken=query['is_broken'])

        if query['service']:
            qset_filters &= Q(service__in=query['service'])

        if query['is_sox'] is not None:
            qset_filters &= Q(is_sox=query['is_sox'])

        if query['use_webauth'] is not None:
            qset_filters &= Q(use_webauth=query['use_webauth'])

        if query['is_favorite']:
            qset_filters &= Q(in_favorites__user=requester.impersonated)

        if query['state'] == 'active':
            qset_filters &= Q(is_active=True, is_broken=False)
        elif query['state'] == 'inactive':
            qset_filters &= Q(is_active=False)
        elif query['state'] == 'broken':
            qset_filters &= Q(is_active=True, is_broken=True)
        else:
            qset_filters &= Q(is_active=True)

        if query['responsibles']:
            role_qs = (
                InternalRole.objects
                .filter(
                    node__value_path='/',
                    user_object_permissions__user__in=query['responsibles'],
                    role='responsible'
                )
            )
            qset_filters &= Q(nodes__internal_roles__in=role_qs)

        if query['team_members']:
            role_qs = (
                InternalRole.objects
                .filter(
                    node__value_path='/',
                    user_object_permissions__user__in=query['team_members'],
                    role='users_view'
                )
            )
            qset_filters &= Q(nodes__internal_roles__in=role_qs)

        system_contains = query['system__contains'].strip()
        if system_contains:
            name_query = {'{}__icontains'.format(get_localized_fieldname('name')): system_contains}
            qset_filters &= Q(
                Q(slug__icontains=system_contains) |
                Q(**name_query)
            )

        return qset_filters

    def apply_filters(self, request, applicable_filters, **kwargs):
        return self.get_object_list(request).filter(applicable_filters)

    def put_detail(self, request, **kwargs):
        """Изменить название или описание системы"""

        system = get_object_or_404(System, slug=kwargs['slug'].lower())
        data = self.deserialize(request, request.body)
        requester = self.get_requester(request, data)
        # TODO: сделать отдельное право
        if not system.is_permitted_for(requester, 'core.idm_view_roles'):
            raise Forbidden(_('Вы не можете редактировать данную систему'))
        form = forms.SystemEditForm(data, requester=request.user, system=system)
        if not form.is_valid():
            raise BadRequest(form.errors)
        names = form.cleaned_data['name']
        system.name, system.name_en = names['ru'], names['en']
        if form.cleaned_data['description']:
            descriptions = form.cleaned_data['description']
            system.description, system.description_en = descriptions['ru'], descriptions['en']
        system.save(update_fields=('name', 'name_en', 'description', 'description_en'))
        return self.create_response(request, None, status=204)

    def post_detail(self, request, **kwargs):
        """
        Запустить действие
        """
        system = get_object_or_404(System, slug=kwargs['slug'].lower())

        is_generic_plugin = system.plugin_type in SYSTEM_PLUGIN_TYPE.GENERICS
        is_yaml_node_plugin = system.node_plugin_type == SYSTEM_NODE_PLUGIN_TYPE.YAML
        is_dumb_with_yaml_system = not is_generic_plugin and is_yaml_node_plugin

        if not (is_generic_plugin or is_dumb_with_yaml_system):
            raise BadRequest(_('Доступны только системы с плагином generic')) # TODO: поменять текст ошибки, как выкатим фронтовый тикет для IDM-10064

        data = self.deserialize(request, request.body)
        requester = self.get_requester(request, data)
        form = forms.SystemOperationForm(data)
        if not form.is_valid():
            raise BadRequest(form.errors)
        operation = form.cleaned_data['operation']

        if is_dumb_with_yaml_system and operation not in ['sync_nodes', 'pull_handle']:
            raise BadRequest(_('В системе можно синхронизировать только узлы'))

        if not system.is_operational() and form.cleaned_data['operation'] not in ('recover', 'pull_handle'):
            raise Forbidden(_('Система сломана, операции невозможны'))

        tvm_id = system.get_tvm_id()
        
        if (
            operation.startswith('sync_') and
            system.auth_factor == 'tvm' and
            not tvm_id
        ):
            raise BadRequest('У данной системы не указан ID TVM-приложения')

        if operation == 'pull_handle':
            if not system.is_permitted_for(requester, 'core.idm_view_roles'):
                raise Forbidden(_('Вы не можете обращаться к ручкам данной системы'))

            handles_form = forms.SystemHandleForm(form.cleaned_data.get('options', {}))
            if not handles_form.is_valid():
                raise BadRequest(handles_form.errors)

            handle = handles_form.cleaned_data['handle']
            auth_factor = handles_form.cleaned_data['auth_factor']
            plugin = system.plugin

            if is_dumb_with_yaml_system and handle != 'info':
                raise BadRequest(_('В системе можно диагностировать только ручку info'))

            if handle == 'info' and is_yaml_node_plugin:
                if not system.roles_tree_url:
                    raise BadRequest(_('Не установлена ссылка на дерево в параметрах системы'))
                url = system.roles_tree_url
            else:
                try:
                    url = plugin.base_url % handle
                except exceptions.URLNotDefined:
                    raise BadRequest(_('Не установлен адрес ручки в параметрах системы'))

            if handles_form.cleaned_data['library'] == 'requests':
                http_lib = http
            elif handles_form.cleaned_data['library'] == 'curl':
                http_lib = curl
            else:
                http_lib = http if system.use_requests else curl

            tvm_id = system.get_tvm_id(url=url)

            if system.auth_factor == 'tvm' and not tvm_id:
                raise BadRequest('У данной системы не указан ID TVM-приложения')

            result = {
                'auth_factor': auth_factor,
                'library': http_lib.__file__.split('/')[-1],
                'url': url,
            }

            try:
                response = http_lib.get(
                    url,
                    use_client_certificate=(auth_factor == 'cert'),
                    tvm_id=tvm_id if auth_factor == 'tvm' else None,
                    check_server_certificate=system.check_certificate,
                    timeout=system.endpoint_long_timeout,
                )
            except Exception as exc:
                log.exception('Exception during http request')
                result['text'] = force_text(exc)
                result['headers'] = _('(Нет)')
                result['response'] = '<Connection error>'
            else:
                result['response'] = response
                if isinstance(response.headers, str):
                    result['headers'] = response.headers
                else:
                    result['headers'] = '\n'.join('%s: %s' % (k, response.headers[k]) for k in sorted(response.headers))

                try:
                    # Форматируем JSON
                    result['text'] = json.dumps(json.loads(response.text), indent=2, ensure_ascii=False)
                except ValueError:
                    # Прислали невалидный JSON, показываем как есть
                    result['text'] = response.text

            return self.create_response(request, result)

        elif operation == 'sync_nodes':
            if not system.is_permitted_for(requester, 'core.edit_role_nodes'):
                raise Forbidden(_('Вы не можете синхронизировать дерево ролей в данной системе'))

            if form.cleaned_data['dry_run']:
                system.fetch_root_role_node()
                try:
                    system.root_role_node.fetcher.prepare()
                    data = system.root_role_node.fetcher.fetch(system)
                except exceptions.IDMError as error:
                    raise BadRequest(error.message)
                except Exception:
                    return self.create_response(request, {'queue': _('Система не отвечает или отвечает как-то не так')})

                system.root_role_node.rehash()
                queue = system.root_role_node.get_queue(data, force_update=True)
                result = {'queue': self.render_queue(queue)}
                return self.create_response(request, result)

            else:
                log.info(
                    'user "%s" attempting to sync nodes in system "%s"', requester.impersonated.username, system.slug
                )
                everysync.sync_roles_and_nodes(
                    system,
                    requester=requester,
                    force_nodes=True,
                    from_web=True,
                    steps=everysync.SYNC_NODES_ONLY,
                    block=True,
                )
                return self.create_response(request, {})

        elif operation == 'sync_roles':

            if form.cleaned_data['dry_run']:
                if not system.is_permitted_for(requester.impersonated, 'core.check_roles'):
                    raise Forbidden(_('Вы не можете запускать сверку ролей в данной системе'))
                steps = everysync.CHECK_ROLES_ONLY
                log.info(
                    'user "%s" attempting to check roles in system "%s"', requester.impersonated.username,
                    system.slug
                )
            else:
                if not system.is_permitted_for(requester.impersonated, 'core.sync_roles'):
                    raise Forbidden(_('Вы не можете синхронизировать роли в данной системе'))
                log.info(
                    'user "%s" attempting to sync roles in system "%s"', requester.impersonated.username,
                    system.slug
                )
                steps = everysync.SYNC_ROLES_ONLY
            
                inc_count = system.inconsistencies.active().count()
                inc_max = json.loads(config.SYSTEM_INCONSISTENCY_EXCEPT).get(system.slug, config.SYSTEM_INCONSISTENCY_MAX)
                if inc_count > inc_max:
                    raise Forbidden(
                        _('Количество расхождений превышает порог автоматической '
                          'обработки, обратитесь к дежурному - '
                          'https://st.yandex-team.ru/createTicket?queue=IDM&priority=2&tags%5B%5D=duty&type=256')
                    )

            everysync.sync_roles_and_nodes(
                system,
                requester=requester,
                force_roles=True,
                from_web=True,
                steps=steps,
                block=True,
                resolve_in_idm_direct=form.cleaned_data['resolve_in_idm_direct'],
            )
            return self.create_response(request, {})

        elif operation == 'sync_memberships':
            if system.group_policy not in SYSTEM_GROUP_POLICY.AWARE_OF_MEMBERSHIPS:
                raise BadRequest(
                    message=_('Система не поддерживает работу с членствами в группах')
                )
            SyncGroupMembershipSystemRelations.apply_async(
                kwargs={
                    'system_id': system.pk,
                    'requester_id': requester.impersonated.id,
                    'check_only': False,
                    'resolve_only': False,
                },
                countdown=settings.IDM_PLUGIN_TASK_COUNTDOWN,
            )
            log.info(
                'user "%s" attempting to sync memberships in system "%s"', requester.impersonated.username,
                system.slug
            )
            return self.create_response(request, {})

        elif operation == 'recover':
            if not system.is_permitted_for(requester, 'core.recover_system'):
                raise Forbidden(_('Вы не можете починить данную систему'))

            log.info('user "%s" attempting to recover system "%s"', requester.impersonated.username, system.slug)
            system.recover(user=requester.impersonated, reason=_('Система восстановлена через интерфейс IDM'))
            return self.create_response(request, None, status=204)

        else:
            raise BadRequest(message=f'Illegal operation {operation}')

    @staticmethod
    def render_queue(queue: RoleNodeQueue) -> str:
        return render_to_string(
            'api/queue.html',
            {
                'queue': queue.as_context(),
                'queue_is_empty': not queue or all([isinstance(item, HashUpdate) for item in queue.items])
            }
        )

    def get_filter_fields(self, request, slug, **kwargs):
        if not slug:
            log.error('slug not found in request %s', request)
            raise BadRequest('Invalid data sent, no system slug.')

        self.method_check(request, allowed=['get'])
        system = get_object_or_404(System.objects.select_related('actual_workflow'), slug=slug)
        data = [field.as_frontend_api() for field in system.systemrolefields.filter(state=FIELD_STATE.ACTIVE)]
        return self.create_response(request, data)

    def get_workflow_history(self, system, limit=25):
        versions_ids = list(
            system.workflows.approved().
            order_by('-approved', '-pk').values_list('pk', flat=True)[:limit + 1]
        )

        workflows = Workflow.objects.select_related('user', 'approver').filter(pk__in=versions_ids)

        versions_dict = {
            version.pk: version for version in workflows
        }
        versions = [versions_dict[version_id] for version_id in versions_ids]

        pairs = list(zip_longest(versions, versions[1:]))[:limit]  # пара с предыдущей версией

        result = []
        for pair in pairs:
            workflow = pair[0]
            if pair[1]:
                old_user_wf_code = pair[1].workflow
                old_group_wf_code = pair[1].group_workflow
            else:
                old_user_wf_code = old_group_wf_code = ''

            diff = get_diff(
                old_text=old_user_wf_code,
                new_text=workflow.workflow,
                full=True,
                highlight=False,
            )
            group_diff = get_diff(
                old_text=old_group_wf_code,
                new_text=workflow.group_workflow,
                full=True,
                highlight=False,
            )

            result.append({
                'id': workflow.pk,
                'updated': workflow.approved,
                'author': {'login': workflow.user},
                'approver': {'login': workflow.approver.username} if workflow.approver else None,
                'diff': diff,
                'group_diff': group_diff,
                'workflow': workflow.workflow,
                'group_workflow': workflow.group_workflow
            })

        return result

    def get_system_workflow(self, request, slug, **kwargs):
        """
        Workflow
        """
        if not slug:
            log.error('slug not found in request %s', request)
            raise BadRequest('Invalid data sent, no system slug.')

        self.method_check(request, allowed=['get'])
        system = get_object_or_404(System.objects.select_related('actual_workflow'), slug=slug)

        if not system.is_permitted_for(request.user, 'core.idm_view_workflow'):
            raise Forbidden('You cannot view workflow')

        result = {
            'system_id': system.id,
            'workflow': system.get_user_workflow_code(),
            'group_workflow': system.get_group_workflow_code(),
            'updated': system.get_workflow_changed_date(),
            'can_edit': True,
            'develop_workflow': [],
            'committed_workflow': [],
            'history': self.get_workflow_history(system),
        }

        drafts = Workflow.objects.filter(
            user=request.user,
            system=system,
            state='edit',
        ).order_by('-updated', '-pk')

        result['develop_workflow'] = [
            {
                'id': workflow.id,
                'added': workflow.added,
                'updated': workflow.updated,
                'state': workflow.state,
                'comment': workflow.comment or '',
            }
            for workflow in drafts
        ]

        committed = Workflow.objects.filter(system=system, state='commited').select_related('user').order_by('-updated', '-pk')
        if not system.is_permitted_for(request.user, 'core.approve_workflow'):
            committed = committed.filter(user=request.user)

        result['committed_workflow'] = [{
            'id': workflow.id,
            'added': workflow.added,
            'updated': workflow.updated,
            'author': {'login': workflow.user.username},
            'comment': workflow.comment or '',
        } for workflow in committed]

        return self.create_response(request, result)

    def clone_system_workflow(self, request, slug, **kwargs):
        """
        создание рабочей копии workflow
        """
        if not slug:
            log.error('slug not found in request %s', request)
            raise BadRequest('Invalid data sent, no system slug.')

        self.method_check(request, allowed=['post'])
        system = get_object_or_404(System, slug=slug)

        if not system.is_permitted_for(request.user, 'core.idm_view_workflow'):
            raise Forbidden('Вы не можете редактировать workflow')

        workflow_dev = system.clone_workflow(request.user)

        result = {
            'id': workflow_dev.id,
            'workflow': workflow_dev.workflow,
            'group_workflow': workflow_dev.group_workflow,
            'added': workflow_dev.added,
            'updated': workflow_dev.updated,
        }

        return self.create_response(request, result)

    def get_editable_fields(self, request, slug, **kwargs):
        """Получить список доступных для редактирования полей"""
        if not slug:
            log.error('slug not found in request %s', request)
            raise BadRequest('Invalid data sent, no system slug.')

        self.method_check(request, allowed=['get'])
        system = get_object_or_404(System, slug=slug.lower())

        ADDITIONAL = {'emails', 'rolefields'}  # поля, которые проставляются как-то не по-нормальному
        EXCLUDED = {  # readonly или непригодные для редактирования поля
            'permissions', 'state', 'is_favorite',
            'last_sync_start_at', 'last_sync_at', 'last_check_at',
            'slug', 'use_mini_form',
        }

        ALWAYS_FORBIDDEN = {
            'roles_review_days', 'endpoint_timeout', 'endpoint_long_timeout', 'max_approvers', 'can_be_broken',
            'auth_factor',
        }
        FORBIDDEN_UNDER_SOX = {
            'is_sox', 'has_review',
            'review_on_relocate_policy', 'inconsistency_policy', 'workflow_approve_policy',
        }

        # То, что видит суперюзер
        displayed_additional_fields = [
            (field if isinstance(field, str) else field[0])  # если это "сложное" related-поле, то выводим display_name
            for field in
            self._meta.additional_fields_for_details
        ]
        initial = {
            field: {}
            for field
            in (self._meta.fields + displayed_additional_fields + list(ADDITIONAL))
            if field not in EXCLUDED
        }
        # То, что видит команда без SOX
        allowed_by_default = {
            field: initial[field]
            for field
            in initial
            if field not in ALWAYS_FORBIDDEN
        }
        # То, что видит команда под SOX
        allowed_under_sox = {
            field: allowed_by_default[field]
            for field
            in allowed_by_default
            if field not in FORBIDDEN_UNDER_SOX
        }

        is_admin = system.is_permitted_for(request.user, 'core.edit_system_extended')
        can_edit = system.is_permitted_for(request.user, 'core.edit_system')

        if is_admin:
            editable_fields = initial
        elif can_edit:
            if system.is_sox:
                editable_fields = allowed_under_sox
            else:
                editable_fields = allowed_by_default
        else:
            editable_fields = []

        if 'plugin_type' in editable_fields:
            if is_admin:
                editable_fields['plugin_type']['allowed_values'] = [
                    x[0]
                    for x
                    in SYSTEM_PLUGIN_TYPE.CHOICES
                ]
            else:
                editable_fields['plugin_type']['allowed_values'] = [
                    x[0]
                    for x
                    in SYSTEM_PLUGIN_TYPE.UNPRIVILEGED_CHOICES
                ]

        result = {'editable_fields': editable_fields}
        return self.create_response(request, result)

    def get_system_and_role_to_users(self, objects):
        role_qs = (
            InternalRole.objects
            .filter(
                node__value_path='/',
                node__system__in=objects,
                role__in=('responsible', 'users_view')
            )
        )

        user_fields = ('id', 'username', 'first_name', 'last_name', 'type')
        user_qs = (
            User.objects
            .filter(id__in=role_qs.values('user_object_permissions__user'))
            .only(*user_fields)
        )

        users = {user.id: user for user in user_qs}
        roles = role_qs.values('role', 'node__system', 'user_object_permissions__user')

        system_and_role_to_users = defaultdict(set)
        for role in roles:
            user_id = role['user_object_permissions__user']
            if user_id is not None:
                system_and_role_to_users[(role['node__system'], role['role'])].add(users[user_id])

        return system_and_role_to_users

    def get_object_list(self, request, **kwargs):
        objects = super(SystemResource, self).get_object_list(request)
        request.system_and_role_to_users = SimpleLazyObject(lambda: self.get_system_and_role_to_users(objects))
        request.favorite_systems = SimpleLazyObject(lambda: {fav.system_id for fav in request.user.favorite_systems.all()})
        request.permissions_by_systems = SimpleLazyObject(lambda: request.user.get_permissions_by_systems())
        request.requester_can_view_secrets = SimpleLazyObject(lambda: request.user.has_perm('core.idm_view_system_secrets'))
        return objects

    def _process_rolefields(self, system, slugslist):
        existing_fields = {
            field.slug: field
            for field in system.systemrolefields.filter(slug__in=slugslist)
        }

        for fieldslug in slugslist:
            if fieldslug not in existing_fields:  # Такого поля еще нет в базе
                # Добавляем только те поля, которые есть среди полей в ролях
                rolefield = RoleField.objects.filter(slug=fieldslug, node__system=system).first()
                if rolefield is None:
                    raise BadRequest(
                        message=_('Поля %s нет среди полей в ролях для данной системы' % fieldslug)
                    )

                system.systemrolefields.create(
                    slug=rolefield.slug,
                    name=rolefield.name,
                    name_en=rolefield.name_en,
                    is_required=rolefield.is_required,
                    type=rolefield.type,
                    options=rolefield.options,
                )
            elif existing_fields[fieldslug].state == FIELD_STATE.DEPRIVED:
                existing_fields[fieldslug].state = FIELD_STATE.CREATED
                existing_fields[fieldslug].save(update_fields=['state'])

        # Проверям поля, которые уже есть в базе, но отсутствуют в запросе и переводим их в depriving
        system.systemrolefields.exclude(slug__in=slugslist).update(
            state=FIELD_STATE.DEPRIVING,
            updated_at=timezone.now(),
        )

    def patch_detail(self, request, **kwargs):
        """Редактирование системы"""
        system = get_object_or_404(System, slug=kwargs['slug'].lower())
        data = self.deserialize(request, request.body)
        requester = self.get_requester(request, data)

        if not system.is_permitted_for(requester, 'core.edit_system'):
            raise Forbidden(_('Вы не можете редактировать данную систему'))

        if system.is_permitted_for(requester, 'core.edit_system_extended'):
            form_class = forms.ExtendedSystemEditForm
        elif system.is_sox:
            form_class = forms.SoxSystemEditForm
        else:
            form_class = forms.SystemEditForm
        form = form_class(data, requester=request.user, system=system)

        if not form.is_valid():
            raise BadRequest(form.errors)
        cleaned_data = form.cleaned_data

        update_fields = []
        for field in form.changed_data:
            if field in ('name', 'description'):
                update_fields += [field, field + '_en']
                setattr(system, field, cleaned_data[field]['ru'])
                setattr(system, field+'_en', cleaned_data[field]['en'])
            elif field == 'rolefields':
                self._process_rolefields(system, cleaned_data[field])
            else:
                update_fields.append(field)
                setattr(system, field, cleaned_data[field])

        system.save(update_fields=update_fields)
        if 'is_active' in update_fields and cleaned_data.get('is_active') is False:
            if not request.user.is_superuser:
                raise Forbidden(_('Выключить систему может только суперпользователь, обратитесь в поддержку IDM'))
            SystemShutdown.apply_async(kwargs={'system_pk': system.pk, 'requester_pk': requester.impersonated.pk})
        Action.objects.create(system=system, user=requester.impersonated, action=ACTION.SYSTEM_UPDATED)
        return self.get_detail(request, **kwargs)

    def post_list(self, request, **kwargs):
        requester = self.get_requester(request)
        data = self.deserialize(request, request.body)
        form = forms.CreateSystemForm(data)
        if not form.is_valid():
            raise BadRequest(form.errors)
        cleaned_data = form.cleaned_data
        system = System.objects.create(
            slug=cleaned_data['slug'],
            name=cleaned_data['name']['ru'],
            name_en=cleaned_data['name']['en'],
            service=cleaned_data['service'],
            description=cleaned_data['description']['ru'],
            description_en=cleaned_data['description']['en'],
            creator=requester.impersonated,
        )
        system.metainfo = SystemMetainfo.objects.create()
        system.save(update_fields=['metainfo'])

        SystemRolePush.objects.bulk_create(
            [SystemRolePush(
                role_slug=role_slug,
                system=system,
                **{'user' if isinstance(subject, User) else 'group': subject}
            ) for subject, role_slug in chain(
                zip(
                    chain(cleaned_data['responsibles_user'], cleaned_data['responsibles_group']),
                    repeat(IDM_SYSTEM_TREE.RESPONSIBLES_ROLE_SLUG),
                ),
                zip(
                    chain(cleaned_data['team_members_user'], cleaned_data['team_members_group']),
                    repeat(IDM_SYSTEM_TREE.TEAM_MEMBERS_ROLE_SLUG),
                ),
            )]
        )

        Action.objects.create(system=system, user=requester.impersonated, action=ACTION.SYSTEM_CREATED)
        RequestNewSystemResponsibles.apply_async(
            kwargs={
                'system_id': system.pk,
            },
            countdown=settings.IDM_PLUGIN_TASK_COUNTDOWN
        )
        return self.create_response(request, None, status=201)

    def dehydrate_common(self, bundle):
        bundle.data['emails'] = bundle.obj.get_emails()
        bundle.data['name'] = bundle.obj.get_name_localized_dict()
        bundle.data['description'] = bundle.obj.get_description_localized_dict()

        return bundle

    def dehydrate_for_list(self, bundle):
        system_and_role_to_users = bundle.request.system_and_role_to_users
        teams = (
            ('responsibles', 'responsible'),
            ('team', 'users_view'),
        )

        for team_slug, internal_role in teams:
            users = system_and_role_to_users[(bundle.obj.id, internal_role)]
            bundle.data[team_slug] = list(map(ApiUserField().convert, users))
        bundle.data['is_favorite'] = bundle.obj.id in bundle.request.favorite_systems
        bundle.data['permissions'] = bundle.request.permissions_by_systems.get(bundle.obj.slug, [])

        if bundle.request.requester_can_view_secrets:
            for field in ['auth_factor', 'base_url', 'tvm_id', 'plugin_type']:
                bundle.data[field] = getattr(bundle.obj, field)

        return self.dehydrate_common(bundle)

    @staticmethod
    def _deep_getattr(source_object, field):
        """Проходится по всем полям. Если поле – строка, то записывает значение поля.
        Если поле – список строк, то проваливается в атрибуты, атрибуты атрибутов, etc.
        Возращает ключ, по которому поле должно отображаться, и его значение."""
        if isinstance(field, str):
            return field, getattr(source_object, field)
        else:
            display_name, attributes_path = field
            return display_name, get_attribute(source_object, attributes_path)

    def dehydrate_for_detail(self, bundle):
        bundle.data['is_favorite'] = bundle.obj.in_favorites.filter(user=bundle.request.user).exists()
        bundle.data['rolefields'] = ','.join(
            bundle.obj
            .systemrolefields
            .filter(state__in=(FIELD_STATE.ACTIVE, FIELD_STATE.CREATED))
            .order_by('slug')
            .values_list('slug', flat=True)
        )

        requester = self.get_requester(bundle.request)
        requester_has_perm = 'idm_view_system_secrets' in requester.impersonated.get_permissions(system=bundle.obj)
        requester_is_creator = requester.impersonated == bundle.obj.creator
        if getattr(bundle.request, 'requester_can_view_secrets', None) or requester_has_perm or requester_is_creator:
            for field in self.Meta.additional_fields_for_details:
                key, value = self._deep_getattr(bundle.obj, field)
                bundle.data[key] = value

        return self.dehydrate_common(bundle)

    def dehydrate_for_related(self, bundle):
        bundle.data['name'] = bundle.obj.get_name()
        bundle.data['description'] = bundle.obj.get_description()
        return bundle
