from datetime import date, timedelta
from dateutil.relativedelta import relativedelta

from django.conf import settings

from staff.celery_app import app
from staff.lib.tasks import LockedTask

from staff.person.models import Staff, AFFILIATION

from staff.achievery.domain import GivenAchievement, Achievement, Person
from staff.achievery.permissions import RoleRegistry

NONE_LEVEL = -1

FULL_YANDEX = AFFILIATION.YANDEX, AFFILIATION.YAMONEY


def get_objects_kwargs():

    owner_login = settings.ACHIEVERY_ROBOT_PERSON_LOGIN
    owner_person = Staff.objects.get(login=owner_login)

    roles = RoleRegistry(owner_person)

    return {'user': owner_person, 'role_registry': roles}


def get_persons_with_achievement(achievement_id, persons_ids=None):
    persons_with_achievement = (
        GivenAchievement.get_model_class().objects
        .filter(achievement_id=achievement_id, is_active=True)
        .values_list('person_id', flat=True)
    )

    if persons_ids is not None:
        persons_with_achievement = (
            persons_with_achievement.filter(person_id__in=persons_ids)
        )

    return persons_with_achievement


def get_candidate_qs():
    return (
        Staff.objects
        .filter(
            affiliation__in=FULL_YANDEX,
            is_dismissed=False,
            is_robot=False,
        )
    )


def get_not_beginner_date():
    return date.today() - timedelta(settings.BEGINNER_DAYS)


def get_one_year_ago():
    return date.today() - relativedelta(years=1)


def calculate_years(join_at):
    today = date.today()
    years = today.year - join_at.year

    if years <= 0:
        return 0

    if today.month < join_at.month:
        return years - 1

    elif today.month == join_at.month and today.day < join_at.day:
        return years - 1

    return years


class GiveAchievements(LockedTask):
    @property
    def achievement_id(self):
        raise NotImplementedError

    def achievement_filter(self, persons_qs):
        return persons_qs

    def get_level(self, person):
        return -1

    def locked_run(self, persons_ids=None, *args, **kwargs):
        with_achievement = get_persons_with_achievement(
            self.achievement_id, persons_ids
        )

        persons_qs = get_candidate_qs().exclude(id__in=with_achievement)
        if persons_ids is not None:
            persons_qs = persons_qs.filter(id__in=persons_ids)

        persons_qs = self.achievement_filter(persons_qs)

        if not persons_qs:
            return

        objects_kwargs = get_objects_kwargs()
        achievement = (
            Achievement.objects(**objects_kwargs)
            .get(id=self.achievement_id)
        )

        for person in persons_qs:
            level = self.get_level(person)

            if level is None:
                continue

            person_ctl = Person(model=person, **objects_kwargs)
            obj, created = (
                GivenAchievement.objects(**objects_kwargs)
                .restore_or_create(
                    achievement=achievement,
                    person=person_ctl,
                    defaults={'level': level},
                )
            )

            if not created:
                obj.level = level
                obj.save()


@app.task(name='staff.person.tasks.achievery.GiveBeginnerAchievements')
class GiveBeginnerAchievements(GiveAchievements):
    @property
    def achievement_id(self):
        return settings.ACHIEVEMENT_BEGINNER_ID

    def achievement_filter(self, persons_qs):
        not_beginner_date = get_not_beginner_date()
        persons_qs = persons_qs.filter(join_at__gt=not_beginner_date)
        return persons_qs


@app.task(name='staff.person.tasks.achievery.GiveRestoredAchievements')
class GiveRestoredAchievements(GiveAchievements):
    @property
    def achievement_id(self):
        return settings.ACHIEVEMENT_RESTORED_ID

    def achievement_filter(self, persons_qs):
        persons_qs = persons_qs.filter(quit_at__isnull=False)
        return persons_qs


@app.task(name='staff.person.tasks.achievery.GiveEmployeeAchievements')
class GiveEmployeeAchievements(GiveAchievements):
    @property
    def achievement_id(self):
        return settings.ACHIEVEMENT_EMPLOYEE_ID

    def get_level(self, person):
        return calculate_years(person.join_at)

    def achievement_filter(self, persons_qs):
        persons_qs = persons_qs.filter(join_at__lte=get_one_year_ago()).order_by('id')
        return persons_qs


class DepriveAchievements(LockedTask):
    comment = ''

    @property
    def achievement_id(self):
        raise NotImplementedError

    def achievement_filter(self, given_qs):
        return given_qs

    def locked_run(self, persons_ids=None, comment='', **kwargs):
        given_achievements = (
            GivenAchievement.get_model_class().objects
            .filter(achievement_id=self.achievement_id, is_active=True)
        )
        if persons_ids is not None:
            given_achievements = given_achievements.filter(
                person_id__in=persons_ids
            )
        given_achievements = self.achievement_filter(given_achievements)

        objects_kwargs = get_objects_kwargs()

        for given_achievement in given_achievements:
            ctrl = GivenAchievement(
                model=given_achievement, **objects_kwargs
            )
            ctrl.comment = comment or self.comment
            ctrl.delete()


@app.task(name='staff.person.tasks.achievery.DepriveBeginnerAchievements')
class DepriveBeginnerAchievements(DepriveAchievements):
    @property
    def achievement_id(self):
        return settings.ACHIEVEMENT_BEGINNER_ID

    comment = "You're not a newbie now."

    def achievement_filter(self, given_qs):
        not_beginner_date = get_not_beginner_date()
        return given_qs.filter(person__join_at__lte=not_beginner_date)


@app.task(name='staff.person.tasks.achievery.DepriveEmployeeAchievements')
class DepriveEmployeeAchievements(DepriveAchievements):
    @property
    def achievement_id(self):
        return settings.ACHIEVEMENT_EMPLOYEE_ID

    comment = (
        'A new countdown started. '
        'In a year you will get this achievement again.'
    )

    def achievement_filter(self, given_qs):
        return given_qs.filter(person__join_at__gt=get_one_year_ago())


@app.task(name='staff.person.tasks.achievery.ChangeLevelEmployeeAchievements')
class ChangeLevelEmployeeAchievements(LockedTask):

    def locked_run(self, persons_ids=None, **kwargs):
        given_achievements = (
            GivenAchievement.get_model_class().objects
            .filter(
                achievement_id=settings.ACHIEVEMENT_EMPLOYEE_ID,
                is_active=True,
                person__is_robot=False,
                person__is_dismissed=False,
                person__affiliation__in=FULL_YANDEX,
                person__join_at__lte=get_one_year_ago(),
            )
            .values_list('id', 'level', 'person__join_at')
        )
        if persons_ids is not None:
            given_achievements = given_achievements.filter(
                person_id__in=persons_ids
            )

        given_achievements_levels = {
            id_: calculate_years(join_at)
            for id_, level, join_at in given_achievements
            if level != calculate_years(join_at)
        }

        given_achievements = (
            GivenAchievement.get_model_class().objects
            .filter(id__in=given_achievements_levels.keys())
        )

        objects_kwargs = get_objects_kwargs()

        for given_achievement in given_achievements:
            level = given_achievements_levels[given_achievement.id]
            ctrl = GivenAchievement(
                model=given_achievement, **objects_kwargs
            )
            ctrl.level = level
            ctrl.save()
