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

from collections import defaultdict
from urllib.parse import urlparse, parse_qs
from django.conf import settings
from django.db import models
from django.db.models import F
from django.db.models.fields.files import FieldFile
from django.db.models.fields.related import ForeignKey
from django.core import validators
from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.translation import ugettext as _
from rest_framework import serializers, ISO_8601
from rest_framework.serializers import ValidationError

from events.accounts.models import User, Organization, get_or_create_organization
from events.common_app.api_admin.v2.serializers import ContentTypeSerializer
from events.common_app.blackbox_requests import JsonBlackbox
from events.common_app.startrek.client import get_startrek_client
from events.common_app.serializers import IdentifierField
from events.common_app.utils import (
    chunks,
    get_lang_with_fallback,
    get_user_ip_address,
)
from events.common_app.wiki import get_wiki_client
from events.geobase_contrib.models import City
from events.surveyme.logic.access import FORM_ACCESS_TYPE_COMMON, FORM_ACCESS_TYPE_OWNER, FORM_ACCESS_TYPE_RESTRICTED
from events.surveyme.models import (
    Survey,
    AnswerType,
    SurveyQuestion,
    SurveyText,
    SurveyQuestionShowConditionNodeItem,
    SurveyQuestionShowConditionNode,
    SurveyQuestionChoice,
    SurveyAgreement,
    ProfileSurveyAnswer,
    SurveySubmitConditionNodeItem,
    SurveySubmitConditionNode,
    SurveyGroup,
    SurveyQuestionHintType,
    SurveyQuestionMatrixTitle,
    ValidatorType,
    SurveyTemplate,
    SurveyStyleTemplate,

    ANOTHER_GROUP_QUESTION_IN_LOGIC,
    STYLES_TEMPLATE_DOES_NOT_EXIST,
    GROUP_QUESTION_CANT_USE_IN_SURVEY_HOOK_LOGIC,
    GROUP_QUESTION_CANT_USE_IN_SHOW_QUESTION_LOGIC,
    FOLLOW_TYPE_CHOICES,
)
from events.common_storages.models import ProxyStorageModel
from events.rest_framework_contrib.serializers import InternalModelSerializerV2Mixin
from events.rest_framework_contrib.mixins import TranslationSerializerMixin
from events.balance.models import Ticket, Order
from events.surveyme.dataclasses import (
    SurveyQuiz,
    SurveyQuizItem,
    QUIZ_CALC_METHOD_RANGE,
    QUIZ_CALC_METHOD_SCORES,
)
from events.surveyme.fields.base.serializers import (
    RedirectSerializer,
    FooterSerializer,
    StatsSerializer,
    TeaserSerializer,
    QuizSerializer,
)
from events.surveyme.models import (
    SurveyStateConditionNode,
    SurveyStateConditionItem,
    ProfileSurveyAnswerStateConditionResult,
)
from events.surveyme.utils import (
    check_email_title,
    check_if_internal_host,
)
from events.surveyme_integration.models import (
    SurveyHook,
    SurveyHookCondition,
    ServiceSurveyHookSubscription,
    SubscriptionHeader,
    SurveyHookConditionNode,
    JSONRPCSubscriptionData,
    JSONRPCSubscriptionParam,
    StartrekSubscriptionData,
    WikiSubscriptionData,
    SubscriptionAttachment,
    IntegrationFileTemplate,
    SurveyVariable,
)
from events.surveyme_integration.variables import (
    UserEmailVariable,
    RequestQueryParamVariable,
    FormQuestionAnswerVariable,
    FormAuthorEmailVariable,
    DirectoryStaffMetaUserVariable,
    DirectoryStaffMetaQuestionVariable,
)
from events.surveyme_integration.variables.directory import (
    DirectoryStaffEmailVariableRenderer,
)
from events.surveyme_keys.models import Key, SurveyKeysBundle
from events.followme.api_admin.v2.serializers import (
    UserUidOnlySerializer,
    UserFollowerSerializer,
)
from events.surveyme_integration.exceptions import (
    WIKI_PAGE_DOESNT_EXISTS_MESSAGE,
    WIKI_NOT_VALID_SUPERTAG_MESSAGE,
    EMAIL_INCORRECT_VARIABLE_TYPE_MESSAGE,
    EMAIL_INCORRECT_QUESTION_TYPE_MESSAGE,
    EMAIL_INCORRECT_EMAIL_MESSAGE,
    EMAIL_INCORRECT_FROM_TITLE,
    EMPTY_VALUE_EXCEPTION_MESSAGE,
    STARTREK_INCORRECT_QUEUE_MESSAGE,
    STARTREK_INCORRECT_PARENT_MESSAGE,
    STARTREK_QUEUE_AND_PARENT_EMPTY_MESSAGE,
    STARTREK_QUEUE_NOT_EXIST_MESSAGE,
    STARTREK_PARENT_NOT_EXIST_MESSAGE,
    STARTREK_TYPE_EMPTY_MESSAGE,
    STARTREK_TYPE_NOT_EXIST_MESSAGE,
    STARTREK_PRIORITY_EMPTY_MESSAGE,
    STARTREK_PRIORITY_NOT_EXIST_MESSAGE,
    STARTREK_TITLE_EMPTY_MESSAGE,
    NO_SUCH_VARIABLE_MESSAGE,
)
from events.media.models import Image
from events.media.api_admin.v2.serializers import ImageSerializer


DATETIME_INPUT_FORMATS = (ISO_8601, '%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%d')
DISALLOWED_B2B_HEADERS = {
    'return-path',
    'received',
    'from',
    'content-transfer-encoding',
    'content-type',
    'subject',
    'x-yandex-local',
}

EXPORT_FORMATS = [
    ('csv', 'text/csv'),
    ('xlsx', 'application/excel'),
    ('json', 'application/json'),
]

UPLOAD_FORMATS = [
    ('mds', 'MDS'),
    ('disk', 'Yandex.Disk'),
]

QUIZ_CALC_METHOD_CHOICES = (
    QUIZ_CALC_METHOD_RANGE,
    QUIZ_CALC_METHOD_SCORES,
)
QUIZ_MIN_ITEMS_COUNT = 2
QUIZ_MAX_SCORES = pow(2, 31) - 1


class StringSeparatedField(serializers.Field):
    def __init__(self, *args, **kwargs):
        self.separator = kwargs.pop('separator', ',')
        super().__init__(*args, **kwargs)

    def _cast_val(self, val):
        return val

    def _generate_array(self, data):
        for val in data.split(self.separator):
            val = val.strip()
            if val:
                yield self._cast_val(val)

    def to_internal_value(self, data):
        if isinstance(data, str):
            return list(self._generate_array(data))
        raise serializers.ValidationError('Not a string')

    def to_representation(self, value):
        return self.separator.join(map(str, value or []))


class IntegerSeparatedField(StringSeparatedField):
    def _cast_val(self, val):
        try:
            return int(val)
        except ValueError:
            raise serializers.ValidationError('Must be string of integers')


class ExportColumnsSerializer(serializers.Serializer):
    questions = IntegerSeparatedField(separator=',', required=False)
    user_fields = StringSeparatedField(separator=',', required=False)
    answer_fields = StringSeparatedField(separator=',', required=False)
    orders = serializers.BooleanField(default=False)  # deprecated


MIN_LIMIT_SIZE = 0
MAX_LIMIT_SIZE = 1000


class ExportAnswersSerializer(serializers.Serializer):
    pks = IntegerSeparatedField(separator=',', required=False)
    export_format = serializers.ChoiceField(choices=EXPORT_FORMATS, default='xlsx')
    upload = serializers.ChoiceField(choices=UPLOAD_FORMATS, default='mds')
    upload_files = serializers.BooleanField(default=False)
    export_archived_answers = serializers.BooleanField(default=False)  # deprecated
    export_columns = ExportColumnsSerializer(required=False)
    date_started = serializers.DateTimeField(required=False)
    date_finished = serializers.DateTimeField(required=False)
    limit = serializers.IntegerField(
        required=False,
        min_value=MIN_LIMIT_SIZE,
        max_value=MAX_LIMIT_SIZE,
    )


class WritableField(serializers.Field):
    def to_internal_value(self, data):
        return data

    def to_representation(self, value):
        return value


class VariableMixin(object):
    def to_internal_value(self, data):
        result_variables = list()
        variables = data.get('variables') or {}
        for variable in variables.values():
            variable_id = variable.pop('_id', None)
            if variable_id:
                variable['variable_id'] = variable_id
                result_variables.append(variable)
        data['variables'] = result_variables
        return super(VariableMixin, self).to_internal_value(data)

    def to_representation(self, instance):
        result = super(VariableMixin, self).to_representation(instance)
        variables = result['variables']
        result['variables'] = {
            variable['variable_id']: variable
            for variable in variables
        }
        return result


class ValidatorTypeSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    class Meta:
        model = ValidatorType
        fields = (
            'id',
            'name',
            'slug',
            'is_external',
        )


class AnswerTypeSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    validator_types = ValidatorTypeSerializer(many=True)

    class Meta:
        model = AnswerType
        fields = (
            'id',
            'is_allow_choices',
            'is_could_be_used_in_conditions',
            'is_read_only',
            'slug',
            'name',
            'icon',
            'kind',
            'admin_preview',
            'allow_settings',
            'required_settings',
            'is_allow_widgets',
            'validator_types',
        )


class AnswerTypeCopySerializer(AnswerTypeSerializer, serializers.ModelSerializer):
    class Meta:
        model = AnswerType
        fields = (
            'admin_preview',
            'validator_types',
        )


class SurveyAgreementSerializer(TranslationSerializerMixin, InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    class Meta:
        model = SurveyAgreement
        fields = (
            'id',
            'is_required',
            'name',
            'slug',
            'text',
            'translations',
        )


class SurveyVariableSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    _id = serializers.CharField(source='variable_id')
    variable_id = serializers.CharField()
    hook_subscription_id = serializers.IntegerField(required=False, allow_null=True)
    integration_file_template_id = serializers.IntegerField(required=False, allow_null=True)
    arguments = serializers.JSONField(required=False, allow_null=True)
    filters = serializers.JSONField(required=False, allow_null=True)

    class Meta:
        model = SurveyVariable
        fields = (
            'hook_subscription_id',
            'integration_file_template_id',
            '_id',
            'var',
            'format_name',
            'variable_id',
            'arguments',
            'filters',
        )


class SurveyTemplateSerializer(TranslationSerializerMixin, InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    image = ImageSerializer(read_only=True)
    image_admin = ImageSerializer(read_only=True)

    class Meta:
        model = SurveyTemplate
        fields = (
            'id',
            'name',
            'description',
            'slug',
            'image',
            'image_admin',
            'connect_template',
        )


class SurveyStyleTemplateSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    styles = serializers.JSONField(required=False, allow_null=True)
    template_id = serializers.IntegerField(source='id', read_only=True)
    image_page = ImageSerializer(required=False, allow_null=True)
    image_form = ImageSerializer(required=False, allow_null=True)

    class Meta:
        model = SurveyStyleTemplate
        fields = (
            'id',
            'name',
            'type',
            'styles',
            'template_id',
            'image_page',
            'image_form',
        )


class UserListSerializer(serializers.ListSerializer):
    def get_passport_data(self, uids):
        kwargs = (
            settings.INTERNAL_SITE_BLACKBOX_KWARGS
            if settings.APP_TYPE in ('forms_int', 'forms_ext_admin')
            else settings.EXTERNAL_SITE_BLACKBOX_KWARGS
        )
        bb = JsonBlackbox(**kwargs)
        params = {
            'uid': ','.join(uids),
            'dbfields': ('accounts.login.uid', 'account_info.fio.uid'),
            'userip': get_user_ip_address(),
        }
        response = bb.userinfo(**params)
        return {
            user['id']: {
                'fields': {
                    'login': user.get('dbfields', {}).get('accounts.login.uid', ''),
                    'fio': user.get('dbfields', {}).get('account_info.fio.uid', ''),
                },
            }
            for user in response.get('users') or []
        }

    def get_all_passport_data(self, uids):
        all_data = {}
        for chunk in chunks(uids, 100):
            all_data.update(self.get_passport_data(chunk))
        return all_data

    def get_uids(self, iterable):
        return set(
            str(item.user.uid)
            for item in iterable
            if item.user and item.user.uid
        )

    def to_representation(self, data):
        iterable = data.all() if isinstance(data, models.Manager) else data
        uids = self.get_uids(iterable)
        passport_data = self.get_all_passport_data(uids) if uids else {}
        if passport_data:
            for item in iterable:
                user = item.user
                if user and user.uid:
                    user._params = passport_data.get(str(user.uid))
        return [
            self.child.to_representation(item) for item in iterable
        ]


class OrganizationField(serializers.SlugRelatedField):
    queryset = Organization.objects.all()

    def to_internal_value(self, dir_id):
        return get_or_create_organization(dir_id)


class SurveyGroupSerializer(serializers.ModelSerializer):
    profile = UserFollowerSerializer(read_only=True, source='user')
    org_id = OrganizationField(source='org', slug_field='dir_id', allow_null=True, required=False)

    class Meta:
        model = SurveyGroup
        list_serializer_class = UserListSerializer

        fields = (
            'name',
            'id',
            'metrika_counter_code',
            'profile',
            'org_id',
        )


class SurveyGroupListSerializer(serializers.ModelSerializer):
    profile = UserUidOnlySerializer(read_only=True, source='user')
    org_id = OrganizationField(source='org', slug_field='dir_id', allow_null=True, required=False)

    class Meta:
        model = SurveyGroup

        fields = (
            'id',
            'name',
            'profile',
            'org_id',
        )


class SurveyContentObjectField(serializers.RelatedField):
    def to_representation(self, value):
        if not value:
            return None
        else:
            raise Exception('Unexpected type of survey content object')

        return value


class SurveyStateConditionItemSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    id = serializers.IntegerField()

    class Meta:
        model = SurveyStateConditionItem
        fields = (
            'id',
            'operator',
            'condition',
            'value',
            'position',
            'content_type_attribute',
            'survey_question',
        )


class SurveyStateConditionNodeSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    id = serializers.IntegerField()
    items = SurveyStateConditionItemSerializer(many=True)

    class Meta:
        model = SurveyStateConditionNode
        fields = (
            'id',
            'title',
            'invite_recommendation',
            'limit',
            'rating',
            'survey',
            'items',
            'position',
        )

    def create(self, validated_data):
        items = validated_data.pop('items', [])
        node = SurveyStateConditionNode.objects.create(**validated_data)
        for item in items:
            SurveyStateConditionItem.objects.create(node=node, **item)


class SubscriptionHeaderSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    id = serializers.IntegerField()

    class Meta:
        model = SubscriptionHeader
        fields = (
            'id',
            'name',
            'value',
            'add_only_with_value',
        )


class SubscriptionDataGenericFieldBaseSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField()
    question_id = serializers.IntegerField()
    position = serializers.IntegerField()

    class Meta:
        fields = (
            'id',
            'question_id',
            'position'
        )


class JSONRPCParamSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    id = serializers.IntegerField()

    class Meta:
        model = JSONRPCSubscriptionParam
        fields = (
            'id',
            'name',
            'value',
            'add_only_with_value',
        )


class JSONRPCSubscriptionDataSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField()
    params = JSONRPCParamSerializer(many=True)
    subscription = serializers.PrimaryKeyRelatedField(queryset=ServiceSurveyHookSubscription.objects.all())

    class Meta:
        model = JSONRPCSubscriptionData
        fields = '__all__'


class StartrekSubscriptionDataSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField()
    tags = serializers.JSONField(required=False, allow_null=True)
    followers = serializers.JSONField(required=False, allow_null=True)
    components = serializers.JSONField(required=False, allow_null=True)
    fields = serializers.JSONField(required=False, allow_null=True)
    subscription = serializers.PrimaryKeyRelatedField(queryset=ServiceSurveyHookSubscription.objects.all())

    class Meta:
        model = StartrekSubscriptionData
        fields = '__all__'

    def validate_queue(self, value):
        if value != '':
            if re.match(r'^[A-Z][A-Z0-9]*$', value) is None:
                raise ValidationError(STARTREK_INCORRECT_QUEUE_MESSAGE)
        return value

    def validate_parent(self, value):
        if value != '':
            if re.match(r'^[A-Z][A-Z0-9]*-\d+$', value) is None:
                raise ValidationError(STARTREK_INCORRECT_PARENT_MESSAGE)
        return value

    def get_queue_name(self, parent):
        m = re.search(r'^[A-Z][A-Z0-9]*', parent)
        if m is not None:
            return m.group(0)

    def is_startrek_subscription(self, subscription):
        return subscription.service_type_action.service_type.slug == 'startrek'

    def validate(self, data):
        if data:
            subscription = data.get('subscription')
            if not subscription or self.is_startrek_subscription(subscription):
                self.validate_subscription_data(data)
        return data

    def get_client(self):
        survey = self.context.get('survey')
        if survey and survey.org:
            dir_id = survey.org.dir_id
        else:
            dir_id = None
        return get_startrek_client(dir_id)

    def _get_field_type(self, field_data):
        schema = field_data.get('schema') or {}
        schema_type = schema.get('type')
        schema_items = schema.get('items')
        if schema_type != 'array':
            return schema_type
        return f'{schema_type}/{schema_items}'

    def _get_field_key(self, field_data):
        return {
            'id': field_data.get('id'),
            'slug': field_data.get('key'),
            'name': field_data.get('name'),
            'type': self._get_field_type(field_data),
        }

    def validate_startrek_fields(self, client, queue_name, fields, errors):
        _fields, _errors = [], []
        for field in fields:
            field_key = field.get('key') or {}
            field_id = field_key.get('id') or field_key.get('slug')
            field_data = client.get_field(field_id)
            if field_data is not None:
                if 'queue' in field_data:
                    if field_data['queue']['key'] != queue_name:
                        _errors.append('Поле %(field_id)s из чужой очереди' % {
                            'field_id': field_id,
                        })
                field['key'] = self._get_field_key(field_data)
                _fields.append(field)
            else:
                _errors.append('Поле %(field_id)s не найдено' % {
                    'field_id': field_id,
                })
        if _errors:
            errors['fields'] = _errors

        return _fields

    def validate_subscription_data(self, data):
        errors = {}
        queue_name = data.get('queue') or ''
        parent = data.get('parent') or ''
        if queue_name == '' and parent == '':
            raise ValidationError({'queue': [STARTREK_QUEUE_AND_PARENT_EMPTY_MESSAGE]})

        queue_name = queue_name or self.get_queue_name(parent)

        client = self.get_client()
        queue = client.get_queue(queue_name)
        if queue is None:
            raise ValidationError({'queue': [STARTREK_QUEUE_NOT_EXIST_MESSAGE % queue_name]})

        if parent != '':
            if client.get_issue(parent) is None:
                raise ValidationError({'parent': [STARTREK_PARENT_NOT_EXIST_MESSAGE % parent]})

        issuetype = data.get('type')
        if issuetype is None:
            errors['type'] = [STARTREK_TYPE_EMPTY_MESSAGE]
        elif issuetype not in set(t.get('id') for t in client.get_issuetypes(queue_name) or []):
            errors['type'] = [STARTREK_TYPE_NOT_EXIST_MESSAGE]

        priority = data.get('priority')
        if priority is None:
            errors['priority'] = [STARTREK_PRIORITY_EMPTY_MESSAGE]
        elif priority not in set(p.get('id') for p in client.get_priorities() or []):
            errors['priority'] = [STARTREK_PRIORITY_NOT_EXIST_MESSAGE]

        fields = data.get('fields')
        if fields:
            data['fields'] = self.validate_startrek_fields(client, queue_name, fields, errors)

        if errors:
            raise ValidationError(errors)


class WikiSubscriptionDataSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField()
    supertag = serializers.CharField(required=True, allow_blank=True)
    text = serializers.CharField(required=True, allow_blank=True)
    subscription = serializers.PrimaryKeyRelatedField(queryset=ServiceSurveyHookSubscription.objects.all())

    def is_wiki_subscription(self, subscription):
        return subscription and subscription.service_type_action.service_type.slug == 'wiki'

    def validate(self, data):
        """
        Если data is False - создается первая интегарция на форме
        и это не вики интеграция, проверять данные не нужно
        Если же subscription в data нет - создается новая интеграция
        и это вики интеграция, проверяем, аналогично если subscription
        есть и ее тип wiki
        """
        if data:
            subscription = data.get('subscription')
            if not subscription or self.is_wiki_subscription(subscription):
                supertag = data.get('supertag')
                if supertag is not None:
                    data['supertag'] = self.validate_supertag_value(supertag)
        return data

    def validate_supertag_value(self, value):
        if not value:
            raise ValidationError({'supertag': [EMPTY_VALUE_EXCEPTION_MESSAGE]})

        o = urlparse(value)

        clean_path = o.path.strip('/')
        supertag = clean_path
        if o.fragment:
            supertag = '%s#%s' % (supertag, o.fragment)

        survey = self.context.get('survey')
        dir_id = None
        if survey:
            dir_id = getattr(survey.org, 'dir_id', None)

        client = get_wiki_client(dir_id)
        if not client.is_valid_supertag(supertag):
            raise ValidationError({'supertag': [WIKI_NOT_VALID_SUPERTAG_MESSAGE % (supertag or value)]})

        if not client.is_page_exists(clean_path):
            raise ValidationError({'supertag': [WIKI_PAGE_DOESNT_EXISTS_MESSAGE % supertag]})

        return supertag

    class Meta:
        model = WikiSubscriptionData
        fields = '__all__'


class ExistingFileField(serializers.FileField):
    def to_internal_value(self, data):
        if data in validators.EMPTY_VALUES:
            return None
        data = self.from_url(data)
        meta_info = ProxyStorageModel.objects.get(path=data)
        storage = meta_info.get_original_storage()
        file_obj = super(ExistingFileField, self).to_internal_value(storage.open(data))
        return FieldFile(file_obj, SubscriptionAttachment().file, data)

    def from_url(self, url):
        if url.startswith('http'):
            data = parse_qs(urlparse(url).query).get('path', None)
            if not data:
                return url
            return data[0]
        return url


class SubscriptionAttachmentSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField()
    path = ExistingFileField(source='file')
    meta = serializers.JSONField(source='file_meta_info', read_only=True, required=False)

    class Meta:
        model = SubscriptionAttachment
        fields = (
            'id',
            'path',
            'meta',
        )


class IntegrationFileTemplateSerializer(VariableMixin, serializers.ModelSerializer):
    id = serializers.IntegerField()
    variables = SurveyVariableSerializer(many=True, source='surveyvariable_set')

    class Meta:
        model = IntegrationFileTemplate
        fields = (
            'id',
            'name',
            'template',
            'type',
            'slug',
            'variables',
        )


EMAIL_LIST_ACCEPTED_VARIABLES = [
    UserEmailVariable.name,
    RequestQueryParamVariable.name,
    FormQuestionAnswerVariable.name,
    FormAuthorEmailVariable.name,
    (DirectoryStaffMetaUserVariable.name, DirectoryStaffEmailVariableRenderer.format_name),
    (DirectoryStaffMetaQuestionVariable.name, DirectoryStaffEmailVariableRenderer.format_name),
]
EMAIL_ASNWER_TYPES = {
    'answer_non_profile_email',
    'param_subscribed_email',
}


class ServiceSurveyHookSubscriptionSerializer(VariableMixin, InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    id = serializers.IntegerField()
    date_created = serializers.DateTimeField()
    variables = SurveyVariableSerializer(many=True, allow_null=True, source='surveyvariable_set')
    headers = SubscriptionHeaderSerializer(many=True, allow_null=True)
    attachments = SubscriptionAttachmentSerializer(many=True, allow_null=True)
    attachment_templates = IntegrationFileTemplateSerializer(many=True, allow_null=True)
    json_rpc = JSONRPCSubscriptionDataSerializer(allow_null=True)
    startrek = StartrekSubscriptionDataSerializer(allow_null=True)
    wiki = WikiSubscriptionDataSerializer(allow_null=True)

    class Meta:
        model = ServiceSurveyHookSubscription
        fields = (
            'id',
            'service_type_action',
            'is_synchronous',
            'is_active',
            'context_language',
            'date_created',
            'date_updated',
            'follow_result',

            'title',
            'body',
            'email',
            'phone',

            # email
            'email_to_address',
            'email_from_address',
            'email_from_title',
            'email_spam_check',
            'attachments',

            # http
            'http_url',
            'http_method',
            'http_format_name',
            'tvm2_client_id',

            # json_rpc
            'json_rpc',

            # startrek
            'startrek',

            'variables',
            'headers',
            'questions',
            'is_all_questions',
            'attachment_templates',

            # wiki
            'wiki',
        )

    def _validate_startrek(self, data):
        errors = {}
        if not data.get('title'):
            errors['title'] = [STARTREK_TITLE_EMPTY_MESSAGE]
        return errors

    def validate(self, data):
        errors = {}
        sta = data.get('service_type_action')
        if sta and sta.service_type.slug == 'startrek':
            errors.update(self._validate_startrek(data))

        if sta and sta.service_type.slug == 'email':
            errors.update(self._validate_email(data))

        if sta and sta.service_type.slug == 'http':
            errors.update(self._validate_http(data))

        if errors:
            raise ValidationError(errors)

        return data

    def _validate_http(self, data):
        errors = defaultdict(list)
        if settings.IS_BUSINESS_SITE:
            if data['http_method'] not in ('get', 'post', 'put', 'delete'):
                errors['http_method'].append('Invalid http method')

            parsed_url = urlparse(data['http_url'])
            if parsed_url.scheme not in ('http', 'https'):
                errors['http_url'].append('Invalid url schema')

            if check_if_internal_host(parsed_url.netloc):
                if parsed_url.netloc not in settings.B2B_WHITE_LIST:
                    errors['http_url'].append('Internal host')
        return errors

    def _validate_email(self, data):
        # FORMS-1994

        errors = {}

        email_to_address = data.get('email_to_address') or ''
        if email_to_address:
            field_errors = self._validate_email_list(email_to_address, data)
            if field_errors:
                errors['email_to_address'] = field_errors
        else:
            errors['email_to_address'] = [EMPTY_VALUE_EXCEPTION_MESSAGE]

        email_from_title = data.get('email_from_title') or ''
        if not check_email_title(email_from_title):
            errors['email_from_title'] = [EMAIL_INCORRECT_FROM_TITLE]

        survey = self.context.get('survey')
        if settings.IS_BUSINESS_SITE:
            data['email_from_address'] = f'{survey.pk}@forms-mailer.yaconnect.com'

        headers = []
        for header in data.get('headers') or []:
            header_name = header['name'].lower()
            if header_name == 'reply-to':
                # В UI заголовок Reply-To можно задать через специальное поле email_reply_to,
                # либо через поля headers. При запросе фронтенд кладет Reply-To в headers, но
                # при отображении настроек существующих уведомлений Reply-To всегда отображается
                # в поле email_reply_to. Ошибка тоже всегда показывается над этим полем.
                field_errors = self._validate_email_list(header['value'], data)
                if field_errors:
                    errors['email_reply_to'] = field_errors
            if self._is_valid_header_name(header_name):
                headers.append(header)
        data['headers'] = headers

        return errors

    def _is_valid_header_name(self, header_name):
        if settings.IS_BUSINESS_SITE:
            return header_name == 'reply-to'
        return True

    def _validate_email_list(self, value, data):
        # FORMS-1994
        #
        # В поле email_to_address и заголовке Reply-To можно ввести список email,
        # чередуя через запятую либо точку с запятой переменные и строки с email.
        #
        # Переменные могут быть следующих типов:
        # - Данные пользователя | Электронная почта
        # - Запрос | GET-параметр
        # - Ответ на вопрос (только вопросы типа "Почта")
        #
        # В итоге в этих полях может быть что-то такое:
        #
        # {5aa8fc207de3dc9630b93ba3},foo@bar.com,o-gulyaev@yandex-team.ru,{5aa8fc207de3dc9630b93ba5},{5aa8fc207de3dc9630b93ba4},example@web.org

        if not settings.IS_BUSINESS_SITE:
            return
        variables = {
            var['variable_id']: var
            for var in data.get('surveyvariable_set') or []
        }
        for item in re.split(r'[,;]\s*', value):
            if item.startswith('{'):
                var_id = item[1:-1]
                var = variables.get(var_id)

                if var is None:
                    return [NO_SUCH_VARIABLE_MESSAGE % {'var_id': var_id}]

                if not self._is_valid_variable_type(var):
                    return [EMAIL_INCORRECT_VARIABLE_TYPE_MESSAGE % {'var_id': var_id}]

                if var['var'] == FormQuestionAnswerVariable.name:
                    question_id = var['arguments']['question']
                    try:
                        question = SurveyQuestion.objects.get(id=question_id)
                    except SurveyQuestion.DoesNotExist:
                        return ['No such question']
                    if question.answer_type.slug not in EMAIL_ASNWER_TYPES:
                        return [EMAIL_INCORRECT_QUESTION_TYPE_MESSAGE % {'var_id': var_id}]
            else:
                try:
                    validators.validate_email(item)
                except DjangoValidationError:
                    return [EMAIL_INCORRECT_EMAIL_MESSAGE % {'email': item}]
        data['surveyvariable_set'] = variables.values()

    def _is_valid_variable_type(self, var):
        for v in EMAIL_LIST_ACCEPTED_VARIABLES:
            if isinstance(v, str):
                if var['var'] == v:
                    return True
            elif isinstance(v, tuple):
                var_name, format_name = v
                if var['var'] == var_name and var['format_name'] == format_name:
                    return True
        return False


class SurveyHookConditionSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    id = serializers.IntegerField()

    class Meta:
        model = SurveyHookCondition
        fields = (
            'id',
            'operator',
            'condition',
            'value',
            'position',
            'content_type_attribute',
            'survey_question'
        )


class SurveyHookConditionNodeSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    id = serializers.IntegerField()
    items = SurveyHookConditionSerializer(many=True)

    class Meta:
        model = SurveyHookConditionNode
        fields = (
            'id',
            'items',
        )


class SurveyHookSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    id = serializers.IntegerField()
    condition_nodes = SurveyHookConditionNodeSerializer(many=True)
    subscriptions = ServiceSurveyHookSubscriptionSerializer(many=True)

    class Meta:
        model = SurveyHook
        fields = (
            'id',
            'is_active',
            'name',
            'triggers',
            'condition_nodes',
            'subscriptions'
        )


class SurveyListSurveyHookSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    id = serializers.IntegerField()
    subscriptions = ServiceSurveyHookSubscriptionSerializer(many=True)

    class Meta:
        model = SurveyHook
        fields = (
            'id',
            'is_active',
            'name',
            'subscriptions'
        )


class TicketSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    id = serializers.IntegerField()
    quantity = serializers.IntegerField(required=False, allow_null=True)

    class Meta:
        model = Ticket
        fields = (
            'id',
            'price',
            'name',
            'quantity',
            'is_can_pay_by_yandex_money',
            'trust_return_path',
            'trust_unsuccessful_return_path',
            'currency',
        )


class SurveyListSerializer(TranslationSerializerMixin, InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    content_type_id = serializers.IntegerField(required=False, allow_null=True)
    group = SurveyGroupListSerializer(required=False, allow_null=True)
    generated_title = serializers.CharField(source='name', read_only=True)
    status = serializers.CharField(source='publication_status', read_only=True)
    org_id = OrganizationField(source='org', slug_field='dir_id', allow_null=True, required=False)
    notifications_count = serializers.SerializerMethodField()
    answers_count = serializers.IntegerField(source='answercount.count', read_only=True)
    profile = UserUidOnlySerializer(read_only=True, source='user')
    perm_status = serializers.SerializerMethodField()

    def get_notifications_count(self, obj):
        return 0

    def get_perm_status(self, survey):
        request = self.context.get('request')
        if request and survey.user and request.user.pk == survey.user.pk:
            return 'owner'
        return 'restricted'

    class Meta:
        model = Survey
        fields = (
            'id',
            'name',
            'type',
            'content_type_id',
            'object_id',
            'group',
            'generated_title',
            'status',
            'org_id',
            'notifications_count',
            'answers_count',
            'profile',
            'date_created',
            'date_updated',
            'date_published',
            'date_unpublished',
            'date_archived',
            'perm_status',
        )


class SurveyShortSerializer(SurveyListSerializer):
    group_id = serializers.IntegerField(required=False, allow_null=True)
    group = SurveyGroupSerializer(required=False, allow_null=True)
    profile = UserFollowerSerializer(read_only=True, source='user')
    is_ban_detected = serializers.NullBooleanField(read_only=True)
    redirect = RedirectSerializer(required=False, source='extra.redirect', default=None, allow_null=True)
    footer = FooterSerializer(required=False, source='extra.footer', default=None, allow_null=True)
    teaser = TeaserSerializer(required=False, source='extra.teaser', default=None, allow_null=True)
    quiz = QuizSerializer(required=False, source='extra.quiz', default=None, allow_null=True)
    stats = StatsSerializer(required=False, source='extra.stats', default=None, allow_null=True)

    class Meta:
        model = Survey
        fields = (
            'id',
            'name',
            'type',
            'content_type_id',
            'object_id',
            'group_id',
            'group',
            'generated_title',
            'is_published_external',
            'is_public',
            'status',
            'org_id',
            'notifications_count',
            'maximum_answers_count',
            'answers_count',
            'profile',
            'date_created',
            'date_updated',
            'date_published',
            'date_unpublished',
            'date_archived',
            'datetime_auto_open',
            'datetime_auto_close',
            'perm_status',
            'auto_control_publication_status',
            'language',
            'is_ban_detected',
            'hashed_id',
            'redirect',
            'footer',
            'teaser',
            'quiz',
            'stats',
        )


class SurveyDetailSerializer(SurveyShortSerializer):
    group = SurveyGroupSerializer(required=False, allow_null=True)
    group_id = serializers.IntegerField(required=False, allow_null=True)
    content_type = ContentTypeSerializer(required=False, allow_null=True)
    content_object = SurveyContentObjectField(required=False, read_only=True)
    state_nodes = SurveyStateConditionNodeSerializer(
        source='surveystateconditionnode_set',
        many=True,
        required=False,
        allow_null=True,
    )
    tickets_info = TicketSerializer(required=False, allow_null=True)
    is_ready_for_answer = serializers.BooleanField(read_only=True)
    hooks = SurveyHookSerializer(
        many=True,
        required=False,
        allow_null=True,
    )
    free_tickets_count = serializers.IntegerField(read_only=True)
    integration_file_templates = IntegrationFileTemplateSerializer(many=True)
    validator_url = serializers.CharField(required=False, allow_null=True)
    profile = UserFollowerSerializer(read_only=True, source='user')
    styles_template = SurveyStyleTemplateSerializer(read_only=True)
    styles_template_id = serializers.IntegerField(required=False, allow_null=True)
    yt_url = serializers.SerializerMethodField()
    export_answers_to_yt = serializers.BooleanField(source='save_logs_for_statbox', required=False)
    follow_type = serializers.ChoiceField(required=False, allow_null=True, choices=FOLLOW_TYPE_CHOICES)
    languages = serializers.JSONField(required=False, allow_null=True)

    class Meta:
        model = Survey
        fields = [
            'id',
            'name',
            'type',
            'need_auth_to_answer',
            'is_allow_answer_editing',
            'is_allow_multiple_answers',
            'is_allow_answer_versioning',
            'is_only_for_iframe',
            'is_remove_answer_after_integration',
            'is_send_user_notification_email_message',
            'user_notification_email_message_subject',
            'user_notification_email_message_text',
            'user_notification_email_frontend_name',
            'content_type',
            'content_type_id',
            'content_object',
            'object_id',
            'agreements',
            'state_nodes',
            'hooks',
            'tickets_info',
            'is_published_external',
            'is_public',
            'is_ready_for_answer',
            'group_id',
            'group',
            'generated_title',
            'is_recent_data_is_sended_to_localize',
            'metrika_counter_code',
            'captcha_display_mode',
            'slug',
            'maximum_answers_count',
            'free_tickets_count',
            'auto_control_publication_status',
            'datetime_auto_open',
            'datetime_auto_close',
            'integration_file_templates',
            'validator_url',
            'profile',
            'notifications_count',
            'answers_count',
            'date_created',
            'date_updated',
            'date_published',
            'date_unpublished',
            'date_archived',
            'perm_status',
            'auto_control_publication_status',
            'styles_template',
            'styles_template_id',
            'org_id',
            'language',
            'is_ban_detected',
            'hashed_id',
            'redirect',
            'footer',
            'teaser',
            'quiz',
            'stats',
            'yt_url',
            'export_answers_to_yt',
            'follow_type',
            'languages',
        ]

    def get_yt_url(self, obj):
        from events.common_app.yt import utils
        if obj.save_logs_for_statbox and not settings.IS_BUSINESS_SITE:
            return utils.get_yt_url(obj.pk)

    def validate_is_published_external(self, value):
        instance = getattr(self, 'instance', None)
        if instance and instance.is_ban_detected and value:
            raise ValidationError({'is_published_external': [_('Нельзя опубликовать забаненную форму. Обратитесь в службу поддержки.')]})
        return value

    def validate_org_id(self, value):
        instance = getattr(self, 'instance', None)
        if instance and instance.org_id:
            raise ValidationError('Changing org for form in organization is prohibited')
        return value

    def update_questions(self, subscription_data):
        found = 'questions' in subscription_data
        data = subscription_data.pop('questions', None) or []
        def wrapper(subscription):
            subscription.questions.set(data)
        return wrapper, found

    def update_headers(self, subscription_data):
        found = 'headers' in subscription_data
        data = subscription_data.pop('headers', None) or []
        def wrapper(subscription):
            SubscriptionHeader.objects.filter(subscription=subscription).delete()
            for item in data:
                if 'subscription' not in item:
                    item['subscription'] = subscription
                header = SubscriptionHeader(**item)
                header.save()
        return wrapper, found

    def update_attachments(self, subscription_data):
        found = 'attachments' in subscription_data
        data = subscription_data.pop('attachments', None) or []
        def wrapper(subscription):
            SubscriptionAttachment.objects.filter(subscription=subscription).delete()
            for item in data:
                if 'subscription' not in item:
                    item['subscription'] = subscription
                attachment = SubscriptionAttachment(**item)
                attachment.save()
        return wrapper, found

    def update_attachment_templates(self, subscription_data):
        found = 'attachment_templates' in subscription_data
        data = subscription_data.pop('attachment_templates', None) or []
        def wrapper(subscription):
            slugs = [it.get('slug') for it in data if 'slug' in it]
            survey = subscription.survey_hook.survey
            templates = survey.integration_file_templates.filter(slug__in=slugs)
            subscription.attachment_templates.set(templates)
        return wrapper, found

    def update_wiki(self, subscription_data):
        found = 'wiki' in subscription_data
        data = subscription_data.pop('wiki', None) or {}
        def wrapper(subscription):
            if 'subscription' not in data:
                data['subscription'] = subscription
            wiki = WikiSubscriptionData(**data)
            wiki.save()
        return wrapper, found

    def update_startrek(self, subscription_data):
        found = 'startrek' in subscription_data
        data = subscription_data.pop('startrek', None) or {}
        def wrapper(subscription):
            if 'subscription' not in data:
                data['subscription'] = subscription
            startrek = StartrekSubscriptionData(**data)
            startrek.save()
        return wrapper, found

    def update_json_rpc(self, subscription_data):
        found = 'json_rpc' in subscription_data
        data = subscription_data.pop('json_rpc', None) or {}
        params = data.pop('params', None) or []
        def wrapper(subscription):
            if 'subscription' not in data:
                data['subscription'] = subscription
            json = JSONRPCSubscriptionData(**data)
            json.save()
            JSONRPCSubscriptionParam.objects.filter(subscription=json).delete()
            for item in params:
                if 'subscription' not in item:
                    item['subscription'] = json
                json_param = JSONRPCSubscriptionParam(**item)
                json_param.save()
        return wrapper, found

    def update_subscription(self, subscription_data):
        data = subscription_data

        def wrapper(hook):
            update_questions, update_questions_found = self.update_questions(data)
            update_headers, update_headers_found = self.update_headers(data)
            update_attachments, update_attachments_found = self.update_attachments(data)
            update_attachment_templates, update_attachment_templates_found = self.update_attachment_templates(data)
            update_wiki, update_wiki_found = self.update_wiki(data)
            update_startrek, update_startrek_found = self.update_startrek(data)
            update_json_rpc, update_json_rpc_found = self.update_json_rpc(data)
            update_variables, update_variables_found = self.update_variables(data, 'hook_subscription_id')

            if 'survey_hook' not in data:
                data['survey_hook'] = hook
            subscription = ServiceSurveyHookSubscription(**data)
            subscription.save()

            if update_questions_found:
                update_questions(subscription)
            if update_headers_found:
                update_headers(subscription)
            if update_attachments_found:
                update_attachments(subscription)
            if update_attachment_templates_found:
                update_attachment_templates(subscription)
            if update_wiki_found:
                update_wiki(subscription)
            if update_startrek_found:
                update_startrek(subscription)
            if update_json_rpc_found:
                update_json_rpc(subscription)
            if update_variables_found:
                update_variables(subscription)

            return subscription.pk
        return wrapper

    def update_variables(self, data, related_obj_key):
        found = 'surveyvariable_set' in data
        variables_data = data.pop('surveyvariable_set', None) or []

        def wrapper(related_obj):
            variables_to_stay = set()
            for item in variables_data:
                variables_to_stay.add(item['variable_id'])
                self.update_variable(item, related_obj, related_obj_key)
            (SurveyVariable.objects
             .filter(**{related_obj_key: related_obj, })
             .exclude(variable_id__in=variables_to_stay)
             .delete()
             )

        return wrapper, found

    def update_variable(self, variable, related_obj, related_obj_key):
        data = variable
        data[related_obj_key] = related_obj.id
        survey_variable = SurveyVariable(**data)
        survey_variable.save()
        return survey_variable.pk

    def update_subscriptions(self, hook_data):
        found = 'subscriptions' in hook_data
        data = hook_data.pop('subscriptions', [])

        def wrapper(hook):
            for item in data:
                update_subscription = self.update_subscription(item)
                yield update_subscription(hook)
        return wrapper, found

    def update_condition_node(self, condition_node):
        data = condition_node
        items = data.pop('items', [])
        def wrapper(hook):
            if 'hook' not in data:
                data['hook'] = hook
            condition_node = SurveyHookConditionNode(**data)
            condition_node.save()
            SurveyHookCondition.objects.filter(condition_node=condition_node).delete()
            for item in items:
                if 'condition_node' not in item:
                    item['condition_node'] = condition_node
                condition_node_item = SurveyHookCondition(**item)
                condition_node_item.save()

            return condition_node.pk
        return wrapper

    def update_condition_nodes(self, hook_data):
        found = 'condition_nodes' in hook_data
        data = hook_data.pop('condition_nodes', [])
        def wrapper(hook):
            for item in data:
                update_condition_node = self.update_condition_node(item)
                yield update_condition_node(hook)
        return wrapper, found

    def update_triggers(self, hook_data):
        found = 'triggers' in hook_data
        data = hook_data.pop('triggers', [])
        def wrapper(hook):
            hook.triggers.set(data)
        return wrapper, found

    def update_hook(self, hook_data):
        data = hook_data
        def wrapper(instance):
            update_subscriptions, update_subscriptions_found = self.update_subscriptions(data)
            update_condition_nodes, update_condition_nodes_found = self.update_condition_nodes(data)
            update_triggers, update_triggers_found = self.update_triggers(data)

            if 'survey' not in data:
                data['survey'] = instance
            hook = SurveyHook(**data)
            hook.save()

            if update_subscriptions_found:
                old_subscriptions = set(hook.subscriptions.values_list('pk', flat=True))
                subscriptions = set(update_subscriptions(hook))
                hook.subscriptions.filter(pk__in=old_subscriptions - subscriptions).delete()

            if update_condition_nodes_found:
                old_condition_nodes = set(hook.condition_nodes.values_list('pk', flat=True))
                condition_nodes = set(update_condition_nodes(hook))
                hook.condition_nodes.filter(pk__in=old_condition_nodes - condition_nodes).delete()

            if update_triggers_found:
                update_triggers(hook)

            return hook.pk
        return wrapper

    def update_hooks(self, validated_data):
        found = 'hooks' in validated_data
        data = validated_data.pop('hooks', [])
        def wrapper(instance):
            for item in data:
                update_hook = self.update_hook(item)
                yield update_hook(instance)
        return wrapper, found

    def update_state_node(self, state_data):
        data = state_data
        items = data.pop('items', [])
        def wrapper(instance):
            if 'survey' not in data:
                data['survey'] = instance
            node = SurveyStateConditionNode(**data)
            node.save()
            for item in items:
                if 'node' not in item:
                    item['node'] = node
                node_item = SurveyStateConditionItem(**item)
                node_item.save()

            return node.pk
        return wrapper

    def update_state_nodes(self, validated_data):
        found = 'surveystateconditionnode_set' in validated_data
        data = validated_data.pop('surveystateconditionnode_set', [])
        def wrapper(instance):
            for item in data:
                update_state_node = self.update_state_node(item)
                yield update_state_node(instance)
        return wrapper, found

    def update_integration_file_templates(self, validated_data):
        found = 'integration_file_templates' in validated_data
        data = validated_data.pop('integration_file_templates', [])

        def wrapper(instance):
            for item in data:
                if 'survey' not in item:
                    item['survey'] = instance
                update_variables, update_variables_found = self.update_variables(item, 'integration_file_template_id')
                template = IntegrationFileTemplate(**item)
                template.save()
                if update_variables_found:
                    update_variables(template)
                yield template.pk
        return wrapper, found

    def update_tickets_info(self, validated_data):
        data = validated_data.pop('tickets_info', None)
        if data is not None:
            def wrapper(instance):
                data['survey'] = instance
                tickets_info = Ticket(**data)
                tickets_info.save()
            return wrapper

    def update_styles_template(self, validated_data):
        found = 'styles_template_id' in validated_data
        data = validated_data.pop('styles_template_id', None)
        def wrapper(instance):
            if instance.styles_template_id != data:
                if data is not None:
                    try:
                        instance.styles_template = SurveyStyleTemplate.objects.get(pk=data)
                    except SurveyStyleTemplate.DoesNotExist:
                        raise ValidationError({'styles_template_id': [STYLES_TEMPLATE_DOES_NOT_EXIST]})
                else:
                    instance.styles_template = None
        return wrapper, found

    def update_extra(self, instance, validated_data):
        if validated_data is not None:
            data = instance.extra or {}
            data.update(validated_data)
            instance.extra = data
        else:
            instance.extra = None

    def update(self, instance, validated_data):
        update_hooks, update_hooks_found = self.update_hooks(validated_data)
        update_state_nodes, update_state_found = self.update_state_nodes(validated_data)
        update_integration_file_templates, templates_found = self.update_integration_file_templates(validated_data)
        update_tickets_info = self.update_tickets_info(validated_data)
        update_styles_template, styles_template_found = self.update_styles_template(validated_data)

        if templates_found:
            old_templates = set(instance.integration_file_templates.values_list('pk', flat=True))
            templates = set(update_integration_file_templates(instance))
            instance.integration_file_templates.filter(pk__in=old_templates - templates).delete()

        if update_hooks_found:
            old_hooks = set(instance.hooks.values_list('pk', flat=True))
            hooks = set(update_hooks(instance))
            instance.hooks.filter(pk__in=old_hooks - hooks).delete()

        if update_state_found:
            old_state_nodes = set(instance.surveystateconditionnode_set.values_list('pk', flat=True))
            state_nodes = set(update_state_nodes(instance))
            instance.surveystateconditionnode_set.filter(pk__in=old_state_nodes - state_nodes).delete()

        if update_tickets_info:
            update_tickets_info(instance)

        if styles_template_found:
            update_styles_template(instance)

        if validated_data.get('is_allow_answer_editing'):
            validated_data['is_allow_multiple_answers'] = False

        if validated_data.get('is_allow_answer_versioning'):
            validated_data['is_allow_multiple_answers'] = True
            validated_data['is_allow_answer_editing'] = True

        if 'extra' in validated_data:
            extra_data = validated_data.pop('extra')
            self.update_extra(instance, extra_data)

        return super(SurveyDetailSerializer, self).update(instance, validated_data)


class SurveyQuestionChoiceSerializer(TranslationSerializerMixin,
                                     InternalModelSerializerV2Mixin,
                                     serializers.ModelSerializer):
    id = serializers.IntegerField(required=False)
    survey_question_id = serializers.IntegerField(required=False)
    label_image = ImageSerializer(required=False, allow_null=True)

    class Meta:
        model = SurveyQuestionChoice
        fields = [
            'id',
            'survey_question_id',
            'position',
            'slug',
            'is_hidden',
            'label_image',
            'label',
        ]


class SurveyQuestionChoiceCopySerializer(SurveyQuestionChoiceSerializer, serializers.ModelSerializer):
    class Meta:
        model = SurveyQuestionChoice
        fields = [
            'id',
            'label',
            'survey_question_id',
            'label_image',
        ]


class SurveyQuestionMatrixTitleSerializer(TranslationSerializerMixin,
                                          InternalModelSerializerV2Mixin,
                                          serializers.ModelSerializer):
    id = serializers.IntegerField(required=False)
    survey_question_id = serializers.IntegerField(required=False)

    class Meta:
        model = SurveyQuestionMatrixTitle
        fields = [
            'id',
            'survey_question_id',
            'position',
            'type',
            'label',
        ]


class SurveyQuestionMatrixTitleCopySerializer(SurveyQuestionMatrixTitleSerializer, serializers.ModelSerializer):
    class Meta:
        model = SurveyQuestionMatrixTitle
        fields = [
            'id',
            'position',
            'survey_question_id',
            'type',
            'label',
        ]


class ParamMaxValidator(object):
    def __init__(self, param_min_field, message=None):
        self.param_min_field = param_min_field
        self.message = message

    def set_context(self, serializer_field):
        self.serializer_field = serializer_field

    def __call__(self, value):
        param_max = value
        serializer = self.serializer_field.parent
        raw_param_min = serializer.initial_data.get(self.param_min_field)

        try:
            param_min = serializer.fields[self.param_min_field].run_validation(raw_param_min)
        except ValidationError:
            return

        if (param_min is not None) and (param_max is not None) and param_min > param_max:
            message = self.message or _('%(param_max_field)s не должен быть меньше %(param_min_field)s')
            raise ValidationError(message % {
                'param_max_field': self.serializer_field.field_name,
                'param_min_field': self.param_min_field,
            })


class ParamQuizAnswerSerializer(serializers.Serializer):
    scores = serializers.FloatField(default=0.0)
    correct = serializers.BooleanField(default=True)
    value = serializers.CharField(max_length=1000)

    def validate(self, data):
        if 'value' not in data or not data['value']:
            raise ValidationError({'value': _('This field is required.')})
        scores = round(data.get('scores') or 0)
        if abs(scores) > QUIZ_MAX_SCORES:
            raise ValidationError({'scores': _('Too big scores.')})
        return data


class ParamQuizSerializer(serializers.Serializer):
    enabled = serializers.BooleanField(default=False)
    required = serializers.BooleanField(default=False)
    answers = ParamQuizAnswerSerializer(many=True, default=[])

    def validate(self, data):
        instance = self.instance
        if not instance and self.parent:
            instance = self.parent.instance

        if instance and instance.param_is_allow_multiple_choice:
            if 'required' not in data:
                data['required'] = True
        return data


class SurveyQuestionSerializer(TranslationSerializerMixin,
                               InternalModelSerializerV2Mixin,
                               serializers.ModelSerializer):
    variables = WritableField(required=False, allow_null=True)
    answer_type_id = serializers.IntegerField()
    param_hint_type_id = serializers.IntegerField(required=False, allow_null=True)
    survey_id = IdentifierField()
    group_id = serializers.IntegerField(required=False, allow_null=True)
    answer_type = AnswerTypeSerializer(required=False, allow_null=True)
    validator_type_id = serializers.IntegerField(required=False, allow_null=True)
    validator_type = ValidatorTypeSerializer(required=False, allow_null=True)
    validator_options = serializers.JSONField(required=False, allow_null=True)
    choices = SurveyQuestionChoiceSerializer(
        many=True,
        source='surveyquestionchoice_set',
        required=False,
        allow_null=True
    )
    matrix_titles = SurveyQuestionMatrixTitleSerializer(
        many=True,
        source='surveyquestionmatrixtitle_set',
        required=False,
        allow_null=True
    )
    param_data_source_params = serializers.JSONField(required=False, allow_null=True)
    param_hint_data_source_params = serializers.JSONField(required=False, allow_null=True)
    param_date_field_type = serializers.CharField(required=False, allow_blank=True)
    param_date_field_min = serializers.DateField(required=False, allow_null=True, input_formats=DATETIME_INPUT_FORMATS)
    param_date_field_max = serializers.DateField(required=False, allow_null=True, input_formats=DATETIME_INPUT_FORMATS,
                                                 validators=[ParamMaxValidator('param_date_field_min')])
    param_min = serializers.IntegerField(required=False, allow_null=True,
                                         min_value=-settings.MAX_POSTGRESQL_INT_VALUE,
                                         max_value=settings.MAX_POSTGRESQL_INT_VALUE)
    param_max = serializers.IntegerField(required=False, allow_null=True,
                                         min_value=-settings.MAX_POSTGRESQL_INT_VALUE,
                                         max_value=settings.MAX_POSTGRESQL_INT_VALUE,
                                         validators=[ParamMaxValidator('param_min')])
    param_payment = serializers.JSONField(required=False, allow_null=True)
    param_quiz = ParamQuizSerializer(required=False, allow_null=True)
    initial = serializers.JSONField(required=False, allow_null=True)
    label_image = ImageSerializer(required=False, allow_null=True)

    class Meta:
        model = SurveyQuestion
        fields = [
            'id',
            'choices',
            'survey_id',
            'answer_type_id',
            'answer_type',
            'validator_type_id',
            'validator_type',
            'validator_options',
            'position',
            'page',
            'initial',
            'param_is_required',
            'param_is_allow_multiple_choice',
            'param_is_allow_other',
            'param_max',
            'param_min',
            'param_is_section_header',
            'param_max_file_size',
            'param_price',
            'param_variables',
            'variables',
            'param_widget',
            'param_is_hidden',
            'param_slug',
            'param_hint_type_id',
            'param_data_source',
            'param_data_source_params',
            'param_hint_data_source',
            'param_hint_data_source_params',
            'param_is_random_choices_position',
            'param_modify_choices',
            'param_max_files_count',
            'param_is_disabled_init_item',
            'param_date_field_type',
            'param_date_field_min',
            'param_date_field_max',
            'matrix_titles',
            'param_suggest_choices',
            'param_has_logic',
            'param_payment',
            'param_quiz',
            'group_id',
            'label_image',
            'label',
            'param_help_text',
        ]

    def get_none(self, obj):
        return None

    def get_related_field_name(self, model):
        for f in model._meta.fields:
            if isinstance(f, ForeignKey) and f.related_model is self.Meta.model:
                return f.column

    def update_field(self, field_name, parent_name):
        def updater(validated_data):
            found = field_name in validated_data
            data = validated_data.pop(field_name, [])
            def wrapper(instance):
                lang, _ = get_lang_with_fallback()
                default_language = instance.get_default_language()
                model = getattr(instance, field_name).model
                for item in data:
                    if parent_name not in item:
                        item[parent_name] = instance.pk
                    existed_obj_id = item.get('id')

                    updated_item = {
                        name: value
                        for name, value in item.items()
                        if name not in model.FIELDS_FOR_TRANSLATION or lang == default_language
                    }
                    if existed_obj_id:
                        obj = model.objects.get(pk=existed_obj_id)
                        for name, value in updated_item.items():
                            setattr(obj, name, value)
                    else:
                        obj = model(**updated_item)

                    for name in model.FIELDS_FOR_TRANSLATION:
                        if name in item:
                            obj.set_translated_field(name, item[name])

                    obj.save()
                    yield obj.pk
            return wrapper, found
        return updater

    def update_param_quiz(self, validated_data):
        found = 'param_quiz' in validated_data
        data = validated_data.pop('param_quiz', None)
        def wrapper(instance):
            instance.param_quiz = data
        return wrapper, found

    def create(self, validated_data):
        make_choices = self.update_field('surveyquestionchoice_set', 'survey_question_id')
        make_matrix_titles = self.update_field('surveyquestionmatrixtitle_set', 'survey_question_id')
        update_param_quiz, param_quiz_found = self.update_param_quiz(validated_data)

        update_choices, choices_found = make_choices(validated_data)
        update_matrix_titles, matrix_titles_found = make_matrix_titles(validated_data)

        instance = super(SurveyQuestionSerializer, self).create(validated_data)

        if choices_found:
            list(update_choices(instance))

        if matrix_titles_found:
            list(update_matrix_titles(instance))

        if param_quiz_found:
            update_param_quiz(instance)
            instance.save()

        return instance

    def get_answer_type(self, answer_type_id):
        try:
            return AnswerType.objects.get(pk=answer_type_id)
        except AnswerType.DoesNotExist:
            raise ValidationError(_('answer_type_doesnt_exist'))

    def _validate_field_lengths(self, data):
        answer_type = None
        if self.instance:
            answer_type = self.instance.answer_type
        else:
            answer_type = self.get_answer_type(
                data.get(
                    'answer_type_id'
                )
            )
        if answer_type and answer_type.slug != 'answer_statement':
            if len(data.get('label', '')) > 5000:
                raise ValidationError(
                    {
                        'label': _('field_length_exceeded') % {
                            'limit': 5000
                        }
                    }
                )
            if len(data.get('param_help_text', '')) > 5000:
                raise ValidationError(
                    {
                        'param_help_text': _('field_length_exceeded') % {
                            'limit': 5000
                        }
                    }
                )

    def validate_answer_type_id(self, answer_type_id):
        answer_type = self.get_answer_type(answer_type_id)

        if answer_type.slug == 'answer_payment':
            survey_id = self.initial_data.get('survey_id')
            if SurveyQuestion.objects.check_answer_payment_exists(survey_id):
                raise ValidationError(_('answer_payment_alredy_exists'))

            group_id = self.initial_data.get('group_id')
            if group_id:
                raise ValidationError(_('answer_payment_cant_be_part_of_group'))

            self.initial_data['param_min'] = settings.YOOMONEY_MIN_AMOUNT
            self.initial_data['param_max'] = settings.YOOMONEY_MAX_AMOUNT

            if 'param_payment' not in self.initial_data:
                self.initial_data['param_payment'] = {
                    'account_id': None,
                    'is_fixed': False,
                }
        elif answer_type.slug == 'answer_group':
            self.initial_data['param_is_required'] = False

        return answer_type_id

    def validate_param_is_required(self, param_is_required):
        if self.instance:
            if self.instance.answer_type.slug == 'answer_group':
                return False
        return param_is_required

    def validate_account_id(self, account_id):
        if account_id is not None:
            if not str(account_id).isdigit():
                raise ValidationError({'account_id': _('account_id_doesnt_exist')})
            if not (10 < len(str(account_id)) < 30):
                raise ValidationError({'account_id': _('account_id_doesnt_exist')})
        return account_id

    def validate_param_payment(self, param_payment):
        if param_payment is not None:
            self.validate_account_id(param_payment.get('account_id'))
            if 'is_fixed' not in param_payment:
                param_payment['is_fixed'] = False
        return param_payment

    def validate_group_id(self, group_id):
        if self.instance and group_id:
            if self.instance.answer_type.slug == 'answer_payment':
                raise ValidationError(_('answer_payment_cant_be_part_of_group'))
            conditions_in_other_group = (
                SurveyQuestionShowConditionNodeItem.objects
                .filter(
                    survey_question_show_condition_node__survey_question_id=self.instance.pk,
                    survey_question__group_id__isnull=False,
                )
                .exclude(survey_question__group_id=group_id)
            )
            if conditions_in_other_group.exists():
                raise ValidationError(ANOTHER_GROUP_QUESTION_IN_LOGIC)

            conditions_on_show_question = (
                SurveyQuestionShowConditionNodeItem.objects.all()
                .filter(survey_question_id=self.instance.pk)
                .exclude(
                    survey_question_show_condition_node__survey_question__group_id=group_id,
                )
            )
            if conditions_on_show_question.exists():
                raise ValidationError(GROUP_QUESTION_CANT_USE_IN_SHOW_QUESTION_LOGIC)

            conditions_on_survey_hook = (SurveyHookCondition.objects
                                         .filter(survey_question_id=self.instance.pk))
            if conditions_on_survey_hook.exists():
                raise ValidationError(GROUP_QUESTION_CANT_USE_IN_SURVEY_HOOK_LOGIC)
        return group_id

    def get_survey_id(self):
        if self.instance:
            return self.instance.survey_id
        return self.initial_data.get('survey_id')

    def validate_param_slug(self, param_slug):
        queryset = (
            SurveyQuestion.objects.all()
            .filter(survey_id=self.get_survey_id(), param_slug=param_slug)
        )
        if self.instance:
            queryset = queryset.exclude(pk=self.instance.pk)
        if queryset.exists():
            raise ValidationError(f'Question slug {param_slug} already exists')
        return param_slug

    def validate(self, data):
        self._validate_field_lengths(data)
        validator_type_id = data.get('validator_type_id', None)
        if validator_type_id:
            validator_type = ValidatorType.objects.get(pk=data['validator_type_id'])
            validator_class = settings.SURVEYME_VALIDATORS.get(validator_type.slug)

            if validator_class and len(validator_class.required_options):
                if 'validator_options' not in data:
                    raise ValidationError(
                        "Для типа валидации '%s' требуется еще поле validator_options" % validator_type.name)

                for option in validator_class.required_options:
                    if option not in data['validator_options']:
                        message = (
                            'Недостаточное количество элементов в параметре "validator_options".\n'
                            'Нужные элементы для валидатора "{0}" - {1}'
                            .format(validator_type.name, ', '.join(validator_class.required_options))
                        )
                        raise ValidationError(message)

        return data

    def update(self, instance, validated_data):
        make_choices = self.update_field('surveyquestionchoice_set', 'survey_question_id')
        make_matrix_titles = self.update_field('surveyquestionmatrixtitle_set', 'survey_question_id')

        update_choices, choices_found = make_choices(validated_data)
        update_matrix_titles, matrix_titles_found = make_matrix_titles(validated_data)

        if choices_found:
            old_choices = set(instance.surveyquestionchoice_set.values_list('pk', flat=True))
            choices = set(update_choices(instance))
            instance.surveyquestionchoice_set.filter(pk__in=old_choices - choices).update(
                is_deleted=True,
                slug=F('pk'),
            )

        if matrix_titles_found:
            old_matrix_titles = set(instance.surveyquestionmatrixtitle_set.values_list('pk', flat=True))
            matrix_titles = set(update_matrix_titles(instance))
            instance.surveyquestionmatrixtitle_set.filter(pk__in=old_matrix_titles - matrix_titles).update(
                is_deleted=True,
            )
        return super(SurveyQuestionSerializer, self).update(instance, validated_data)


class SurveyQuestionCopySerializer(SurveyQuestionSerializer, serializers.ModelSerializer):
    choices = SurveyQuestionChoiceCopySerializer(
        many=True,
        source='surveyquestionchoice_set',
        required=False,
        allow_null=True
    )
    matrix_titles = SurveyQuestionMatrixTitleCopySerializer(
        many=True,
        source='surveyquestionmatrixtitle_set',
        required=False,
        allow_null=True
    )
    answer_type = AnswerTypeCopySerializer(required=False, allow_null=True)

    class Meta:
        model = SurveyQuestion
        fields = [
            'id',
            'choices',
            'answer_type_id',
            'answer_type',
            'validator_options',
            'validator_type_id',
            'validator_type',
            'param_is_required',
            'param_is_allow_multiple_choice',
            'param_is_allow_other',
            'param_max',
            'param_min',
            'param_is_section_header',
            'param_max_file_size',
            'param_is_hidden',
            'param_slug',
            'param_hint_type_id',
            'param_data_source',
            'param_data_source_params',
            'param_hint_data_source',
            'param_hint_data_source_params',
            'param_is_random_choices_position',
            'param_modify_choices',
            'param_max_files_count',
            'param_is_disabled_init_item',
            'param_date_field_type',
            'param_date_field_min',
            'param_date_field_max',
            'matrix_titles',
            'param_suggest_choices',
            'param_has_logic',
            'group_id',
            'label_image',
            'param_help_text',
            'label',
            'param_quiz',
        ]


class SurveyQuestionForStatSerializer(serializers.ModelSerializer):
    answer_type_id = serializers.IntegerField()
    group_id = serializers.IntegerField(allow_null=True)
    answer_type_slug = serializers.SlugRelatedField(
        source='answer_type', slug_field='slug',
        read_only=True,
    )

    class Meta:
        model = SurveyQuestion
        fields = [
            'id',
            'label',
            'position',
            'page',
            'answer_type_id',
            'group_id',
            'param_slug',
            'param_is_required',
            'answer_type_slug',
            'param_widget',
            'param_data_source',
            'param_is_allow_multiple_choice',
        ]


class PositionSerializer(serializers.Serializer):
    position = serializers.IntegerField(min_value=1)
    page = serializers.IntegerField(min_value=1, required=False)


class MoveSurveyQuestionPageSerializer(serializers.Serializer):
    page = serializers.IntegerField(min_value=1, required=True)
    to = serializers.IntegerField(min_value=1, required=True)


class SurveyQuestionPageSerializer(serializers.Serializer):
    page = serializers.IntegerField(min_value=1, required=True)


class ProfileSurveyAnswerCitySerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    class Meta:
        model = City
        fields = (
            'id',
            'full_name'
        )


class UserSurveyAnswerProfileSerializer(serializers.ModelSerializer):
    uid = serializers.IntegerField(read_only=True)
    name_and_surname_with_fallback = serializers.CharField(source='get_name_and_surname_with_fallback')
    yandex_username = serializers.CharField(read_only=True, source='username')
    param_subscribed_email = serializers.CharField(read_only=True, source='email')

    class Meta:
        model = User
        fields = [
            'id',
            'name_and_surname_with_fallback',
            'yandex_username',
            'uid',
            'param_subscribed_email',
        ]


class ProfileSurveyAnswerKeysBundleSerializer(serializers.ModelSerializer):
    class Meta:
        model = SurveyKeysBundle
        fields = (
            'id',
            'name',
        )


class ProfileSurveyAnswerKeySerializer(serializers.ModelSerializer):
    bundle = ProfileSurveyAnswerKeysBundleSerializer()

    class Meta:
        model = Key
        fields = (
            'id',
            'value',
            'bundle',
        )


class ProfileSurveyAnswerStateConditionResultSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    survey_state_condition_node_id = serializers.IntegerField(read_only=True)

    class Meta:
        model = ProfileSurveyAnswerStateConditionResult
        fields = (
            'survey_state_condition_node_id',
            'value'
        )


class SimpleSurveySerializer(serializers.ModelSerializer):
    content_object = SurveyContentObjectField(required=False, allow_null=True, read_only=True)

    class Meta:
        model = Survey
        fields = (
            'id',
            'name',
            'content_object',
            'type',
        )


class SimpleProfileSurveyAnswerSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    survey = SimpleSurveySerializer(many=False, read_only=True)

    class Meta:
        model = ProfileSurveyAnswer
        fields = (
            'id',
            'survey',
        )


class ProfileSurveyAnswerOrderSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    class Meta:
        model = Order
        fields = (
            'id',
            'payment_status',
        )


class ProfileSurveyAnswerSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    profile = UserSurveyAnswerProfileSerializer(source='user')
    survey_id = IdentifierField(read_only=True)
    keys = ProfileSurveyAnswerKeySerializer(many=True, read_only=True)
    states = ProfileSurveyAnswerStateConditionResultSerializer(many=True, read_only=True)
    orders = ProfileSurveyAnswerOrderSerializer(many=True, read_only=True)

    class Meta:
        model = ProfileSurveyAnswer
        fields = (
            'id',
            'profile',
            'survey_id',
            'secret_code',
            'date_created',
            'date_updated',
            'states',
            'keys',
            'orders',
        )


class SurveyTextSerializer(TranslationSerializerMixin,
                           InternalModelSerializerV2Mixin,
                           serializers.ModelSerializer):
    class Meta:
        model = SurveyText
        fields = [
            'id',
            'slug',
            'survey',
            'max_length',
            'null',
            'value',
        ]


# triggers


class SurveyQuestionShowConditionNodeItemSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    class Meta:
        model = SurveyQuestionShowConditionNodeItem
        fields = '__all__'


class SurveyQuestionShowConditionNodeSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    items = SurveyQuestionShowConditionNodeItemSerializer(many=True)

    class Meta:
        model = SurveyQuestionShowConditionNode
        fields = (
            'id',
            'survey_question',
            'items',
        )

    def create(self, validated_data):
        items = validated_data.pop('items', [])
        node = SurveyQuestionShowConditionNode.objects.create(**validated_data)
        for item in items:
            SurveyQuestionShowConditionNodeItem.objects.create(survey_question_show_condition_node=node, **item)
        return node


class SurveySubmitConditionNodeItemSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    class Meta:
        model = SurveySubmitConditionNodeItem
        fields = '__all__'


class SurveySubmitConditionNodeSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    items = SurveySubmitConditionNodeItemSerializer(many=True)
    survey_id = IdentifierField()

    class Meta:
        model = SurveySubmitConditionNode
        fields = (
            'id',
            'survey_id',
            'items',
        )


class SurveyQuestionHintTypeSerializer(InternalModelSerializerV2Mixin, serializers.ModelSerializer):
    class Meta:
        model = SurveyQuestionHintType
        fields = (
            'id',
            'slug',
            'name',
        )


class UserListItemSerializer(serializers.Serializer):
    login = serializers.CharField(source='get_yandex_username')
    uid = serializers.CharField()
    cloud_uid = serializers.CharField()
    display = serializers.CharField(source='get_name_and_surname_with_fallback', read_only=True)


class GroupListItemSerializer(serializers.Serializer):
    url = serializers.CharField(source='get_info_url')
    id = serializers.IntegerField()
    name = serializers.CharField()


class SurveyAccessSerializerBase(serializers.Serializer):
    type = serializers.ChoiceField(choices=[
        FORM_ACCESS_TYPE_COMMON,
        FORM_ACCESS_TYPE_OWNER,
        FORM_ACCESS_TYPE_RESTRICTED,
    ])


class SurveyAccessSerializer(SurveyAccessSerializerBase):
    users = UserListItemSerializer(many=True, required=False)
    groups = GroupListItemSerializer(many=True, required=False)


class UnionField(serializers.Field):
    def __init__(self, *args, **kwargs):
        self.union = kwargs.pop('union', None) or []
        super().__init__(*args, **kwargs)

    def to_internal_value(self, data):
        errors = []
        for field in self.union:
            try:
                return field.to_internal_value(data)
            except ValidationError as e:
                errors.append(e)
        if errors:
            raise ValidationError(errors)


class UserSerializer(serializers.Serializer):
    uid = serializers.CharField(required=False)
    cloud_uid = serializers.CharField(required=False)


class SurveyAccessDeserializer(SurveyAccessSerializerBase):
    users = serializers.ListField(
        child=UnionField(union=(serializers.CharField(), UserSerializer())),
        required=False,
    )
    groups = serializers.ListField(child=serializers.IntegerField(), required=False)


class SurveyQuestionRestore(serializers.Serializer):
    page = serializers.IntegerField(required=False)
    position = serializers.IntegerField(required=False)


class HideNotificationsSerializer(serializers.Serializer):
    subscription = serializers.IntegerField(required=False)


class SurveyQuizItemSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=256)
    description = serializers.CharField(required=False, max_length=1024, allow_blank=True)
    image_id = serializers.IntegerField(required=False, default=None, allow_null=True)
    image = serializers.SerializerMethodField()

    def get_image(self, obj):
        try:
            image = Image.objects.get(pk=obj.get('image_id'))
            return ImageSerializer(image).data
        except Image.DoesNotExist:
            pass

    def validate(self, data):
        item = SurveyQuizItem(data)
        if item.image_id is not None:
            if not Image.objects.filter(pk=item.image_id).exists():
                raise ValidationError({'image_id': _('Некорректой код картинки')})
        return data


class SurveyQuizSerializer(serializers.Serializer):
    id = IdentifierField(read_only=True)
    show_results = serializers.BooleanField(source='extra.quiz.show_results', default=False)
    show_correct = serializers.BooleanField(source='extra.quiz.show_correct', default=False)
    calc_method = serializers.ChoiceField(source='extra.quiz.calc_method',
                                          choices=QUIZ_CALC_METHOD_CHOICES,
                                          default=QUIZ_CALC_METHOD_RANGE)
    pass_scores = serializers.FloatField(source='extra.quiz.pass_scores', default=0)
    total_scores = serializers.FloatField(read_only=True)
    question_count = serializers.IntegerField(source='quiz_question_count', read_only=True)
    items = SurveyQuizItemSerializer(source='extra.quiz.items', many=True, default=[])

    def validate(self, data):
        extra = data.get('extra') or {}
        quiz = SurveyQuiz(extra.get('quiz'))
        if not quiz.items:
            raise ValidationError({'items': _('Список поддиапазонов не может быть пустым')})
        if quiz.calc_method == QUIZ_CALC_METHOD_SCORES:
            if len(quiz.items) != QUIZ_MIN_ITEMS_COUNT:
                raise ValidationError({'items': _('Список поддиапазонов должен быть равен двум')})
        elif quiz.calc_method == QUIZ_CALC_METHOD_RANGE:
            if len(quiz.items) < QUIZ_MIN_ITEMS_COUNT:
                raise ValidationError({'items': _('Список поддиапазонов должен быть больше или равен двум')})
        if quiz.calc_method == 'scores':
            if quiz.pass_scores is None:
                raise ValidationError({'pass_scores': _('Пороговое значение не может быть null')})
            if quiz.pass_scores < 0:
                raise ValidationError({'pass_scores': _('Пороговое значение должно быть больше ноля')})
            total_scores = self.instance.total_scores or 0.0
            if quiz.pass_scores > total_scores:
                raise ValidationError({'pass_scores': _('Пороговое значение должно быть меньше максимального количества баллов')})
        return data

    def update_extra(self, instance, validated_data):
        if validated_data is not None:
            data = instance.extra or {}
            data.update(validated_data)
            instance.extra = data
        else:
            instance.extra = None

    def update(self, instance, validated_data):
        if 'extra' in validated_data:
            extra_data = validated_data.pop('extra')
            self.update_extra(instance, extra_data)
            instance.save(update_fields=['extra'])
        return instance


class SurveyBanSerializer(serializers.ModelSerializer):
    ban = serializers.NullBooleanField(required=False, source='is_ban_detected')

    def update(self, instance, validated_data):
        if 'is_ban_detected' in validated_data:
            if validated_data['is_ban_detected']:
                instance.is_published_external = False
        return super().update(instance, validated_data)

    class Meta:
        model = Survey
        fields = (
            'ban',
        )


class AnswerParamsSerializer(serializers.Serializer):
    started = serializers.DateTimeField(required=False, input_formats=('%Y-%m-%d', ISO_8601))
    finished = serializers.DateTimeField(required=False, input_formats=('%Y-%m-%d', ISO_8601))
