import logging
import waffle

from datetime import timedelta

from django.db.models import Prefetch, Value, Case, When, CharField
from django.utils import timezone

from intranet.femida.src.candidates.choices import (
    CONSIDERATION_STATUSES,
    CONSIDERATION_EXTENDED_STATUSES,
    CONSIDERATION_ISSUE_LEVELS,
)
from intranet.femida.src.candidates.models import Consideration, ConsiderationIssue
from intranet.femida.src.utils.queryset import queryset_iterator


logger = logging.getLogger(__name__)


class IssueTypesRegistry:

    _issue_types = {}

    @classmethod
    def register_issue_type(cls, klass):
        assert klass.type_name not in cls._issue_types
        cls._issue_types[klass.type_name] = klass
        return klass

    @classmethod
    def get_issue_types(cls):
        return cls._issue_types

    @classmethod
    def get_issue_type(cls, type_name):
        return cls._issue_types.get(type_name)


register_issue_type = IssueTypesRegistry.register_issue_type


class IssueTypeBase:
    """
    Базовый класс для создания и обновления level-а ConsiderationIssue определённого типа
    """
    type_name = None

    @property
    def dry_run(self):
        return not waffle.switch_is_active('enable_consideration_issues_creation')

    @classmethod
    def get_annotations(cls):
        return {}

    @classmethod
    def get_consideration_qs(cls):
        return (
            Consideration.unsafe
            .filter(state=CONSIDERATION_STATUSES.in_progress)
            .annotate(**cls.get_annotations())
        )

    @classmethod
    def get_issue_level_subquery(cls):
        return Value(None, output_field=CharField())

    @classmethod
    def get_issue_params(cls, consideration):
        return {}

    @classmethod
    def get_consideration_with_issue_level_qs(cls):
        return queryset_iterator(
            queryset=(
                cls.get_consideration_qs()
                .annotate(issue_level=cls.get_issue_level_subquery())
                .filter(issue_level__isnull=False)
                .prefetch_related(Prefetch(
                    'consideration_issues',
                    queryset=(
                        ConsiderationIssue.objects
                        .filter(type=cls.type_name, is_resolved=False)
                    ),
                    to_attr='existing_consideration_issues',
                ))
            ),
            chunk_size=5000,
        )

    def _update_consideration_issues(self, level_by_cons_issue_id):
        cons_issues_to_update = []
        for consideration_issue in ConsiderationIssue.objects.filter(id__in=level_by_cons_issue_id):
            consideration_issue.level = level_by_cons_issue_id[consideration_issue.id]
            cons_issues_to_update.append(consideration_issue)
        if not self.dry_run:
            ConsiderationIssue.objects.bulk_update(
                cons_issues_to_update,
                ['level', 'modified'],
                batch_size=1000,
            )
        logger.info('Updated %d consideration_issues', len(cons_issues_to_update))

    def _create_consideration_issues(self, cons_issues_to_create):
        if not self.dry_run:
            ConsiderationIssue.objects.bulk_create(cons_issues_to_create, batch_size=1000)
        logger.info('Created %d consideration_issues', len(cons_issues_to_create))

    def update_or_create_consideration_issues(self):
        level_by_cons_issue_id = {}  # for updating
        cons_issues_to_create = []
        for consideration in self.get_consideration_with_issue_level_qs():
            # если у consideration уже есть незарезолвленный issue
            # такого типа, то обновляем level
            # иначе, создаём новый issue
            if consideration.existing_consideration_issues:
                consideration_issue = consideration.existing_consideration_issues[0]
                if consideration_issue.level == consideration.issue_level:
                    continue
                level_by_cons_issue_id[consideration_issue.id] = consideration.issue_level
            else:
                cons_issues_to_create.append(
                    ConsiderationIssue(
                        consideration=consideration,
                        type=self.type_name,
                        level=consideration.issue_level,
                        params=self.get_issue_params(consideration),
                    )
                )
        self._update_consideration_issues(level_by_cons_issue_id)
        self._create_consideration_issues(cons_issues_to_create)


class ThresholdIssueType(IssueTypeBase):
    """
    IssueTypeManager, в котором issue_level определяется исходя из значения
    поля field_name у consideration в соответствии с конфигом thresholds

    Формат thresholds: словарь, в котором
        - ключи: CONSIDERATION_EXTENDED_STATUSES, если условия отличаются
        для разных статусов, либо любой ключ, например "any"
        - значения: словари, в которых
            - ключи: CONSIDERATION_ISSUE_LEVELS
            - значения: те значения, с которыми будет сравниваться поле

    То, как поле field_name будет сравниваться со значениями из thresholds,
    можно регулировать с помощью get_condition_key, get_condition_value
    """
    field_name = None
    thresholds = {}

    @classmethod
    def get_condition_key(cls):
        return cls.field_name

    @classmethod
    def get_condition_value(cls, threshold):
        return threshold

    @classmethod
    def get_issue_level_subquery(cls):
        cases = []
        for level, _ in CONSIDERATION_ISSUE_LEVELS:
            for extended_status, thresholds_by_level in cls.thresholds.items():
                condition = (
                    {'extended_status': extended_status}
                    if extended_status in CONSIDERATION_EXTENDED_STATUSES
                    else {}
                )
                threshold = thresholds_by_level.get(level)
                if threshold is not None:
                    condition[cls.get_condition_key()] = cls.get_condition_value(threshold)
                    condition['then'] = Value(level)
                    cases.append(When(**condition))
        if cases:
            return Case(*cases, output_field=CharField())
        return super().get_issue_level_subquery()

    @classmethod
    def get_issue_params(cls, consideration):
        return {cls.field_name: getattr(consideration, cls.field_name)}


class DateThresholdIssueType(ThresholdIssueType):
    """
    ThresholdIssueTypeManager для issues, у которых level определяется
    по полю с типом DateField/DateTimeField
    """
    now = None
    scale = 'days'

    @classmethod
    def get_condition_key(cls):
        return f'{cls.field_name}__lte'

    @classmethod
    def get_condition_value(cls, threshold):
        return cls.now - timedelta(**{cls.scale: int(threshold)})

    @classmethod
    def get_issue_level_subquery(cls):
        cls.now = timezone.now()
        return super().get_issue_level_subquery()

    @classmethod
    def get_issue_params(cls, consideration):
        date_field = getattr(consideration, cls.field_name)
        return {cls.field_name: date_field.strftime('%Y-%m-%d')}
