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


import copy
import hashlib
from datetime import datetime

import ldap
from flask import current_app
from flask_sqlalchemy import BaseQuery

from api.extensions import db
from api.lib.database import CRUDModel
from api.lib.database import Model
from api.lib.database import SoftDeleteMixin
from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.const import OperateType


class App(Model):
    __tablename__ = "acl_apps"

    name = db.Column(db.String(64), index=True)
    description = db.Column(db.Text)
    app_id = db.Column(db.Text)
    secret_key = db.Column(db.Text)


class UserQuery(BaseQuery):
    def _join(self, *args, **kwargs):
        super(UserQuery, self)._join(*args, **kwargs)

    def authenticate(self, login, password):
        user = self.filter(db.or_(User.username == login,
                                  User.email == login)).filter(User.deleted.is_(False)).filter(User.block == 0).first()
        if user:
            current_app.logger.info(user)
            authenticated = user.check_password(password)
            if authenticated:
                from api.tasks.acl import op_record
                op_record.apply_async(args=(None, login, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)
        else:
            authenticated = False

        return user, authenticated

    def authenticate_with_key(self, key, secret, args, path):
        user = self.filter(User.key == key).filter(User.deleted.is_(False)).filter(User.block == 0).first()
        if not user:
            return None, False
        if user and hashlib.sha1('{0}{1}{2}'.format(
                path, user.secret, "".join(args)).encode("utf-8")).hexdigest() == secret:
            authenticated = True
        else:
            authenticated = False

        return user, authenticated

    def authenticate_with_ldap(self, username, password):
        ldap_conn = ldap.initialize(current_app.config.get('LDAP_SERVER'))
        ldap_conn.protocol_version = 3
        ldap_conn.set_option(ldap.OPT_REFERRALS, 0)
        if '@' in username:
            email = username
            who = current_app.config.get('LDAP_USER_DN').format(username.split('@')[0])
        else:
            who = current_app.config.get('LDAP_USER_DN').format(username)
            email = "{}@{}".format(who, current_app.config.get('LDAP_DOMAIN'))

        username = username.split('@')[0]
        user = self.get_by_username(username)
        try:

            if not password:
                raise ldap.INVALID_CREDENTIALS

            ldap_conn.simple_bind_s(who, password)

            if not user:
                from api.lib.perm.acl.user import UserCRUD
                user = UserCRUD.add(username=username, email=email)

            from api.tasks.acl import op_record
            op_record.apply_async(args=(None, username, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)

            return user, True
        except ldap.INVALID_CREDENTIALS:
            return user, False

    def search(self, key):
        query = self.filter(db.or_(User.email == key,
                                   User.nickname.ilike('%' + key + '%'),
                                   User.username.ilike('%' + key + '%')))
        return query

    def get_by_username(self, username):
        user = self.filter(User.username == username).first()

        return user

    def get_by_nickname(self, nickname):
        user = self.filter(User.nickname == nickname).first()

        return user

    def get_by_wxid(self, wx_id):
        user = self.filter(User.wx_id == wx_id).first()

        return user

    def get(self, uid):
        user = self.filter(User.uid == uid).first()

        return copy.deepcopy(user)


class User(CRUDModel, SoftDeleteMixin):
    __tablename__ = 'users'
    __bind_key__ = "user"
    query_class = UserQuery

    uid = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(32), unique=True)
    nickname = db.Column(db.String(20), nullable=True)
    department = db.Column(db.String(20))
    catalog = db.Column(db.String(64))
    email = db.Column(db.String(100), unique=True, nullable=False)
    mobile = db.Column(db.String(14), unique=True)
    _password = db.Column("password", db.String(80))
    key = db.Column(db.String(32), nullable=False)
    secret = db.Column(db.String(32), nullable=False)
    date_joined = db.Column(db.DateTime, default=datetime.utcnow)
    last_login = db.Column(db.DateTime, default=datetime.utcnow)
    block = db.Column(db.Boolean, default=False)
    has_logined = db.Column(db.Boolean, default=False)
    wx_id = db.Column(db.String(32))
    employee_id = db.Column(db.String(16), index=True)
    avatar = db.Column(db.String(128))
    # apps = db.Column(db.JSON)

    def __str__(self):
        return self.username

    def is_active(self):
        return not self.block

    def get_id(self):
        return self.uid

    @staticmethod
    def is_authenticated():
        return True

    def _get_password(self):
        return self._password

    def _set_password(self, password):
        self._password = hashlib.md5(password.encode('utf-8')).hexdigest()

    password = db.synonym("_password", descriptor=property(_get_password, _set_password))

    def check_password(self, password):
        if self.password is None:
            return False
        return self.password == password or self.password == hashlib.md5(password.encode('utf-8')).hexdigest()


class RoleQuery(BaseQuery):
    def _join(self, *args, **kwargs):
        super(RoleQuery, self)._join(*args, **kwargs)

    def authenticate(self, login, password):
        role = self.filter(Role.name == login).first()
        if role:
            authenticated = role.check_password(password)

            if authenticated:
                from api.tasks.acl import op_record
                op_record.apply_async(args=(None, login, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)

        else:
            authenticated = False

        return role, authenticated

    def authenticate_with_key(self, key, secret, args, path):
        role = self.filter(Role.key == key).filter(Role.deleted.is_(False)).first()
        if not role:
            return None, False
        if role and hashlib.sha1('{0}{1}{2}'.format(
                path, role.secret, "".join(args)).encode("utf-8")).hexdigest() == secret:
            authenticated = True
        else:
            authenticated = False

        return role, authenticated


class Role(Model):
    __tablename__ = "acl_roles"
    query_class = RoleQuery

    name = db.Column(db.String(64), index=True, nullable=False)
    is_app_admin = db.Column(db.Boolean, default=False)
    app_id = db.Column(db.Integer, db.ForeignKey("acl_apps.id"))
    uid = db.Column(db.Integer)
    _password = db.Column("password", db.String(80))
    key = db.Column(db.String(32))
    secret = db.Column(db.String(32))

    def _get_password(self):
        return self._password

    def _set_password(self, password):
        if password:
            self._password = hashlib.md5(password.encode('utf-8')).hexdigest()

    password = db.synonym("_password", descriptor=property(_get_password, _set_password))

    def check_password(self, password):
        if self.password is None:
            return False
        return self.password == password or self.password == hashlib.md5(password.encode('utf-8')).hexdigest()


class RoleRelation(Model):
    __tablename__ = "acl_role_relations"

    parent_id = db.Column(db.Integer, db.ForeignKey('acl_roles.id'))
    child_id = db.Column(db.Integer, db.ForeignKey('acl_roles.id'))
    app_id = db.Column(db.Integer, db.ForeignKey('acl_apps.id'))


class ResourceType(Model):
    __tablename__ = "acl_resource_types"

    name = db.Column(db.String(64), index=True)
    description = db.Column(db.Text)
    app_id = db.Column(db.Integer, db.ForeignKey('acl_apps.id'))


class ResourceGroup(Model):
    __tablename__ = "acl_resource_groups"

    name = db.Column(db.String(64), index=True, nullable=False)
    resource_type_id = db.Column(db.Integer, db.ForeignKey("acl_resource_types.id"))
    uid = db.Column(db.Integer, index=True)

    app_id = db.Column(db.Integer, db.ForeignKey('acl_apps.id'))

    resource_type = db.relationship("ResourceType", backref='acl_resource_groups.resource_type_id')


class Resource(Model):
    __tablename__ = "acl_resources"

    name = db.Column(db.String(128), nullable=False)
    resource_type_id = db.Column(db.Integer, db.ForeignKey("acl_resource_types.id"))
    uid = db.Column(db.Integer, index=True)

    app_id = db.Column(db.Integer, db.ForeignKey("acl_apps.id"))

    resource_type = db.relationship("ResourceType", backref='acl_resources.resource_type_id')


class ResourceGroupItems(Model):
    __tablename__ = "acl_resource_group_items"

    group_id = db.Column(db.Integer, db.ForeignKey('acl_resource_groups.id'), nullable=False)
    resource_id = db.Column(db.Integer, db.ForeignKey('acl_resources.id'), nullable=False)

    resource = db.relationship("Resource", backref='acl_resource_group_items.resource_id')


class Permission(Model):
    __tablename__ = "acl_permissions"

    name = db.Column(db.String(64), nullable=False)
    resource_type_id = db.Column(db.Integer, db.ForeignKey("acl_resource_types.id"))

    app_id = db.Column(db.Integer, db.ForeignKey("acl_apps.id"))


class RolePermission(Model):
    __tablename__ = "acl_role_permissions"

    rid = db.Column(db.Integer, db.ForeignKey('acl_roles.id'))
    resource_id = db.Column(db.Integer, db.ForeignKey('acl_resources.id'))
    group_id = db.Column(db.Integer, db.ForeignKey('acl_resource_groups.id'))
    perm_id = db.Column(db.Integer, db.ForeignKey('acl_permissions.id'))
    app_id = db.Column(db.Integer, db.ForeignKey("acl_apps.id"))

    perm = db.relationship("Permission", backref='acl_role_permissions.perm_id')


class Trigger(Model):
    __tablename__ = "acl_triggers"

    name = db.Column(db.String(128))
    wildcard = db.Column(db.Text)
    uid = db.Column(db.Text)  # TODO
    resource_type_id = db.Column(db.Integer, db.ForeignKey('acl_resource_types.id'))
    roles = db.Column(db.Text)  # TODO
    permissions = db.Column(db.Text)  # TODO
    enabled = db.Column(db.Boolean, default=True)

    app_id = db.Column(db.Integer, db.ForeignKey('acl_apps.id'))


class OperationRecord(Model):
    __tablename__ = "acl_operation_records"

    app = db.Column(db.String(32), index=True)
    rolename = db.Column(db.String(32), index=True)
    operate = db.Column(db.Enum(*OperateType.all()), nullable=False)
    obj = db.Column(db.JSON)


class AuditRoleLog(Model):
    __tablename__ = "acl_audit_role_logs"

    app_id = db.Column(db.Integer, index=True)

    operate_uid = db.Column(db.Integer, comment='操作人uid', index=True)
    operate_type = db.Column(db.String(32), comment='操作类型', index=True)
    scope = db.Column(db.String(16), comment='范围')
    link_id = db.Column(db.Integer, comment='资源id', index=True)
    origin = db.Column(db.JSON, default=dict(), comment='原始数据')
    current = db.Column(db.JSON, default=dict(), comment='当前数据')
    extra = db.Column(db.JSON, default=dict(), comment='其他内容')
    source = db.Column(db.String(16), default='', comment='来源')


class AuditResourceLog(Model):
    __tablename__ = "acl_audit_resource_logs"

    app_id = db.Column(db.Integer, index=True)
    operate_uid = db.Column(db.Integer, comment='操作人uid', index=True)
    operate_type = db.Column(db.String(16), comment='操作类型', index=True)

    scope = db.Column(db.String(16), comment='范围')
    link_id = db.Column(db.Integer, comment='资源名', index=True)
    origin = db.Column(db.JSON, default=dict(), comment='原始数据')
    current = db.Column(db.JSON, default=dict(), comment='当前数据')
    extra = db.Column(db.JSON, default=dict(), comment='权限名')
    source = db.Column(db.String(16), default='', comment='来源')


class AuditPermissionLog(Model):
    __tablename__ = "acl_audit_permission_logs"

    app_id = db.Column(db.Integer, index=True)

    operate_uid = db.Column(db.Integer, comment='操作人uid', index=True)
    operate_type = db.Column(db.String(16), comment='操作类型', index=True)

    rid = db.Column(db.Integer, comment='角色id', index=True)
    resource_type_id = db.Column(db.Integer, comment='资源类型id', index=True)
    resource_ids = db.Column(db.JSON, default=[], comment='资源')
    group_ids = db.Column(db.JSON, default=[], comment='资源组')
    permission_ids = db.Column(db.JSON, default=[], comment='权限')
    source = db.Column(db.String(16), comment='来源')


class AuditTriggerLog(Model):
    __tablename__ = "acl_audit_trigger_logs"

    app_id = db.Column(db.Integer, index=True)

    trigger_id = db.Column(db.Integer, comment='trigger', index=True)
    operate_uid = db.Column(db.Integer, comment='操作人uid', index=True)
    operate_type = db.Column(db.String(16), comment='操作类型', index=True)

    origin = db.Column(db.JSON, default=dict(), comment='原始数据')
    current = db.Column(db.JSON, default=dict(), comment='当前数据')
    extra = db.Column(db.JSON, default=dict(), comment='权限名')
    source = db.Column(db.String(16), default='', comment='来源')