/*!
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.

rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/

package com.rebuild.core.privileges;

import cn.devezhao.bizz.privileges.DepthEntry;
import cn.devezhao.bizz.privileges.Permission;
import cn.devezhao.bizz.privileges.Privileges;
import cn.devezhao.bizz.privileges.impl.BizzDepthEntry;
import cn.devezhao.bizz.privileges.impl.BizzPermission;
import cn.devezhao.bizz.security.QueryFilter;
import cn.devezhao.bizz.security.member.BusinessUnit;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Filter;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.bizz.CustomEntityPrivileges;
import com.rebuild.core.privileges.bizz.Department;
import com.rebuild.core.privileges.bizz.User;
import com.rebuild.core.service.query.AdvFilterParser;
import com.rebuild.utils.JSONUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.util.HashSet;
import java.util.Set;

/**
 * 基于角色权限的查询过滤器
 *
 * @author Zhao Fangfang
 * @since 1.0, 2019-6-21
 */
@Slf4j
public class RoleBaseQueryFilter implements Filter, QueryFilter {
    private static final long serialVersionUID = -7388577069739389698L;

    /**
     * 总是拒绝
     */
    public static final Filter DENIED = new RoleBaseQueryFilter() {
        private static final long serialVersionUID = -1841438304452108874L;
        @Override
        public String evaluate(Entity entity) {
            return "( 1 = 0 )";
        }
    };

    /**
     * 总是允许
     */
    public static final Filter ALLOWED = new RoleBaseQueryFilter() {
        private static final long serialVersionUID = -1300184338130890817L;
        @Override
        public String evaluate(Entity entity) {
            return "( 1 = 1 )";
        }
    };

    private RoleBaseQueryFilter() {
        this.user = null;
        this.useAction = null;
    }

    // --

    final private User user;
    final private Permission useAction;

    /**
     * @param user
     * @param useAction
     */
    public RoleBaseQueryFilter(User user, Permission useAction) {
        this.user = user;
        this.useAction = useAction == null ? BizzPermission.READ : useAction;
    }

    @Override
    public String evaluate(int entity) {
        return evaluate(MetadataHelper.getEntity(entity));
    }

    @Override
    public String evaluate(final Entity entity) {
        if (user == null || !user.isActive()) {
            return DENIED.evaluate(null);
        } else if (user.isAdmin()) {
            return ALLOWED.evaluate(null);
        }

        Entity useMainEntity = null;
        if (!MetadataHelper.hasPrivilegesField(entity)) {
            // TODO BIZZ 实体全部用户可见???
            if (MetadataHelper.isBizzEntity(entity) || EasyMetaFactory.valueOf(entity).isPlainEntity()) {
                return ALLOWED.evaluate(null);
            } else if (entity.getMainEntity() != null) {
                useMainEntity = entity.getMainEntity();
            } else {
                log.warn("None privileges entity use `Application#createQueryNoFilter` please : {} \n\t{}",
                        entity, StringUtils.join(Thread.currentThread().getStackTrace(), "\n\t"));
                return DENIED.evaluate(null);
            }
        }

        // 未配置权限的默认拒绝
        // 明细实体使用主实体权限
        final Privileges ep = user.getOwningRole().getPrivileges(
                useMainEntity != null ? useMainEntity.getEntityCode() : entity.getEntityCode());
        if (ep == Privileges.NONE) {
            return DENIED.evaluate(null);
        }

        String owningFormat = "%s = '%s'";
        Field dtmField = null;
        if (useMainEntity != null) {
            dtmField = MetadataHelper.getDetailToMainField(entity);
            owningFormat = dtmField.getName() + "." + owningFormat;
        }

        final String customFilterWithAndOr = buildCustomFilter(ep, dtmField);
        final String customFilter = customFilterWithAndOr == null ? null : customFilterWithAndOr.substring(3).trim();
        final String shareFilter = buildShareFilter(entity, dtmField);
        final DepthEntry depth = ep.superlative(useAction);

        // 全部

        if (depth == BizzDepthEntry.GLOBAL) {
            if (customFilter == null) {
                return ALLOWED.evaluate(null);
            } else {
                return String.format("(%s or %s)", customFilter, shareFilter);
            }
        }

        // 本人

        if (depth == BizzDepthEntry.PRIVATE) {
            String baseFilter = String.format(owningFormat, EntityHelper.OwningUser, user.getIdentity());
            return joinFilters(baseFilter, customFilterWithAndOr, shareFilter);
        }

        // 部门

        Department dept = user.getOwningDept();
        String deptFilter = String.format(owningFormat, EntityHelper.OwningDept, dept.getIdentity());

        if (depth == BizzDepthEntry.LOCAL) {
            return joinFilters(deptFilter, customFilterWithAndOr, shareFilter);
        } else if (depth == BizzDepthEntry.DEEPDOWN) {
            Set<String> set = new HashSet<>();
            set.add(deptFilter);
            for (BusinessUnit ch : dept.getAllChildren()) {
                set.add(String.format(owningFormat, EntityHelper.OwningDept, ch.getIdentity()));
            }

            deptFilter = StringUtils.join(set, " or ");
            return joinFilters(deptFilter, customFilterWithAndOr, shareFilter);
        }

        return DENIED.evaluate(null);
    }

    private String joinFilters(String baseFilter, String customFilter, String shareFilter) {
        if (customFilter == null) {
            return String.format("((%s) or %s)", baseFilter, shareFilter);
        } else {
            return String.format("(((%s) %s) or %s)", baseFilter, customFilter, shareFilter);
        }
    }

    /**
     * 共享权限
     *
     * @param entity
     * @param dtmField
     * @return
     */
    private String buildShareFilter(Entity entity, Field dtmField) {
        if (user == null) return null;

        String shareFilter = "exists (select rights from ShareAccess where belongEntity = '%s' and shareTo = '%s' and recordId = ^%s)";

        // 使用主实体的共享（明细实体）
        if (dtmField != null) {
            shareFilter = String.format(shareFilter,
                    dtmField.getOwnEntity().getMainEntity().getName(),
                    user.getId(), dtmField.getName());
        } else {
            shareFilter = String.format(shareFilter,
                    entity.getName(), user.getId(), entity.getPrimaryField().getName());
        }
        return shareFilter;
    }

    /**
     * 自定义权限
     *
     * @param ep
     * @param dtmField
     * @return
     * @see PrivilegesManager#andOrPassCustomFilter(ID, ID, Permission, Privileges, boolean)
     */
    private String buildCustomFilter(Privileges ep, Field dtmField) {
        if (user == null || useAction == null
                || !(ep instanceof CustomEntityPrivileges)) return null;

        JSONObject hasFilter = ((CustomEntityPrivileges) ep).getCustomFilter(useAction);
        if (hasFilter == null) return null;

        // 兼容转换（明细实体）
        if (dtmField != null) {
            JSONArray items = hasFilter.getJSONArray("items");
            JSONArray itemsNew = new JSONArray();
            for (Object item : items) {
                JSONObject c = (JSONObject) JSONUtils.clone((JSON) item);
                String field = c.getString("field");
                if (field.startsWith("&")) {
                    c.put("field", String.format("&%s.%s", dtmField.getName(), field.substring(1)));
                } else {
                    c.put("field", String.format("%s.%s", dtmField.getName(), field));
                }
                itemsNew.add(c);
            }

            hasFilter = (JSONObject) JSONUtils.clone(hasFilter);
            hasFilter.put("items", itemsNew);
            hasFilter.put("entity", dtmField.getOwnEntity().getName());
        }

        AdvFilterParser advFilterParser = new AdvFilterParser(hasFilter);
        advFilterParser.setUser(user.getId());
        String filterSql = advFilterParser.toSqlWhere();
        if (filterSql == null) return null;
        // v4.1
        String andOr41 = StringUtils.defaultString(hasFilter.getString("_cpAndOr"), "AND");
        return andOr41.toLowerCase() + " " + filterSql;
    }
}
