from typing import Any

import sform

from django.core import exceptions

from staff.departments.models import DepartmentRoles
from staff.lib.models.roles_chain import has_role


def readonly_if_locked(func):
    def wrapped(self, name):
        if self.base_initial.get('locked_proposal'):
            return sform.READONLY
        return func(self, name)
    return wrapped


def actions_matcher(old_actions, new_actions, empty_action):
    old = {
        act['action_id']: act
        for act in old_actions
    }
    for act in new_actions:
        yield old.get(act['action_id'], empty_action), act


class ProposalBaseForm(sform.SForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.base_initial.update(kwargs.get('base_initial', {}))

        if self.initial.get('applied_at') or self.initial.get('locked'):
            self.base_initial['locked_proposal'] = True

    @readonly_if_locked
    def get_field_state(self, name):
        return super(ProposalBaseForm, self).get_field_state(name)

    def _has_hr_analyst_role(self, observer):
        return has_role(observer, (DepartmentRoles.HR_ANALYST.value,))

    def _clean_hr_analyst_field(self, field, value):
        author_user = self.base_initial.get('author_user')
        previous_value = self.initial.get(field)

        if value:
            if author_user and self._has_hr_analyst_role(author_user.get_profile()):
                if hasattr(value, 'name'):
                    return value.name
                return value

            raise exceptions.ValidationError(
                f'No permissions for field `{field}`',
                code='no_permission',
            )

        return previous_value if previous_value else None


class ProposalEntityActionForm(ProposalBaseForm):
    action_id = sform.CharField(max_length=10, state=sform.REQUIRED)


class GridFieldWithMeta(sform.GridField):
    """Специальный GridField, добавляющий `meta` в каждый action"""
    type_name = 'grid'  # мимикрируем под старый тип

    def populate_with_meta(self, fields_list, base_initial):
        pass

    def data_as_dict(self, base_initial, **kwargs):
        fields_list = super().data_as_dict(base_initial=base_initial, **kwargs)
        self.populate_with_meta(fields_list, base_initial)
        return fields_list

    def _protect_actions_matcher(self, new_value: Any) -> None:
        if not isinstance(new_value, list):
            raise sform.ValidationError('Value should be a list', code='should_be_a_list')
        for item in new_value:
            if not isinstance(item, dict):
                raise sform.ValidationError('Value should be a dict', code='should_be_a_dict')
            if 'action_id' not in item:
                raise sform.ValidationError('This field is required.', code='required')
            if not isinstance(item['action_id'], (str, int)):
                raise sform.ValidationError('Value should be a string or int', code='required')

    def clean(self, new_value, old_value, required, trim_on_empty, base_initial, base_data):
        self._protect_actions_matcher(new_value)
        return super().clean(new_value, old_value, required, trim_on_empty, base_initial, base_data)
