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


import copy
import json
import re
from fnmatch import fnmatch

from flask import abort

from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.audit import AuditOperateType
from api.lib.perm.acl.cache import UserCache
from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.resp_format import ErrFormat
from api.models.acl import Trigger
from api.tasks.acl import apply_trigger, cancel_trigger


class TriggerCRUD(object):
    cls = Trigger

    @staticmethod
    def get(app_id):
        triggers = Trigger.get_by(app_id=app_id)
        for trigger in triggers:
            trigger['uid'] = json.loads(trigger['uid'] or '[]')
            trigger['users'] = [UserCache.get(i).username for i in trigger['uid']]
            trigger['roles'] = json.loads(trigger['roles'] or '[]')
            trigger['permissions'] = json.loads(trigger['permissions'] or '[]')

        return triggers

    @staticmethod
    def add(app_id, **kwargs):
        kwargs.pop('app_id', None)
        kwargs['roles'] = json.dumps(kwargs['roles'] or [])
        kwargs['permissions'] = json.dumps(kwargs['permissions'] or [])

        kwargs['uid'] = json.dumps(kwargs.get('uid') or [])

        _kwargs = copy.deepcopy(kwargs)
        _kwargs.pop('name', None)

        Trigger.get_by(app_id=app_id, **_kwargs) and abort(400, ErrFormat.trigger_exists.format(""))
        t = Trigger.create(app_id=app_id, **kwargs)

        AuditCRUD.add_trigger_log(app_id, t.id, AuditOperateType.create, {}, t.to_dict(), {})

        return t

    @staticmethod
    def update(_id, **kwargs):
        existed = Trigger.get_by_id(_id) or abort(404, ErrFormat.trigger_not_found.format("id={}".format(_id)))
        origin = existed.to_dict()
        kwargs['roles'] = json.dumps(kwargs['roles'] or [])
        kwargs['uid'] = json.dumps(kwargs['uid'] or [])
        kwargs['permissions'] = json.dumps(kwargs['permissions'] or [])

        existed.update(**kwargs)

        AuditCRUD.add_trigger_log(existed.app_id, existed.id, AuditOperateType.update,
                                  origin, existed.to_dict(), {})

        return existed

    @staticmethod
    def delete(_id):
        existed = Trigger.get_by_id(_id) or abort(404, ErrFormat.trigger_not_found.format("id={}".format(_id)))
        origin = existed.to_dict()

        existed.soft_delete()

        AuditCRUD.add_trigger_log(existed.app_id, existed.id, AuditOperateType.delete,
                                  origin, {}, {}
                                  )

        return existed

    @staticmethod
    def apply(_id):
        trigger = Trigger.get_by_id(_id) or abort(404, ErrFormat.trigger_not_found.format("id={}".format(_id)))
        if not trigger.enabled:
            return abort(400, ErrFormat.trigger_disabled.format("id={}".format(_id)))

        user_id = AuditCRUD.get_current_operate_uid()

        apply_trigger.apply_async(args=(_id,), kwargs=dict(operator_uid=user_id), queue=ACL_QUEUE)

    @staticmethod
    def cancel(_id):
        trigger = Trigger.get_by_id(_id) or abort(404, ErrFormat.trigger_not_found.format("id={}".format(_id)))
        if not trigger.enabled:
            return abort(400, ErrFormat.trigger_disabled.format("id={}".format(_id)))

        user_id = AuditCRUD.get_current_operate_uid()

        cancel_trigger.apply_async(args=(_id,), kwargs=dict(operator_uid=user_id), queue=ACL_QUEUE)

    @staticmethod
    def match_triggers(app_id, resource_name, resource_type_id, uid):
        triggers = Trigger.get_by(app_id=app_id, enabled=True, resource_type_id=resource_type_id, to_dict=False)

        def _fnmatch(name, wildcard):
            import re

            try:
                return re.compile(wildcard).findall(name)
            except:
                return fnmatch(name, trigger.wildcard)

        uid = int(uid) if uid else uid
        _match_triggers = []
        for trigger in triggers:
            uids = json.loads(trigger.uid or '[]')
            if trigger.wildcard and uids:
                if _fnmatch(resource_name, trigger.wildcard) and uid in uids:
                    _match_triggers.append(trigger)
            elif trigger.wildcard:
                if _fnmatch(resource_name, trigger.wildcard):
                    _match_triggers.append(trigger)
            elif uids:
                if uid in uids:
                    _match_triggers.append(trigger)

        return _match_triggers

    @staticmethod
    def get_resources(app_id, resource_type_id, wildcard, uid):
        from api.models.acl import Resource

        wildcard = wildcard or ''

        if wildcard and uid:
            query = Resource.get_by(__func_in___key_uid=uid,
                                    app_id=app_id,
                                    resource_type_id=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 wildcard:
            query = Resource.get_by(app_id=app_id,
                                    resource_type_id=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=app_id,
                                        resource_type_id=resource_type_id,
                                        to_dict=False)
        else:
            resources = []

        return resources