# -*- coding:utf-8 -*-

import json
import re

from celery_once import QueueOnce
from flask import current_app
from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import NotFound

from api.extensions import celery
from api.extensions import db
from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.audit import AuditOperateSource
from api.lib.perm.acl.audit import AuditOperateType
from api.lib.perm.acl.cache import AppCache
from api.lib.perm.acl.cache import RoleCache
from api.lib.perm.acl.cache import RoleRelationCache
from api.lib.perm.acl.cache import UserCache
from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.record import OperateRecordCRUD
from api.models.acl import Resource
from api.models.acl import Role
from api.models.acl import Trigger


@celery.task(base=QueueOnce,
             name="acl.role_rebuild",
             queue=ACL_QUEUE,
             once={"graceful": True, "unlock_before_run": True})
def role_rebuild(rids, app_id):
    rids = rids if isinstance(rids, list) else [rids]
    for rid in rids:
        RoleRelationCache.rebuild(rid, app_id)

    current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))


@celery.task(name="acl.update_resource_to_build_role", queue=ACL_QUEUE)
def update_resource_to_build_role(resource_id, app_id, group_id=None):
    rids = [i.id for i in Role.get_by(__func_isnot__key_uid=None, fl='id', to_dict=False)]
    rids += [i.id for i in Role.get_by(app_id=app_id, fl='id', to_dict=False)]
    rids += [i.id for i in Role.get_by(__func_is___key_uid=None, __func_is___key_app_id=None, fl='id', to_dict=False)]

    current_app.logger.info(rids)
    for rid in rids:
        if resource_id and resource_id in RoleRelationCache.get_resources(rid, app_id).get('id2perms', {}):
            RoleRelationCache.rebuild2(rid, app_id)

        if group_id and group_id in RoleRelationCache.get_resources(rid, app_id).get('group2perms', {}):
            RoleRelationCache.rebuild2(rid, app_id)


@celery.task(name="acl.apply_trigger", queue=ACL_QUEUE)
def apply_trigger(_id, resource_id=None, operator_uid=None):
    db.session.remove()

    from api.lib.perm.acl.permission import PermissionCRUD

    trigger = Trigger.get_by_id(_id)
    if trigger is None:
        return

    uid = json.loads(trigger.uid or '[]')
    if resource_id is None:
        wildcard = (trigger.wildcard or '')

        if wildcard and uid:
            query = Resource.get_by(__func_in___key_uid=uid,
                                    app_id=trigger.app_id,
                                    resource_type_id=trigger.resource_type_id,
                                    fl=['id', 'app_id'],
                                    only_query=True)
            try:
                re.compile(wildcard)

                resources = query.filter(Resource.name.op('regexp')(wildcard)).all()
            except:
                resources = query.filter(Resource.name.ilike(wildcard.replace('*', '%'))).all()
        elif wildcard:
            query = Resource.get_by(app_id=trigger.app_id,
                                    resource_type_id=trigger.resource_type_id,
                                    only_query=True)
            try:
                re.compile(wildcard)

                resources = query.filter(Resource.name.op('regexp')(wildcard)).all()
            except:
                resources = query.filter(Resource.name.ilike(wildcard.replace('*', '%'))).all()
        elif uid:
            resources = Resource.get_by(__func_in___key_uid=uid,
                                        app_id=trigger.app_id,
                                        resource_type_id=trigger.resource_type_id,
                                        to_dict=False)
        else:
            resources = []
    else:
        resources = [Resource.get_by_id(resource_id)]

    perms = json.loads(trigger.permissions)
    roles = json.loads(trigger.roles)
    for resource in resources:
        for rid in roles:
            try:
                PermissionCRUD.grant(rid, perms, resource.id, rebuild=False, source=AuditOperateSource.trigger)
            except (NotFound, BadRequest):
                pass

    AuditCRUD.add_trigger_log(trigger.app_id, trigger.id, AuditOperateType.trigger_apply, {}, trigger.to_dict(),
                              {'uid': uid,
                               'resource_ids': [r.id for r in resources],
                               'perms': perms,
                               'rids': roles},
                              uid=operator_uid, source=AuditOperateSource.trigger)

    if resources:
        role_rebuild(roles, resources[0].app_id)


@celery.task(name="acl.cancel_trigger", queue=ACL_QUEUE)
def cancel_trigger(_id, resource_id=None, operator_uid=None):
    db.session.remove()

    from api.lib.perm.acl.permission import PermissionCRUD

    trigger = Trigger.get_by_id(_id)
    if trigger is None:
        return

    uid = json.loads(trigger.uid or '[]')
    if resource_id is None:
        wildcard = (trigger.wildcard or '')

        if wildcard and uid:
            query = Resource.get_by(__func_in___key_uid=uid,
                                    app_id=trigger.app_id,
                                    resource_type_id=trigger.resource_type_id,
                                    fl=['id', 'app_id'],
                                    only_query=True)
            try:
                re.compile(wildcard)

                resources = query.filter(Resource.name.op('regexp')(wildcard)).all()
            except:
                resources = query.filter(Resource.name.ilike(wildcard.replace('*', '%'))).all()
        elif wildcard:
            query = Resource.get_by(app_id=trigger.app_id,
                                    resource_type_id=trigger.resource_type_id,
                                    only_query=True)
            try:
                re.compile(wildcard)

                resources = query.filter(Resource.name.op('regexp')(wildcard)).all()
            except:
                resources = query.filter(Resource.name.ilike(wildcard.replace('*', '%'))).all()
        elif uid:
            resources = Resource.get_by(__func_in___key_uid=uid,
                                        app_id=trigger.app_id,
                                        resource_type_id=trigger.resource_type_id,
                                        to_dict=False)
        else:
            resources = []
    else:
        resources = [Resource.get_by_id(resource_id)]

    perms = json.loads(trigger.permissions)
    roles = json.loads(trigger.roles)
    for resource in resources:
        if not resource:
            continue
        for rid in roles:
            try:
                PermissionCRUD.revoke(rid, perms, resource.id, rebuild=False, source=AuditOperateSource.trigger)
            except (NotFound, BadRequest):
                pass

    AuditCRUD.add_trigger_log(trigger.app_id, trigger.id, AuditOperateType.trigger_cancel, {}, trigger.to_dict(),
                              {'uid': uid,
                               'resource_ids': [r.id for r in resources if r],
                               'perms': perms,
                               'rids': roles},
                              uid=operator_uid, source=AuditOperateSource.trigger)

    if resources:
        role_rebuild(roles, resources[0].app_id)


@celery.task(name="acl.op_record", queue=ACL_QUEUE)
def op_record(app, rolename, operate_type, obj):
    if isinstance(app, int):
        app = AppCache.get(app)
        app = app and app.name

    if isinstance(rolename, int):
        u = UserCache.get(rolename)
        if u:
            rolename = u.username
        if not u:
            r = RoleCache.get(rolename)
            if r:
                rolename = r.name

    OperateRecordCRUD.add(app, rolename, operate_type, obj)