import uuid
import waffle

from django.db.models import Q, Prefetch
from django.template.loader import render_to_string
from django.utils.functional import cached_property
from functools import lru_cache
from rest_framework import serializers


from intranet.femida.src.api.candidates.utils import (
    get_unique_contacts,
    get_sorted_serialized_attachments,
    get_unique_serialized_attachments,
)
from intranet.femida.src.api.core.fields import SafeWikiFormattedField
from intranet.femida.src.api.core.serializers import (
    AwareSerializerMixin,
    FemidaSerializer,
    IdNameSerializer,
    WorkflowActionsField,
)
from intranet.femida.src.api.professions.serializers import ProfessionLiteSerializer
from intranet.femida.src.api.skills.serializers import SkillSerializer
from intranet.femida.src.api.users.serializers import UserSerializer
from intranet.femida.src.applications.models import Application
from intranet.femida.src.candidates import models
from intranet.femida.src.candidates.choices import (
    CANDIDATE_RESPONSIBLE_ROLES,
    CANDIDATE_SORTING_TYPES,
    CONSIDERATION_RECRUITER_STAGES,
    CONSIDERATION_STATUSES,
    DUPLICATION_CASE_STATUSES,
    CONTACT_TYPES,
    RKN_CONTACT_TYPES,
    CANDIDATE_COST_RATES,
    CANDIDATE_COST_GROUPS,
    VERIFICATION_TYPES,
)
from intranet.femida.src.candidates.considerations.helpers import (
    get_sorted_considerations,
    add_items_counts_to_considerations_qs,
    get_consideration_items_counts,
)
from intranet.femida.src.candidates.controllers import (
    get_candidate_message_counts,
    get_candidates_extended_statuses,
    CandidateCostHistory,
)
from intranet.femida.src.candidates.helpers import (
    get_candidate_professions_prefetch,
    get_candidate_unique_attachments_prefetch,
    get_main_email,
    get_active_tags,
)
from intranet.femida.src.candidates.models import Verification, Candidate
from intranet.femida.src.candidates.workflow import CandidateWorkflow
from intranet.femida.src.communications.choices import MESSAGE_TYPES
from intranet.femida.src.communications.models import Message
from intranet.femida.src.core.serializers import FakeField
from intranet.femida.src.interviews.helpers import InterviewVisibilityHelper
from intranet.femida.src.interviews.models import Interview
from intranet.femida.src.utils.switches import is_candidate_main_recruiter_enabled


class ResponsiblesSerializerMixin:

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if is_candidate_main_recruiter_enabled():
            self.fields.pop('responsibles', None)
        else:
            self.fields.pop('main_recruiter', None)
            self.fields.pop('recruiters', None)


class ConsiderationLiteSerializer(AwareSerializerMixin, FemidaSerializer):

    responsibles = UserSerializer(many=True)
    counts = serializers.SerializerMethodField()

    def get_counts(self, obj):
        if 'considerations_items_counts' in self.root.context:
            return self.root.context['considerations_items_counts'][obj.id]
        elif hasattr(obj, 'assessments_count'):
            return {
                'assessments': obj.assessments_count,
                'applications': obj.applications_count,
                'messages': obj.messages_count,
            }
        return get_consideration_items_counts(obj.id)

    class Meta:
        model = models.Consideration
        fields = (
            'id',
            'status',
            'extended_status',
            'source',
            'source_description',
            'resolution',
            'started',
            'finished',
            'responsibles',
            'counts',
        )


class CandidateContactSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.CandidateContact
        fields = ('id', 'type', 'account_id', 'is_main', 'is_active')


class CandidateEducationSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.CandidateEducation
        fields = ('id', 'institution', 'faculty', 'degree', 'end_date')


class CandidateJobSerializer(serializers.ModelSerializer):

    def to_representation(self, instance):
        data = super().to_representation(instance)
        user = self.root.context.get('user')
        if not user or not (user.is_recruiter or user.is_recruiter_assessor):
            del data['salary_evaluation']
            del data['currency']
            del data['comment']
        return data

    class Meta:
        model = models.CandidateJob
        fields = (
            'id',
            'employer',
            'position',
            'start_date',
            'end_date',
            'salary_evaluation',
            'currency',
            'comment',
        )


class CandidateSkillSerializer(serializers.ModelSerializer):

    skill = SkillSerializer()

    class Meta:
        model = models.CandidateSkill
        fields = ('id', 'skill', 'candidate', 'confirmed_by')


class CandidateProfessionSerializer(serializers.ModelSerializer):

    profession = ProfessionLiteSerializer()

    def to_representation(self, instance):
        data = super().to_representation(instance)
        user = self.root.context.get('user')
        if not user or not (user.is_recruiter or user.is_recruiter_assessor):
            del data['salary_expectation']
        return data

    class Meta:
        model = models.CandidateProfession
        fields = ('id', 'profession', 'salary_expectation')


class CandidateLiteSerializer(AwareSerializerMixin, FemidaSerializer):

    applications = serializers.SerializerMethodField()
    vacancies = serializers.SerializerMethodField()
    extended_status = serializers.SerializerMethodField()
    is_current_employee = serializers.SerializerMethodField()

    def get_applications(self, obj):
        if 'candidate_applications_map' in self.root.context:
            return self.root.context['candidate_applications_map'].get(obj.id, [])

    def get_vacancies(self, obj):
        if 'candidate_vacancies_map' in self.root.context:
            return self.root.context['candidate_vacancies_map'].get(obj.id, [])

    def get_extended_status(self, obj):
        if hasattr(obj, 'extended_status'):
            return obj.extended_status
        if 'extended_statuses' in self.root.context:
            return self.root.context['extended_statuses'].get(obj.id)

    def get_is_current_employee(self, obj):
        if hasattr(obj, 'is_current_employee'):
            return obj.is_current_employee
        elif 'current_employee_ids' in self.root.context:
            return obj.id in self.root.context['current_employee_ids']

    class Meta:
        model = models.Candidate
        fields = (
            'id',
            'first_name',
            'middle_name',
            'last_name',
            'birthday',
            'gender',
            'country',
            'city',
            'status',
            'login',
            'is_current_employee',
            'applications',
            'vacancies',
            'extended_status',
            'modified',
        )


class CandidateRecruiterListSerializer(CandidateLiteSerializer):

    target_cities = IdNameSerializer(many=True)
    professions = serializers.SerializerMethodField()

    def get_professions(self, obj):
        professions = (i.profession for i in obj.candidate_professions.all())
        return IdNameSerializer(professions, name_source='localized_name', many=True).data

    class Meta(CandidateLiteSerializer.Meta):
        prefetch_related_map = {
            'target_cities': ('target_cities',),
            'professions': ('candidate_professions__profession',),
        }
        fields = CandidateLiteSerializer.Meta.fields + (
            'target_cities',
            'professions',
        )


class ApplicationSerializer(serializers.ModelSerializer):

    vacancy = IdNameSerializer()

    class Meta:
        model = Application
        fields = (
            'id',
            'status',
            'proposal_status',
            'resolution',
            'created',
            'modified',
            'vacancy',
            'is_archived',
        )


class CandidateBaseSerializer(FemidaSerializer):
    """
    Вспомогательный сериализатор. Требует дополнительного указания select/perfect_related.
    """
    target_cities = IdNameSerializer(many=True)
    responsibles = UserSerializer(many=True)
    jobs = CandidateJobSerializer(many=True)
    skills = IdNameSerializer(many=True)
    candidate_professions = CandidateProfessionSerializer(many=True)

    class Meta:
        model = models.Candidate
        fields = (
            'id',
            'first_name',
            'last_name',
            'city',
            'target_cities',
            'responsibles',
            'jobs',
            'skills',
            'candidate_professions',
        )


class CandidateSerializer(ResponsiblesSerializerMixin,
                          AwareSerializerMixin, CandidateBaseSerializer):
    """
    Сериализатор одного кандидата.
    Не использовать как списковый сериализатор,
    либо как вложенный сериализатор для спискового.
    Иначе это породит много лишних запросов.
    """
    contacts = serializers.SerializerMethodField()
    educations = CandidateEducationSerializer(many=True)
    attachments = serializers.SerializerMethodField()
    tags = serializers.SerializerMethodField()
    actions = WorkflowActionsField(CandidateWorkflow)
    extended_status = serializers.SerializerMethodField()
    duplication_cases = serializers.SerializerMethodField()
    is_current_employee = serializers.SerializerMethodField()
    considerations = ConsiderationLiteSerializer(many=True)
    counts = serializers.SerializerMethodField()
    verification = serializers.SerializerMethodField()
    main_recruiter = UserSerializer()
    recruiters = UserSerializer(many=True)
    # Note: фронт не рационально использует этот атрибут.
    # Он нужен лишь для того, чтобы фронт узнал новый порядок
    # претендентов при добавлении кандидата на вакансию.
    # А для нас это лишние 2 запроса за претендентами и их вакансиями
    applications = ApplicationSerializer(many=True)
    vacancies_mailing_agreement = serializers.BooleanField(read_only=True)
    events_mailing_agreement = serializers.BooleanField(read_only=True)

    def get_contacts(self, obj):
        return get_unique_contacts(obj)

    def get_attachments(self, obj):
        return get_sorted_serialized_attachments(obj.unique_attachments)

    def get_tags(self, obj):
        return get_active_tags(obj)

    def get_extended_status(self, obj):
        extended_statuses = get_candidates_extended_statuses([obj.id])
        return extended_statuses.get(obj.id)

    def get_counts(self, obj):
        message_counts = get_candidate_message_counts(obj.id)
        counts = {
            'actual_assessments': 0,
            'archived_assessments': 0,
            'actual_applications': 0,
            'archived_applications': 0,
            'notes': message_counts.get(MESSAGE_TYPES.note, 0),
            'external_messages': (
                message_counts.get(MESSAGE_TYPES.incoming, 0)
                + message_counts.get(MESSAGE_TYPES.outcoming, 0)
            ),
        }
        # Если в ответе не ожидаются рассмотрения, то остальное считать не будем
        if 'considerations' not in self.fields:
            return counts

        for cons in obj.considerations.all():
            prefix = 'archived_' if cons.state == CONSIDERATION_STATUSES.archived else 'actual_'
            counts[prefix + 'assessments'] += cons.assessments_count
            counts[prefix + 'applications'] += cons.applications_count

        return counts

    def get_duplication_cases(self, obj):
        duplication_cases = (
            models.DuplicationCase.objects
            .select_related('first_candidate', 'second_candidate')
            .prefetch_related(
                'first_candidate__candidate_attachments__attachment',
                'second_candidate__candidate_attachments__attachment',
                'first_candidate__contacts',
                'second_candidate__contacts',
            )
            .filter(status=DUPLICATION_CASE_STATUSES.new)
            .filter(Q(first_candidate=obj) | Q(second_candidate=obj))
        )
        res = []
        for case in duplication_cases:
            if case.first_candidate == obj:
                duplicate = case.second_candidate
            else:
                duplicate = case.first_candidate
            res.append({
                'id': case.id,
                'duplicate': {
                    'id': duplicate.id,
                    'first_name': duplicate.first_name,
                    'last_name': duplicate.last_name,
                },
                'similarity_info': case.similarity_info.to_dict(),
            })
        return res

    def get_is_current_employee(self, obj):
        if hasattr(obj, 'is_current_employee'):
            return obj.is_current_employee

    def get_verification(self, obj):
        user = self.root.context.get('user')
        if not user or not user.is_recruiter:
            return
        if not obj.alive_verifications:
            return

        return VerificationDetailSerializer(
            instance=obj.alive_verifications[0],
            context=self.root.context,
        ).data

    class Meta:
        model = models.Candidate
        fields = (
            'id',
            'first_name',
            'middle_name',
            'last_name',
            'birthday',
            'gender',
            'country',
            'city',
            'created',
            'modified',
            'target_cities',
            'status',
            'source',
            'source_description',
            'main_recruiter',
            'recruiters',
            'responsibles',
            'contacts',
            'educations',
            'jobs',
            'skills',
            'tags',
            'candidate_professions',
            'created_by',
            'is_from_submission',
            'attachments',
            'applications',
            'vacancies_mailing_agreement',
            'events_mailing_agreement',
            'is_hidden',
            'login',
            'is_current_employee',
            'actions',
            'extended_status',
            'counts',
            'original',
            'duplication_cases',
            'startrek_key',
            'considerations',
            'verification',
        )
        select_related_map = {
            'created_by': ('created_by',),
        }
        prefetch_related_map = {
            'target_cities': ('target_cities',),
            'skills': ('skills',),
            'tags': ('candidate_tags__tag',),
            'candidate_professions': (get_candidate_professions_prefetch(),),
            'attachments': (
                get_candidate_unique_attachments_prefetch(to_attr='unique_attachments'),
            ),
            'applications': (
                'applications__vacancy',
            ),
            'contacts': ('contacts',),
            'educations': ('educations',),
            'jobs': ('jobs',),
            'main_recruiter': ('candidate_responsibles__user',),
            'recruiters': ('candidate_responsibles__user',),
            'responsibles': ('candidate_responsibles__user',),
            'considerations': (
                Prefetch(
                    lookup='considerations',
                    queryset=add_items_counts_to_considerations_qs(
                        qs=get_sorted_considerations(unsafe=True),
                    ),
                ),
                'considerations__responsibles',
            ),
            'verification': (
                lambda: Prefetch(
                    lookup='verifications',
                    queryset=Verification.objects.alive(),
                    to_attr='alive_verifications',
                ),
            ),
        }


class CandidateForVerificationSerializer(serializers.Serializer):

    actions = WorkflowActionsField(CandidateWorkflow)


class VerificationDetailSerializer(FemidaSerializer):

    candidate = CandidateForVerificationSerializer()

    class Meta:
        model = Verification
        fields = (
            'id',
            'candidate',
            'status',
            'link',
            'resolution',
            'expiration_date',
            'created',
            'sent_on_check',
        )


# FORM SERIALIZERS


class CandidateProfessionFormSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.CandidateProfession
        fields = '__all__'


class CandidateFormSerializer(ResponsiblesSerializerMixin, serializers.ModelSerializer):

    candidate_professions = CandidateProfessionFormSerializer(many=True)
    main_recruiter = serializers.CharField(source='main_recruiter.username', default=None)
    recruiters = serializers.SerializerMethodField()
    responsibles = serializers.SerializerMethodField()
    contacts = serializers.SerializerMethodField()
    educations = CandidateEducationSerializer(many=True)
    jobs = CandidateJobSerializer(many=True)
    skills = serializers.SerializerMethodField()
    tags = serializers.SerializerMethodField()
    attachments = serializers.SerializerMethodField()

    def get_contacts(self, obj):
        contacts = [CandidateContactSerializer(c).data for c in obj.contacts.all() if c.is_active]
        if waffle.switch_is_active('is_rkn'):
            contacts = [c for c in contacts if c['type'] in RKN_CONTACT_TYPES]
        return contacts

    def get_skills(self, obj):
        return [cs.skill_id for cs in obj.candidate_skills.all()]

    def get_tags(self, obj):
        return get_active_tags(obj)

    def get_attachments(self, obj):
        return [ca.attachment_id for ca in obj.candidate_attachments.all()]

    def get_responsibles(self, obj):
        return [u.username for u in obj.responsibles.all()]

    def get_recruiters(self, obj):
        return [recruiter.username for recruiter in obj.recruiters]

    class Meta:
        model = models.Candidate
        fields = '__all__'


class CandidateCompareFormSerializer(CandidateLiteSerializer):

    contacts = serializers.SerializerMethodField()
    educations = CandidateEducationSerializer(many=True)
    jobs = CandidateJobSerializer(many=True)
    candidate_professions = CandidateProfessionSerializer(many=True)
    skills = IdNameSerializer(many=True)
    attachments = serializers.SerializerMethodField()
    target_cities = IdNameSerializer(many=True)
    tags = serializers.SerializerMethodField()
    main_recruiter = serializers.SerializerMethodField()
    inn = serializers.SerializerMethodField()

    def get_main_recruiter(self, obj):
        # отдельный метод используется чтобы вернуть на фронт {}
        # вместо null в случае отсутствия основного рекрутера
        return UserSerializer(obj.main_recruiter).data

    def get_contacts(self, obj):
        # Note: не отдаём контакты Бимери на форму мержа,
        # потому что после мержа в Бимери всё равно появится новый кандидат со своим ID
        return [c for c in get_unique_contacts(obj) if c['type'] != CONTACT_TYPES.beamery]

    def get_attachments(self, obj):
        return get_unique_serialized_attachments(obj)

    def get_tags(self, obj):
        return get_active_tags(obj)

    def get_inn(self, obj):
        return bool(obj.inn)

    class Meta(CandidateLiteSerializer.Meta):
        model = models.Candidate
        fields = CandidateLiteSerializer.Meta.fields + (
            'source',
            'source_description',
            'contacts',
            'educations',
            'jobs',
            'candidate_professions',
            'skills',
            'attachments',
            'responsibles',
            'target_cities',
            'tags',
            'main_recruiter',
            'inn',
        )


class CandidateDuplicateWithDetailsSerializer(serializers.Serializer):

    candidate = CandidateLiteSerializer()
    details = serializers.JSONField()


class CandidateDuplicateAsChoiceSerializer(CandidateDuplicateWithDetailsSerializer):

    label = serializers.ReadOnlyField(source='candidate.get_full_name')


class CandidateSearchFormSerializer(serializers.Serializer):

    professions = serializers.SerializerMethodField()
    skills = serializers.SerializerMethodField()
    without_nohire = serializers.BooleanField(required=False)
    skype_interviews_avg_grade = serializers.CharField(required=False)
    on_site_interviews_avg_grade = serializers.CharField(required=False)
    city = serializers.CharField(required=False)
    institution = serializers.CharField(required=False)
    employer = serializers.CharField(required=False)
    responsibles = serializers.SerializerMethodField()
    target_cities = serializers.SerializerMethodField()
    tags = serializers.SerializerMethodField()
    ignore_employees = serializers.ReadOnlyField()
    is_active = serializers.ReadOnlyField()
    created__gte = serializers.ReadOnlyField()
    created__lte = serializers.ReadOnlyField()
    modified__gte = serializers.ReadOnlyField()
    modified__lte = serializers.ReadOnlyField()
    sort = serializers.ChoiceField(choices=CANDIDATE_SORTING_TYPES)

    def get_tags(self, obj):
        return [i.name for i in obj.get('tags', [])]

    def get_target_cities(self, obj):
        return [i.id for i in obj.get('target_cities', [])]

    def get_professions(self, obj):
        return [i.id for i in obj.get('professions', [])]

    def get_skills(self, obj):
        return [i.id for i in obj.get('skills', [])]

    def get_responsibles(self, obj):
        return [i.username for i in obj.get('responsibles', [])]


class CandidateCreateProposalsFormSerializer(serializers.Serializer):

    professions = serializers.SerializerMethodField()
    skills = serializers.SerializerMethodField()
    interviews = serializers.SerializerMethodField()
    cities = serializers.SerializerMethodField()
    departments = serializers.SerializerMethodField()

    def get_professions(self, obj):
        return [i.profession_id for i in obj.candidate_professions.all()]

    def get_skills(self, obj):
        return [i.skill_id for i in obj.candidate_skills.all()]

    def get_interviews(self, obj):
        return []

    def get_departments(self, obj: Candidate):
        return []

    def get_cities(self, obj):
        return [i.id for i in obj.target_cities.all()]


class CandidateRecruiterListFilterFormSerializer(serializers.Serializer):

    stage = serializers.ChoiceField(choices=CONSIDERATION_RECRUITER_STAGES)
    responsible_role = serializers.ChoiceField(choices=CANDIDATE_RESPONSIBLE_ROLES)


class CandidateCreateVerificationFormSerializer(serializers.Serializer):

    sender = serializers.SerializerMethodField()
    receiver = serializers.SerializerMethodField()
    subject = FakeField('От компании Яндекс')
    text = serializers.SerializerMethodField()
    uuid = serializers.SerializerMethodField()
    type = serializers.SerializerMethodField()

    def get_sender(self, obj):
        return self.root.context['user'].username + '@yandex-team.ru'

    def get_receiver(self, obj):
        return get_main_email(obj) or ''

    def get_text(self, obj):
        is_autohire = self.root.context.get('is_autohire', False)
        verification_type = self._type
        template_name = 'send-body-autohire.txt' if is_autohire else 'send-body.txt'
        return render_to_string(
            template_name=f'email/verifications/{template_name}',
            context={
                'candidate': self.instance,
                'user': self.root.context['user'],
                'verification_link': Verification.get_link(self._uuid, verification_type),
            },
        )

    def get_uuid(self, obj):
        return self._uuid

    @cached_property
    def _uuid(self):
        return uuid.uuid4().hex

    def get_type(self, obj):
        return self._type

    @cached_property
    def _type(self):
        return self.root.context.get('type', VERIFICATION_TYPES.default)


class InterviewForCandidateFilterSerializer(FemidaSerializer):

    def to_representation(self, instance):
        data = super().to_representation(instance)
        visibility_helper = self.root.context.get('visibility_helper')
        if not visibility_helper.is_visible(instance):
            sensitive_fields = ('grade', 'resolution')
            for field in sensitive_fields:
                data[field] = None
        return data

    class Meta:
        model = Interview
        fields = (
            'id',
            'type',
            'grade',
            'state',
            'resolution',
        )


class ConsiderationForCandidateFilterSerializer(FemidaSerializer):

    interviews = serializers.SerializerMethodField()
    source = serializers.SerializerMethodField()

    def get_interviews(self, obj: models.Consideration):
        request_user = self.root.context.get('user')
        interviews = obj.interviews.all()
        visibility_helper = InterviewVisibilityHelper(
            user=request_user,
            consideration=obj,
            interviews=interviews,
        )
        self.root.context['visibility_helper'] = visibility_helper
        serializer = InterviewForCandidateFilterSerializer(interviews, context=self.root.context, many=True)
        return serializer.data

    def get_source(self, obj: models.Consideration):
        if obj.state != CONSIDERATION_STATUSES.in_progress:
            return obj.source
        else:
            candidate = self.root.context.get('candidate')
            if candidate:
                return candidate.source
        return None

    class Meta:
        model = models.Consideration
        fields = (
            'id',
            'extended_status',
            'source',
            'source_description',
            'resolution',
            'started',
            'finished',
            'interviews',
            'state',
        )


class NoteForCandidateFilterSerializer(FemidaSerializer):

    html = SafeWikiFormattedField()
    author = UserSerializer()

    class Meta:
        model = Message
        fields = (
            'id',
            'created',
            'author',
            'html',
            'modified',
        )


class CandidateFilterSerializer(AwareSerializerMixin, CandidateBaseSerializer):
    """
    Сериализатор для страницы фильтров кандидатов
    """
    contacts = serializers.SerializerMethodField()
    is_current_employee = serializers.ReadOnlyField()
    extended_status = serializers.SerializerMethodField()
    extended_status_changed_at = serializers.SerializerMethodField()
    resume = serializers.SerializerMethodField()
    professions = serializers.SerializerMethodField()
    skills = IdNameSerializer(many=True)
    tags = serializers.SerializerMethodField()
    educations = CandidateEducationSerializer(many=True)
    jobs = CandidateJobSerializer(many=True)
    recruiters = UserSerializer(many=True)
    main_recruiter = UserSerializer()
    considerations = serializers.SerializerMethodField()

    def get_considerations(self, obj: models.Candidate):
        self.root.context['candidate'] = obj
        return ConsiderationForCandidateFilterSerializer(
            obj.considerations.all(),
            context=self.root.context,
            many=True,
        ).data

    def get_contacts(self, obj):
        return get_unique_contacts(obj)

    def get_professions(self, obj: models.Candidate):
        professions = (i.profession for i in obj.candidate_professions.all())
        return IdNameSerializer(professions, name_source='localized_name', many=True).data

    def get_tags(self, obj):
        return get_active_tags(obj)

    def get_resume(self, obj: models.Candidate):
        attachments = get_unique_serialized_attachments(obj)
        return attachments[0] if attachments else None

    def get_extended_status(self, obj: models.Candidate):
        return self.root.context['extended_statuses'].get(obj.id, {}).get('extended_status')

    def get_extended_status_changed_at(self, obj: models.Candidate):
        # смотрим на modified у последнего рассмотрения,
        # т.к. пока что consideration меняется при обновлении статуса
        # (до FEMIDA-6778 поле modified обновлялось при мёрдже)
        return self.root.context['extended_statuses'].get(obj.id, {}).get('changed_at')

    class Meta:
        model = models.Candidate
        fields = (
            'id',
            'first_name',
            'last_name',
            'login',
            'is_current_employee',
            'extended_status',
            'extended_status_changed_at',
            'resume',
            'professions',
            'skills',
            'tags',
            'educations',
            'target_cities',
            'recruiters',
            'main_recruiter',
            'jobs',
            'considerations',
            'created',
            'modified',
            'contacts',
        )
        prefetch_related_map = {
            'considerations': ('considerations__interviews',),
            'contacts': ('contacts',),
            'educations': ('educations',),
            'jobs': ('jobs',),
            'main_recruiter': ('candidate_responsibles__user',),
            'professions': ('candidate_professions__profession',),
            'recruiters': ('candidate_responsibles__user',),
            'resume': (get_candidate_unique_attachments_prefetch(),),
            'skills': ('skills',),
            'tags': ('candidate_tags__tag',),
            'target_cities': ('target_cities',),
        }


class RecruiterCandidateFilterSerializer(CandidateFilterSerializer):
    """
    Расширенный сериализатор для страницы фильтров кандидатов.
    Есть дополнительные данные, доступные только рекрутерам и рекрутерам-асессорам
    """
    notes = NoteForCandidateFilterSerializer(many=True)

    class Meta(CandidateFilterSerializer.Meta):
        fields = CandidateFilterSerializer.Meta.fields + (
            'notes',
        )
        prefetch_related_map = CandidateFilterSerializer.Meta.prefetch_related_map | {
            'notes': (
                Prefetch(
                    lookup='messages',
                    queryset=(
                        # Note: Доступы к сообщениям такие же как к кандидатам,
                        # поэтому делаем через unsafe, чтобы упростить запрос
                        Message.unsafe
                        .alive()
                        .filter(type=MESSAGE_TYPES.note)
                        .select_related('author')
                        .order_by('-id')
                    ),
                    to_attr='notes',
                ),
            ),
        }


class SerializerCandidateCostSalaryField(serializers.Serializer):

    value = serializers.IntegerField()
    currency = serializers.ReadOnlyField(source='currency.code', default=None)
    rate = serializers.ChoiceField(choices=CANDIDATE_COST_RATES)
    taxed = serializers.BooleanField()


class SerializerCandidateCostRsuField(serializers.Serializer):

    value = serializers.IntegerField()
    currency = serializers.ReadOnlyField(source='currency.code', default=None)
    taxed = serializers.BooleanField()


class SerializerCandidateCostCommentField(serializers.Serializer):

    comment = serializers.CharField()


class CandidateCostsSerializer(serializers.Serializer):

    salary = SerializerCandidateCostSalaryField()
    total = SerializerCandidateCostSalaryField()
    salary_bonus = SerializerCandidateCostSalaryField()
    rsu = SerializerCandidateCostRsuField()
    comment = SerializerCandidateCostCommentField()


class DictObjectNonStrictAdapter:

    def __init__(self, _dict):
        self._dict = _dict

    def __getattr__(self, item):
        return self._dict.get(item, None)


class CandidateCostsSetSerializer(serializers.Serializer):

    expectation = serializers.SerializerMethodField()
    current = serializers.SerializerMethodField()
    author = UserSerializer(source='created_by')
    created = serializers.DateTimeField()
    modified = serializers.DateTimeField()

    def get_expectation(self, instance):
        return self._filtered_costs(instance, CANDIDATE_COST_GROUPS.expectation)

    def get_current(self, instance):
        return self._filtered_costs(instance, CANDIDATE_COST_GROUPS.current)

    def _filtered_costs(self, instance, group):
        costs = {cost.type: cost for cost in self._costs(instance) if cost.cost_group == group}
        serializer = CandidateCostsSerializer(DictObjectNonStrictAdapter(costs))
        return serializer.data

    @lru_cache(maxsize=32)
    def _costs(self, instance):
        return list(instance.costs.all())


class CandidateAllCostsSerializer(serializers.Serializer):

    costs = serializers.SerializerMethodField()

    def get_costs(self, _instance):
        history = CandidateCostHistory(self.instance)
        return CandidateCostsSetSerializer(history.all(), many=True).data
