# coding: utf-8

import itertools
import logging

from django.conf import settings
from django.db import models
from django.utils import timezone
from django.db import connection

from idm.core.constants.groupmembership import GROUPMEMBERSHIP_STATE
from idm.core.constants.groupmembership_system_relation import MEMBERSHIP_SYSTEM_RELATION_STATE
from idm.core.constants.system import SYSTEM_GROUP_POLICY
from idm.users.models import GroupMembership


log = logging.getLogger(__name__)


class GroupMembershipSystemRelationQuerySet(models.QuerySet):
    def activated(self):
        return self.filter(state=MEMBERSHIP_SYSTEM_RELATION_STATE.ACTIVATED)

    def activating(self):
        return self.filter(state=MEMBERSHIP_SYSTEM_RELATION_STATE.ACTIVATING)

    def deprived(self):
        return self.filter(state=MEMBERSHIP_SYSTEM_RELATION_STATE.DEPRIVED)

    def depriving(self):
        return self.filter(state=MEMBERSHIP_SYSTEM_RELATION_STATE.DEPRIVING)

    def get_hold(self):
        return self.filter(state=MEMBERSHIP_SYSTEM_RELATION_STATE.HOLD)

    def active(self):
        return self.filter(
            state__in={MEMBERSHIP_SYSTEM_RELATION_STATE.ACTIVATED, MEMBERSHIP_SYSTEM_RELATION_STATE.HOLD},
        )

    def mark_depriving(self):
        get_paired_memberships_sql = '''
        WITH membreships_to_deprive AS (
                SELECT gmbs.id, system_id, group_id, user_id
                FROM core_groupmembershipsystemrelation gmbs INNER JOIN users_groupmembership gmb ON gmbs.membership_id = gmb.id
                WHERE gmbs.id IN %(memberships_to_deprive_ids)s
            ), active_memberships AS (
                SELECT gmbs.id, system_id, group_id, user_id
                FROM core_groupmembershipsystemrelation gmbs INNER JOIN users_groupmembership gmb ON gmbs.membership_id = gmb.id
                WHERE gmbs.id NOT IN %(memberships_to_deprive_ids)s AND gmbs.state IN %(active_states)s
            )
        SELECT membreships_to_deprive.id
        FROM membreships_to_deprive INNER JOIN active_memberships USING (system_id, group_id, user_id)
        '''

        params = {
            'memberships_to_deprive_ids': tuple(self.values_list('pk', flat=True)),
            'active_states': tuple(MEMBERSHIP_SYSTEM_RELATION_STATE.CAN_BE_ACTIVE_STATES),
        }

        if not params['memberships_to_deprive_ids']:
            return

        with connection.cursor() as cursor:
            cursor.execute(get_paired_memberships_sql, params)
            ids_to_deprive = list(itertools.chain(*cursor.fetchall()))

        now = timezone.now()
        self.filter(pk__in=ids_to_deprive).update(state=MEMBERSHIP_SYSTEM_RELATION_STATE.DEPRIVED, updated_at=now)
        log.info('Deprived memberships %s without system notification', ids_to_deprive)
        self.exclude(pk__in=ids_to_deprive).update(state=MEMBERSHIP_SYSTEM_RELATION_STATE.DEPRIVING, updated_at=now)
        log.info('Marked memberships %s as depriving', list(self.exclude(pk__in=ids_to_deprive).values_list('pk', flat=True)))


class GroupMembershipSystemRelationManager(models.Manager.from_queryset(GroupMembershipSystemRelationQuerySet)):
    def hold_membership_system_relations(self, existing_memberships_ids, actual_memberships_ids, system):
        # Переводим членство в hold на некоторое время

        log.info('Mark groupmembership_system_relations for %s hold', system)
        memberships_to_hold = (
            self
            .filter(
                membership_id__in=existing_memberships_ids,
                system=system,
                state__in=MEMBERSHIP_SYSTEM_RELATION_STATE.CAN_BE_PUT_ON_HOLD_STATES,
            )
            .exclude(membership_id__in=actual_memberships_ids)
        )
        memberships_to_hold.update(
            state=MEMBERSHIP_SYSTEM_RELATION_STATE.HOLD,
            updated_at=timezone.now(),
        )
        log.info('Marking hold groupmembership_system_relations for %s completed', system)

    def activate_membership_system_relations(self, existing_memberships_ids, actual_memberships_ids, system):

        log.info('Mark groupmembership_system_relations for %s activating', system)
        memberships_to_activating = (
            self
            .filter(
                membership_id__in=existing_memberships_ids,
                system=system,
                state__in=MEMBERSHIP_SYSTEM_RELATION_STATE.CAN_BE_ACTIVATED_STATES,
                membership__user__is_active=True,
            )
            .filter(membership_id__in=actual_memberships_ids)
        )
        memberships_to_activating.update(
            state=MEMBERSHIP_SYSTEM_RELATION_STATE.ACTIVATING,
            updated_at=timezone.now(),
        )
        log.info('Marking activating groupmembership_system_relations for %s completed', system)

    def create_membership_system_relations(self, existing_memberships_ids, actual_memberships_ids, system):
        log.info('Create new groupmembership_system_relations for %s', system)

        ids_to_create = (
            GroupMembership
            .objects
            .filter(id__in=actual_memberships_ids)
            .exclude(id__in=existing_memberships_ids)
            .values_list('id', flat=True)
        )

        self.bulk_create_groupmembership_system_relations(ids_to_create, system)
        log.info('Create new groupmembership_system_relations for %s completed', system)

    def bulk_create_groupmembership_system_relations(self, membership_ids, system):
        batch_size = settings.IDM_CREATE_GROUPMEMBERSHIPSYSTEMRELATION_BATCH_SIZE

        need_add_memberships = (
            self.model(membership_id=membership_id, system=system, state=MEMBERSHIP_SYSTEM_RELATION_STATE.ACTIVATING)
            for membership_id in membership_ids
        )

        self.bulk_create(need_add_memberships, batch_size=batch_size)

    def move_dismissed_to_depriving(self, system):
        log.info('Mark dismissed groupmembership_system_relations for %s depriving', system)
        memberships_to_depriving = self.filter(
            state__in=MEMBERSHIP_SYSTEM_RELATION_STATE.CAN_BE_PUT_ON_DEPRIVING_STATES,
            membership__user__is_active=False,
            system=system,
        )
        memberships_to_depriving.mark_depriving()

    def deprive_memberships_without_roles(self, actual_groups_ids, system):
        memberships_to_deprive = (
            self
            .filter(
                state__in=MEMBERSHIP_SYSTEM_RELATION_STATE.CAN_BE_PUT_ON_DEPRIVING_STATES,
                system=system,
            )
            .exclude(membership__group_id__in=actual_groups_ids)
        )

        memberships_to_deprive.mark_depriving()

    def move_from_hold_to_depriving(self, system):
        date_to_depriving = (
            timezone.now() - timezone.timedelta(seconds=settings.IDM_ONHOLD_GROUPMEMBERSHIP_SYSTEM_SECONDS)
        )
        log.info('Mark groupmembership_system_relations for %s depriving', system)

        memberships_to_depriving = self.filter(
            state=MEMBERSHIP_SYSTEM_RELATION_STATE.HOLD,
            updated_at__lt=date_to_depriving,
            system=system,
        )

        memberships_to_depriving.mark_depriving()

    def update_passport_logins(self, system):
        need_update = (
            'need_update = TRUE,'
            if system.group_policy == SYSTEM_GROUP_POLICY.AWARE_OF_MEMBERSHIPS_WITH_LOGINS
            else ''
        )

        SQL_UPDATE_PASSPORT_LOGINS = '''
        UPDATE core_groupmembershipsystemrelation gmsr
           SET {need_update} passport_login_id = gm.passport_login_id
          FROM users_groupmembership gm
         WHERE gmsr.membership_id = gm.id
           AND gmsr.system_id = %(system_pk)s
           AND (
                   gmsr.passport_login_id != gm.passport_login_id
                OR (
                       gmsr.passport_login_id IS NULL
                   AND gm.passport_login_id IS NOT NULL
                   )
                OR (
                       gmsr.passport_login_id IS NOT NULL
                   AND gm.passport_login_id IS NULL
                   )
                )
           AND gmsr.state IN %(pushable_states)s;
        '''.format(need_update=need_update)

        with connection.cursor() as cursor:
            cursor.execute(SQL_UPDATE_PASSPORT_LOGINS, {
                'system_pk': system.pk,
                'pushable_states': tuple(map(str, MEMBERSHIP_SYSTEM_RELATION_STATE.PUSHABLE_STATES)),
            })

    def sync_groupmembership_system_relations(self, system, group=None):
        from idm.core.models import System
        assert isinstance(system, System), 'Class %s not System' % type(system).__name__
        log.info('Start groupmembership_system_relations for %s', system)

        actual_groups_ids = set(system.get_group_ids_with_active_roles().distinct())

        actual_memberships_ids = (
            GroupMembership.objects
            .filter(group_id__in=actual_groups_ids, state__in=GROUPMEMBERSHIP_STATE.PUSHABLE_STATES)
            .values_list('id', flat=True)
        )

        existing_memberships_ids = self.filter(system=system).values_list('membership_id', flat=True)

        if group is not None:
            actual_memberships_ids = actual_memberships_ids.filter(group=group)
            existing_memberships_ids = existing_memberships_ids.filter(membership__group=group)

        # Переведем активные членства уволенных в depriving
        self.move_dismissed_to_depriving(system)

        # Удалим членства в группах, на которые больше нет выданных ролей
        self.deprive_memberships_without_roles(actual_groups_ids, system)

        # Переведем ранее помеченные неактивными в активные
        self.activate_membership_system_relations(existing_memberships_ids, actual_memberships_ids, system)

        # Переведем в hold статус
        self.hold_membership_system_relations(existing_memberships_ids, actual_memberships_ids, system)

        # Переведем из статуса hold в depriving
        self.move_from_hold_to_depriving(system)

        # Создадим недостающие связи
        self.create_membership_system_relations(existing_memberships_ids, actual_memberships_ids, system)

        # Если изменился паспортный логин, проставляем need_update
        self.update_passport_logins(system)
