import sform

from datetime import date, timedelta

from django.conf import settings
from django.core.exceptions import ValidationError
from staff.lib.utils.date import parse_datetime

from staff.departments.edit.proposal_mongo import mongo, MONGO_COLLECTION_NAME, from_mongo_id
from staff.departments.models import ProposalMetadata

from staff.proposal.forms.base import ProposalBaseForm
from staff.proposal.forms.conversions import _convert_from_form_data, _convert_to_new_initial
from staff.proposal.forms.department import DepartmentChangesProposalForm
from staff.proposal.forms.person import PersonsChangesProposalForm
from staff.proposal.forms.vacancy import VacancyChangesProposalForm
from staff.proposal.forms.headcount import HeadcountsChangesProposalForm
from staff.users.models import User


class ProposalForm(ProposalBaseForm):
    persons = sform.FieldsetField(PersonsChangesProposalForm)
    departments = sform.FieldsetField(DepartmentChangesProposalForm)
    vacancies = sform.FieldsetField(VacancyChangesProposalForm)
    headcounts = sform.FieldsetField(HeadcountsChangesProposalForm)

    apply_at = sform.DateField(state=sform.REQUIRED)

    description = sform.CharField(max_length=2048)
    link_to_ticket = sform.CharField(max_length=50, default='', state=sform.NORMAL)

    splitted_from = sform.CharField(state=sform.READONLY)
    splitted_to = sform.CharField(state=sform.READONLY)

    def __init__(self, *args, **kwargs):
        initial_old_style = kwargs.pop('initial_old_style', None)
        if initial_old_style:
            initial = _convert_to_new_initial(initial_old_style)
            kwargs['initial'] = initial

        super().__init__(*args, **kwargs)

    @classmethod
    def for_user(cls, user: User, data=None):
        return cls(data=data, base_initial={'author_user': user})

    def clean(self):
        cd = super().clean()

        missed_key = {'persons', 'departments', 'vacancies', 'headcounts'} - set(cd)
        if missed_key:
            raise ValidationError(
                '`{missed}` is missed'.format(missed=missed_key),
                code='configuration_error',
            )

        departments_actions = cd['departments'].get('actions')
        persons_actions = cd['persons'].get('actions')
        vacancies_actions = cd['vacancies'].get('actions')
        headcounts_actions = cd['headcounts'].get('actions')

        if not any([departments_actions, persons_actions, vacancies_actions, headcounts_actions]):
            raise ValidationError(
                'No actions provided',
                code='no_actions_error'
            )

        return cd

    @staticmethod
    def clean_followers(followers_qs):
        return list(followers_qs.values_list('login', flat=True))

    def clean_apply_at(self, value):
        author = self.base_initial.get('author_user')
        if author and author.has_perm('django_intranet_stuff.can_execute_department_proposals'):
            return value

        if value < date.today():
            raise ValidationError(
                'Apply date should be at least today',
                code='invalid',
            )
        cleaned_data = self.cleaned_data
        if is_first_date_required(cleaned_data) and not is_first_date_selected(value):
            raise ValidationError(
                'First day of month required',
                code='should_be_first_day_of_month',
            )
        return value

    @staticmethod
    def get_apply_at(initial):
        datetime_value = initial.get('apply_at')
        if isinstance(datetime_value, str):
            datetime_value = parse_datetime(datetime_value)

        return datetime_value and datetime_value.date()

    def author_has_perm(self, permission):
        author_user = self.base_initial.get('author_user')
        return author_user and author_user.has_perm(permission)

    def clean_link_to_ticket(self, value):
        """
        Проверка что этот тикет является
        тикетом реструктуризации либо департаментным
        для проведённой либо удалённой заявки
        """

        value = value.strip()
        if not value:
            return ''

        if not self.author_has_perm('django_intranet_stuff.can_execute_department_proposals'):
            raise ValidationError(
                'No permissions for field `link_to_ticket`',
                code='no_permission',
            )

        try:
            value = normalize_link(value)
        except Exception:
            raise ValidationError(
                'Ticket {ticket_key} is not in valid queue'.format(ticket_key=value),
                code='department_linked_ticket_wrong_queue',
            )
        value = value.upper()
        _check_ticket_can_be_linked_to_proposal(ticket_key=value)

        return value

    def structure_as_dict(self, prefix=''):
        result = super(ProposalForm, self).structure_as_dict(prefix)
        author_user = self.base_initial.get('author_user')
        user_has_no_permission = (
             author_user
             and not author_user.has_perm('django_intranet_stuff.can_execute_department_proposals')
        )
        if user_has_no_permission or self.base_initial.get('_id'):
            result.pop('link_to_ticket', None)

        return result

    @property
    def cleaned_data_old_style(self):
        new_cd = self.cleaned_data
        old_cd = _convert_from_form_data(new_cd)
        return old_cd


def is_first_date_required(proposal_data):
    if 'persons' not in proposal_data:
        return False
    for person_action in proposal_data['persons']['actions']:
        if 'salary' in person_action:
            if person_action.get('department', {}).get('from_maternity_leave'):
                continue
            return True
    return False


def is_first_date_selected(date_value):
    return (
        date_value.day == 1
        and date_value - date.today() <= timedelta(days=365)
    )


def normalize_link(link: str) -> str:
    """Вернёт строку вида 'SALARY-12345' либо бросит ValueError"""
    if link.startswith(settings.PROPOSAL_QUEUE_ID + '-'):
        return link
    proposal_ticket_url = f'{settings.STARTREK_URL}/{settings.PROPOSAL_QUEUE_ID}-'
    if link.startswith(proposal_ticket_url):
        ticket_no = link.split(settings.PROPOSAL_QUEUE_ID + '-')[1]
        return f'{settings.PROPOSAL_QUEUE_ID}-{ticket_no}'

    raise ValueError('Wrong ticket link')


def _check_ticket_can_be_linked_to_proposal(ticket_key: str) -> None:
    """
    Привязать к заявке можно только тикет
    от другой уже проведённой либо удалённой заявки.
    """
    collection = mongo.db.get_collection(MONGO_COLLECTION_NAME)
    same_ticket_proposals = collection.find(
        {
            '$or': [
                {'tickets.department_linked_ticket': ticket_key},
                {'tickets.department_ticket': ticket_key},
                {'tickets.restructurisation': ticket_key},
                {'tickets.deleted_restructurisation': ticket_key},
                {'tickets.persons': ticket_key},
                {'tickets.deleted_persons': ticket_key},
            ]
        },
        {'_id': 1}
    )
    proposal_ids = [from_mongo_id(p['_id']) for p in same_ticket_proposals]
    proposal_metadata = (
        ProposalMetadata.objects
        .filter(proposal_id__in=proposal_ids)
        .values_list('proposal_id', 'applied_at', 'deleted_at')
        .order_by('id')
    )
    for proposal_id, applied_at, deleted_at in proposal_metadata:
        if not applied_at and not deleted_at:
            raise ValidationError(
                f'Unprocessed proposal {proposal_id} linked to the ticket {ticket_key} exists',
                code='ticket_is_linked_to_unfinished_proposal',
                params={
                    'ticket': ticket_key,
                    'proposal_uid': proposal_id,
                }
            )
