import waffle

from datetime import datetime

from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.template.loader import render_to_string
from rest_framework import serializers
from rest_framework.fields import get_attribute

from intranet.femida.src.api.attachments.serializers import AttachmentLiteSerializer
from intranet.femida.src.api.candidates.serializers import CandidateLiteSerializer
from intranet.femida.src.api.core.serializers import (
    FemidaSerializer,
    IdNameSerializer,
    IdSlugNameSerializer,
    WorkflowActionsField,
)
from intranet.femida.src.api.core.validators import validate_phone, validate_birthday
from intranet.femida.src.api.offers.base_forms import OFFER_DATE_INPUT_FORMATS
from intranet.femida.src.api.staff.serializers import OfficeSerializer
from intranet.femida.src.api.users.serializers import UserSerializer
from intranet.femida.src.applications.models import Application
from intranet.femida.src.candidates.helpers import get_main_email, get_main_phone
from intranet.femida.src.candidates.models import Candidate, Verification
from intranet.femida.src.core.exceptions import SimpleValidationError
from intranet.femida.src.core.serializers import FakeField
from intranet.femida.src.core.switches import TemporarySwitch
from intranet.femida.src.oebs.api import get_budget_position, BudgetPositionError
from intranet.femida.src.offers import models
from intranet.femida.src.offers.choices import (
    CITIZENSHIP,
    EMPLOYEE_TYPES,
    PROBATION_PERIOD_UNIT_TO_TYPES,
)
from intranet.femida.src.offers.controllers import OfferCtl
from intranet.femida.src.offers.models import Brochure, RelocationPackage
from intranet.femida.src.offers.workflow import OfferWorkflow
from intranet.femida.src.vacancies.models import Vacancy


def _validate_birthday(birthday):
    if birthday:
        birthday = birthday.strip()
        for format in OFFER_DATE_INPUT_FORMATS:
            try:
                birthday = datetime.strptime(birthday, format).date()
                break
            except (ValueError, TypeError):
                continue
        else:
            raise SimpleValidationError('invalid')
    validate_birthday(birthday)


class NewhireField(serializers.ReadOnlyField):

    def __init__(self, **kwargs):
        self._source = kwargs.pop('source', None)
        kwargs['source'] = '*'
        super().__init__(**kwargs)

    def bind(self, field_name, parent):
        super().bind(field_name, parent)
        self._source = self._source or field_name
        self._source_attrs = self._source.split('.')

    def to_representation(self, value):
        if hasattr(self.parent, 'newhire_data'):
            newhire_data = self.parent.newhire_data
        else:
            newhire_data = OfferCtl(value).newhire_data
        return get_attribute(newhire_data, self._source_attrs)


class VerificationForOfferAcceptField(serializers.ReadOnlyField):

    def __init__(self, source=None, validator=None):
        super().__init__(source='*')
        self._source = source
        self._validator = validator or (lambda x: None)

    def bind(self, field_name, parent):
        super().bind(field_name, parent)
        self._source = self._source or self.field_name

    def to_representation(self, verification):
        value = verification.form_data.get(self._source)
        try:
            self._validator(value)
        except ValidationError:
            value = None
        return value


class CurrencySerializer(IdNameSerializer):

    code = serializers.ReadOnlyField()


class OfferVacancyLiteSerializer(FemidaSerializer):

    class Meta:
        model = Vacancy
        fields = (
            'id',
            'name',
            'type',
            'status',
            'resolution',
            'is_hidden',
            'startrek_key',
        )


class OfferApplicationLiteSerializer(FemidaSerializer):

    candidate = CandidateLiteSerializer()
    vacancy = OfferVacancyLiteSerializer()
    created_by = UserSerializer()

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


class OfferProfileSerializer(FemidaSerializer):

    photo = IdNameSerializer(source='offer.photo')
    passport_pages = IdNameSerializer(source='offer.passport_pages', many=True)
    snils = IdNameSerializer(source='offer.snils')
    documents = IdNameSerializer(source='offer.documents', many=True)

    class Meta:
        model = models.OfferProfile
        exclude = (
            'id',
        )


class OfferProfileCommentSerializer(FemidaSerializer):

    class Meta:
        model = models.OfferProfileComment
        exclude = ('offer_profile', 'is_hr')


class OfferProfileDetailSerializer(OfferProfileSerializer):

    comments = serializers.SerializerMethodField()

    def get_comments(self, obj):
        return OfferProfileCommentSerializer(
            instance=obj.comments.filter(is_hr=False),
            many=True,
        ).data


class OfferProfileFormSerializer(OfferProfileSerializer):

    photo = serializers.SerializerMethodField()
    passport_pages = serializers.SerializerMethodField()
    snils = serializers.SerializerMethodField()
    documents = serializers.SerializerMethodField()

    def get_photo(self, obj):
        return [a.id for a in obj.offer.photos]

    def get_passport_pages(self, obj):
        return [a.id for a in obj.offer.passport_pages]

    def get_snils(self, obj):
        return [a.id for a in obj.offer.snilses]

    def get_documents(self, obj):
        return [a.id for a in obj.offer.documents]


class OfferSerializer(FemidaSerializer):

    application = OfferApplicationLiteSerializer()
    office = OfficeSerializer()
    geography = IdNameSerializer()
    creator = UserSerializer()
    boss = UserSerializer()
    startrek_key = serializers.ReadOnlyField(source='vacancy.startrek_key')
    actions = WorkflowActionsField(OfferWorkflow)
    profile = OfferProfileSerializer()
    has_custom_offer_text = serializers.SerializerMethodField()
    has_pdf_offer = serializers.SerializerMethodField(method_name='get_need_beauty_offer')
    is_autohire = serializers.ReadOnlyField()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.offer_ctl = OfferCtl(self.instance)

    def get_has_custom_offer_text(self, obj):
        return self.offer_ctl.has_custom_offer_text()

    def get_need_beauty_offer(self, obj):
        # Новый оффер, если грейд кандидата ⩽ 18 и
        # организация = одно из российских юр.лиц и
        # ветка выхода = Яндекс
        # TODO: Удалить флаг после релиза FEMIDA-7504
        request = self.context.get('request')
        if not request or not waffle.flag_is_active(request, TemporarySwitch.ENABLE_NEW_OFFER):
            return False
        return self.offer_ctl.need_beauty_offer()

    class Meta:
        model = models.Offer
        exclude = (
            'boss_old',
            'is_resident',
            'need_help_in_adaptation',
        )


class OfferSchemesSerializer(FemidaSerializer):

    class Meta:
        model = models.OfferSchemesData
        exclude = (
            'offer',
        )


class OfferDetailSerializer(OfferSerializer):

    org = IdNameSerializer()
    department = IdNameSerializer(name_source='localized_name')
    position = IdNameSerializer()
    payment_currency = CurrencySerializer()

    profession = IdNameSerializer(name_source='localized_name')
    programming_language_display = serializers.ReadOnlyField(
        source='get_programming_language_display',
    )
    salary_expectations_currency = CurrencySerializer()
    abc_services = IdNameSerializer(many=True)

    profile = OfferProfileDetailSerializer()
    oebs_data = serializers.SerializerMethodField()
    schemes_data = OfferSchemesSerializer()
    offer_pdf = AttachmentLiteSerializer()
    value_stream = IdSlugNameSerializer(source='vacancy.value_stream')
    geography = IdNameSerializer()

    # Поля из Наниматора
    newhire_status = NewhireField()
    employee_type = NewhireField()
    username = NewhireField()
    join_at = NewhireField()
    startrek_hdrfs_key = NewhireField()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.newhire_data = OfferCtl(self.instance).newhire_data

    def get_oebs_data(self, obj):
        try:
            return get_budget_position(obj.vacancy.budget_position_id)
        except BudgetPositionError:
            return {}

    def get_field_names(self, declared_fields, info):
        # exclude нельзя сделать зависимым от параметров инстанса,
        # поэтому явно исключаем лишние поля здесь
        fields = super().get_field_names(declared_fields, info)
        if self.instance.is_internal:
            fields = set(fields)
            for field in models.Offer.EXTERNAL_ONLY_FIELDS:
                fields.discard(field)
            fields = list(fields)
        return fields


class OfferListSerializer(FemidaSerializer):

    vacancy = OfferVacancyLiteSerializer()
    candidate = CandidateLiteSerializer()
    creator = UserSerializer()

    class Meta:
        model = models.Offer
        fields = (
            'id',
            'status',
            'vacancy',
            'candidate',
            'creator',
            'modified',
        )


class OfferFormSerializer(OfferSerializer):

    profile = OfferProfileFormSerializer()
    office = serializers.IntegerField(source='office_id')
    probation_period_type = serializers.SerializerMethodField()

    def get_probation_period_type(self, obj):
        key = (obj.probation_period, obj.probation_period_unit)
        return PROBATION_PERIOD_UNIT_TO_TYPES.get(key)


class OfferSendFormSerializer(FemidaSerializer):

    receiver = serializers.SerializerMethodField()
    bcc = serializers.SerializerMethodField()
    subject = FakeField('Яндекс. Предложение о работе')
    message = serializers.SerializerMethodField()
    offer_text = serializers.SerializerMethodField()
    attachments = serializers.SerializerMethodField()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.offer_ctl = OfferCtl(self.instance)

    def get_receiver(self, obj):
        return get_main_email(obj.candidate)

    def get_bcc(self, obj):
        return [obj.boss.username] if obj.boss else []

    def get_message(self, obj):
        template_name = 'send-autohire.txt' if obj.is_autohire else 'send.txt'
        return render_to_string(f'email/offers/{template_name}', {
            'offer': obj,
        })

    def get_offer_text(self, obj):
        return self.offer_ctl.offer_text

    def get_attachments(self, obj):
        attachments = self._get_additional_attachments(obj)
        offer_pdf = obj.offer_pdf
        if offer_pdf:
            attachments += [offer_pdf.id]
        return attachments

    def get_need_beauty_offer(self, obj):
        # Новый оффер, если грейд кандидата ⩽ 18 и
        # организация = одно из российских юр.лиц и
        # ветка выхода = Яндекс
        # TODO: Удалить флаг после релиза FEMIDA-7504
        request = self.context.get('request')
        if not request or not waffle.flag_is_active(request, TemporarySwitch.ENABLE_NEW_OFFER):
            return False
        return self.offer_ctl.need_beauty_offer()

    def _get_additional_attachments(self, obj):
        need_brochure = self.get_need_beauty_offer(obj)

        if need_brochure:
            brochure = Brochure.objects.filter(is_active=True).first()
            if brochure:
                return [brochure.attachment_id]
        return []

    class Meta:
        model = models.Offer
        fields = (
            'receiver',
            'bcc',
            'subject',
            'message',
            'offer_text',
            'attachments',
        )


class CandidateForOfferAcceptFormSerializer(FemidaSerializer):

    phone = serializers.SerializerMethodField()
    home_email = serializers.SerializerMethodField()

    def get_phone(self, obj):
        return get_main_phone(obj)

    def get_home_email(self, obj):
        return get_main_email(obj)

    class Meta:
        model = Candidate
        fields = (
            'last_name',
            'first_name',
            'middle_name',
            'birthday',
            'phone',
            'home_email',
        )


class VerificationForOfferAcceptFormSerializer(FemidaSerializer):
    """
    Данные для формы принятия оффера, взятые с заполненной анкеты на КИ.
    """
    last_name = VerificationForOfferAcceptField()
    first_name = VerificationForOfferAcceptField()
    middle_name = VerificationForOfferAcceptField()
    birthday = VerificationForOfferAcceptField(validator=_validate_birthday)
    citizenship = serializers.SerializerMethodField()
    residence_address = serializers.SerializerMethodField()
    phone = VerificationForOfferAcceptField(validator=validate_phone)
    home_email = VerificationForOfferAcceptField(source='email', validator=validate_email)

    def get_citizenship(self, obj):
        is_russian_citizenship = obj.form_data.get('russian_citizenship', '')
        if is_russian_citizenship.lower() == 'да':
            return CITIZENSHIP.RU
        return None

    def get_residence_address(self, obj):
        is_the_same_address = obj.form_data.get('the_same_address', '')
        if is_the_same_address.lower() == 'да':
            return obj.form_data.get('registration_address')
        return obj.form_data.get('residence_address')

    class Meta:
        model = Verification
        fields = (
            'last_name',
            'first_name',
            'middle_name',
            'birthday',
            'citizenship',
            'residence_address',
            'phone',
            'home_email',
        )


class OfferAcceptFormSerializer(FemidaSerializer):
    """
    Данные для формы принятия оффера.
    В форме должен быть доступен только
    сильно ограниченный список полей.
    """
    username = serializers.SerializerMethodField()

    def get_username(self, obj):
        return '' if obj.employee_type == EMPLOYEE_TYPES.new else obj.username

    class Meta:
        model = models.Offer
        fields = (
            'username',
            'join_at',
        )


class OfferRejectFormSerializer(FemidaSerializer):

    recruiter = serializers.SerializerMethodField()

    def get_recruiter(self, obj):
        user = self.root.context.get('user')
        if user:
            return user.username

    class Meta:
        model = models.Offer
        fields = (
            'recruiter',
            'salary_expectations',
            'salary_expectations_currency',
        )


class OfferGeneratedTextSerializer(FemidaSerializer):
    """
    Текст оффера сгенерированный автоматически
    """

    offer_text = serializers.SerializerMethodField()

    def get_offer_text(self, obj):
        ctl = OfferCtl(obj)
        return ctl.generated_offer_text

    class Meta:
        model = models.Offer
        fields = (
            'offer_text',
        )


class OfferUpdateDepartmentFormSerializer(serializers.Serializer):

    department = serializers.IntegerField(source='department_id')


class RelocationPackageSerializer(serializers.ModelSerializer):

    label = serializers.CharField(source='type')

    class Meta:
        model = RelocationPackage
        fields = (
            'label',
            'type',
            'base_allowance',
            'min_grade',
            'max_grade',
        )
