# coding: utf-8


import logging

from django.db import connection, transaction
from django.utils import timezone

from idm.notification.utils import report_problem
from idm.users.constants.group import GROUP_TYPES
from idm.users.models import Group, GroupMembership
from idm.utils.check_switch import check_switch
from idm.utils.lock import lock, LockAlreadyAcquired


log = logging.getLogger(__name__)


SYNC_INDIRECT_MEMBERSHIPS_SQL = '''
CREATE OR REPLACE FUNCTION aggregate_state (prev_state text, state text) RETURNS text AS $$
BEGIN
    CASE
        WHEN prev_state = 'active' THEN
            RETURN 'active';
        WHEN prev_state = 'inactive' THEN
            RETURN state;
    END CASE;
END; $$
LANGUAGE PLPGSQL;

DROP AGGREGATE IF EXISTS max_state(text) CASCADE;

CREATE AGGREGATE max_state (text) (
    SFUNC = aggregate_state,
    STYPE = text,
    INITCOND = 'inactive'
);

-- Тут бы сохранять результат во временную таблицу.
-- Или что будет, если попадем этим селектом между транзакций?

CREATE OR REPLACE VIEW users_indirect_membership
AS
  SELECT
    gm.user_id,
    g.id AS group_id,
    max_state(gm.state) as state

  FROM users_groupmembership gm
  JOIN users_groupclosure gc ON (gm.group_id=gc.child_id)
  JOIN users_group g ON (g.id=gc.parent_id)
  LEFT JOIN users_groupmembership AS gm2 ON (gm.id=gm2.id AND g.id=gm2.group_id)
  WHERE
    g.parent_id IS NOT NULL AND
    gm.is_direct = TRUE AND
    NOT gm2.is_direct IS TRUE
  GROUP BY gm.user_id, g.id;

-- Создадим новые опосредованные членства
INSERT
  INTO users_groupmembership (user_id, group_id, state, is_direct, date_joined, notified_about_passport_login)
  (
    SELECT T1.user_id, T1.group_id, 'active', FALSE, CURRENT_TIMESTAMP, FALSE
    FROM users_indirect_membership AS T1
    LEFT JOIN users_groupmembership as T2
        ON (T1.user_id=T2.user_id AND T1.group_id=T2.group_id AND NOT T2.is_direct IS TRUE)
    WHERE T1.state = 'active' AND T2.user_id is NULL
  );

-- Переведем старые членства по необходимости в статус inactive
UPDATE users_groupmembership SET state='inactive', date_leaved=CURRENT_TIMESTAMP
WHERE id IN (
    SELECT T1.id FROM users_groupmembership AS T1
    JOIN users_indirect_membership AS T2
        ON (T1.user_id=T2.user_id AND T1.group_id=T2.group_id)
    WHERE T2.state = 'inactive' AND NOT T1.state = 'inactive' AND T1.is_direct=FALSE
);

-- Переведем старые членства по необходимости в статус active
UPDATE users_groupmembership SET
    state='active',
    date_leaved=NULL,
    date_joined=CURRENT_TIMESTAMP,
    notified_about_passport_login=FALSE
WHERE id IN (
    SELECT T1.id FROM users_groupmembership AS T1
    JOIN users_indirect_membership AS T2
        ON (T1.user_id=T2.user_id AND T1.group_id=T2.group_id)
    WHERE T2.state = 'active' AND NOT T1.state = 'active' AND T1.is_direct=FALSE
);

UPDATE users_groupmembership SET state='inactive', date_leaved=CURRENT_TIMESTAMP
WHERE id in (
    SELECT T1.id FROM users_groupmembership AS T1
    LEFT JOIN users_indirect_membership AS T2
    ON (T2.group_id=T1.group_id AND T2.user_id=T1.user_id)
    WHERE T2.group_id is NULL AND T1.state != 'inactive' AND T1.is_direct=FALSE
);
'''


@transaction.atomic
def sync_indirect_memberships(block=False):
    result = False
    with lock('idm.users.sync.groups.sync_indirect_memberships', block=block) as acquired:
        if not acquired:
            log.info('Group synchronization cannot be started: lock cannot be acquired')
            return result
        log.info('Start sync indirect memberships')
        with connection.cursor() as cursor:
            cursor.execute(SYNC_INDIRECT_MEMBERSHIPS_SQL)
        log.info('Sync indirect memberships completed')
        result = True
    return result


@check_switch('disable_group_sync')
def sync_group_type(group_type, block=False):
    result = False
    with lock('idm.users.tasks.SyncGroupsTask:%s' % group_type, block=block) as acquired:
        if not acquired:
            log.info('Group synchronization (type:%s) cannot be started: lock cannot be acquired', group_type)
            return result
        root = Group.objects.get_root(group_type)
        try:
            log.info('Starting group synchronization for group type %s', group_type)
            root.synchronize()
            log.info('Group synchronization for group type %s has successfully finished', group_type)
            result = True
        except Exception as exc:
            log.warning('Group syncronization for group type %s failed', group_type, exc_info=1)
            group_type_name = dict(Group.GROUP_CHOICES)[group_type]
            report_problem(
                'Проблема с синхронизацией групп [{}]'.format(group_type),
                ['emails/service/group_sync_error.txt'],
                {'exception_type': type(exc).__name__, 'group_type': group_type_name},
            )
    return result


def deprive_depriving_groups(since=None, force=False):
    """Функция отзывает роли у групп, находящихся в статусе depriving"""
    with lock('idm.users.sync.groups.deprive_depriving_groups') as acquired:
        if not acquired:
            raise LockAlreadyAcquired

        groups = Group.objects.depriving()
        if not force:
            if since is None:
                since = timezone.now()
            groups = groups.filter(expire_at__lte=since)

        for group in groups:
            group.deprive()

        return True
