import logging

from django.conf import settings

from staff.celery_app import app

from staff.lib.db import atomic
from staff.lib.tasks import LockedTask

from staff.groups.autogroup import updated_autogroups
from staff.groups.service_update_tasks import ServiceUpdateTasks
from staff.groups.models import Group
from staff.groups.objects.controller import GroupCtl
from staff.groups.service.processor import ServiceDataProcessor
from staff.groups.service.datasource import get_services


logger = logging.getLogger(__name__)


# Вот здесь написано что делать https://st.yandex-team.ru/STAFF-1527
#  @app.task()
def recalculate_externals_count_for_group(group_id, delay_parent_task=True):
    """Перечисчитать число внешних сотрудников в группе c id=group_id и
    сохранить число в поле externals_count.
    После обновления группы инициировать
    пересчет числа внешних в родительской группе.
    Параметр delay_parent_task=True вызывает пересчет для родительской группы
    асинхронно.
    """
    try:
        group = Group.objects.get(pk=group_id)
    except Group.DoesNotExist:
        msg = (
            'Can\'t recalculate externals for group %d. '
            'Group does not exist.'
        )
        logger.warning(msg, group_id)
        return

    update_groups_members_count_if_necessary(group=group)

    parent = group.parent
    if parent is None:
        return

    if delay_parent_task:
        parent_task = recalculate_externals_count_for_group.delay
    else:
        parent_task = recalculate_externals_count_for_group
    parent_task(group_id=parent.id)


@app.task(ignore_result=True)
class RecalculateGroupsCounts(LockedTask):
    def locked_run(self, force=False, **kwargs):
        """Переcчет показателей в группах."""
        updates_count = 0
        for group in Group.objects.active():
            updated = update_groups_members_count_if_necessary(group=group)
            if updated:
                updates_count += 1

        logger.info(
            'EXTCNT updated externals_count in %d groups',
            updates_count
        )
        return updates_count


@app.task
def check_group_externals_count_for_all_groups():
    """Проверить число внешних для всех групп. Залогировать расхождения."""
    for group in Group.objects.active():
        real_externals_count = GroupCtl(group=group).get_all_externals_count()

        if group.externals_count != real_externals_count:
            log_group_external_count({
                'url': group.url,
                'counter_now': group.externals_count,
                'counter_should_be': real_externals_count,
            })


def log_group_external_count(params):
    msg = (
        'EXTCNT Checked group '
        '%(url)s. %(counter_now)d -> %(counter_should_be)d'
    )
    logger.info(msg, params)


@atomic
def update_groups_members_count_if_necessary(group):
    """Подсчитать кол-во внешних, я-деньговых, и яндексовых сотрудников
    в группе и перезаписать, если они отличаются от значений в базе.
    Вернуть флаг было ли совершено хоть одно обновление.
    """
    ctl = GroupCtl(group=group)
    yandex_count = ctl.get_all_yandex_members_count()
    yamoney_count = ctl.get_all_yamoney_members_count()
    externals_count = ctl.get_all_externals_count()

    # не будем ничего апдейтить, если ничего не изменилось
    if ((group.yandex_count, group.yamoney_count, group.externals_count) ==
            (yandex_count, yamoney_count, externals_count)):
        updated = False
    else:
        ctl.update({
            'yamoney_count': yamoney_count,
            'yandex_count': yandex_count,
            'externals_count': externals_count,
        })
        updated = True
    return updated


@app.task(ignore_results=True)
def update_service_groups(service_id, dry=False, force=False):
    lookup = {
        'fields': 'id,code,state,name,slug,description,parent,type,path,tags.slug',
        'id': service_id,
    }
    data = get_services(lookup=lookup, timeout=(2, 5, 10))

    for service_id, details in data:
        logger.info('Sync service %s', service_id)
        with atomic():
            for cmd in ServiceDataProcessor.process([(service_id, details)]):
                try:
                    if not dry:
                        if force or cmd.useful():
                            cmd.run()
                            logger.info("%s -> %s", cmd.message(), 'done')
                    else:
                        logger.debug("%s -> %s", cmd.message(), 'faked')
                except Exception:
                    logger.info("%s -> %s", cmd.message(), 'fail', exc_info=True)
                    raise
        logger.info('Sync service %s done', service_id)


@app.task(ignore_result=True)
class SyncAllServicesGroups(LockedTask):
    def locked_run(self, force=False, **kwargs):
        lookup = {'fields': 'id'}
        data = get_services(lookup=lookup, timeout=(2, 5, 10))
        services = [service_id for service_id, _ in data]
        ServiceUpdateTasks.schedule_services_update(services, force=force)


@app.task
class UpdateAutogroups(LockedTask):
    def locked_run(self, template_ids=None, verbosity=0):
        updated_autogroups(template_ids, verbosity=verbosity)


@app.task
def retry_all_tasks():
    for _ in range(settings.ABC_SERVICE_UPDATE_TASKS_ATTEMPT_COUNT):
        ServiceUpdateTasks.retry_all()
