package com.sdtool.business.service;

import com.sdtool.business.dao.SystemDao;
import com.sdtool.common.api.*;
import com.sdtool.common.base.BaseService;
import com.sdtool.common.datas.ESKeys;
import com.sdtool.common.datas.RedisKeys;
import com.sdtool.common.entity.site.ERPTokenUser;
import com.sdtool.common.entity.system.*;
import com.sdtool.common.tool.ERPUtils;
import com.dySweetFishPlugin.elasticsearch.ESClient;
import com.dySweetFishPlugin.redis.RedisService;
import com.dySweetFishPlugin.sql.dao.TunaService;
import com.dySweetFishPlugin.tool.lang.ThreadFactoryWithNamePrefix;
import com.sweetfish.convert.json.JsonConvert;
import com.sweetfish.service.Local;
import com.sweetfish.service.RetResult;
import com.sweetfish.source.PageFlipper;
import com.sweetfish.util.AnyValue;
import com.sweetfish.util.AutoLoad;
import com.sweetfish.util.ResourceType;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.DeleteByQueryAction;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.valuecount.ValueCount;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.rex.RMap;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@AutoLoad(false)
@Local
@ResourceType(PermissionService.class)
public class PermissionServiceImpl extends BaseService implements PermissionService {

    @Resource
    RedisService redisService;

    @Resource
    NoSqlKeysService keysService;

    @Resource
    JsonConvert jsonConvert;

    @Resource
    ESClient esClient;

    @Resource
    TunaService tunaService;

    @Resource
    RoleService roleService;

    @Resource
    LoginUserService userService;

    private SystemDao systemDao;

    private ExecutorService roleCacheExecutor;

    @Override
    public void init(AnyValue config) {
        super.init(config);
        roleCacheExecutor = Executors.newFixedThreadPool(2, new ThreadFactoryWithNamePrefix("[角色缓存线程池]"));
    }

    @Override
    public void destroy(AnyValue config) {
        super.destroy(config);
        if (roleCacheExecutor != null) {
            roleCacheExecutor.shutdown();
        }
    }

    @Override
    public void start(AnyValue config) {
        systemDao = tunaService.generate(SystemDao.class);
    }

    //region 菜单和权限管理

    /**
     * 在redis中构造角色、角色菜单、角色功能等缓存
     * 系统启动的时候做一次，然后修改菜单的时候需要手工进行处理，否则菜单权限不正确
     */
    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public int buildRoleCache(boolean sync, String dataSourceId, long supplierCode) {
        if (redisService.setRedisSemaphore(keysService.getRedisKey(RedisKeys.KEY_SYSLOCK, supplierCode, true) + "rolebuiildcache",
                "1", 20)) {

            CompletableFuture<Integer> exec = CompletableFuture.supplyAsync(() -> {

                RMap params = new RMap();
                params.put("status", -1);

                List<Role> roleList = systemDao.queryAllRoles(params, dataSourceId, supplierCode);
                //直接写入到redis
                Map<String, String> roleRedisHashMap = roleList.stream().collect(
                        Collectors.toMap(x -> String.valueOf(x.getId()), x -> jsonConvert.convertTo(x))
                );

                redisService.del(keysService.getRedisKey(RedisKeys.KEY_SYSROLES, supplierCode));
                if (!roleRedisHashMap.isEmpty()) {
                    redisService.hmset(keysService.getRedisKey(RedisKeys.KEY_SYSROLES, supplierCode), roleRedisHashMap);
                }

                roleList.forEach((x) -> {
                    RMap paramRole = new RMap();
                    paramRole.put("roleId", x.getId());

                    List<RoleMenu> roleResourcesList = systemDao.queryRoleMenus(paramRole, dataSourceId, supplierCode);
                    List<RoleMenu> roleFunctionsList = systemDao.queryRoleActions(paramRole, dataSourceId, supplierCode);

                    writeRedisRoleMenu(x.getId(), roleResourcesList, roleFunctionsList, supplierCode);
                });

                return 1;
            }, roleCacheExecutor).whenComplete((v, e) -> {
                if (e != null) {
                    logger.error(e.getMessage(), e);
                }
            });

            if (sync) {
                try {
                    return exec.get(20, TimeUnit.SECONDS);
                } catch (InterruptedException | ExecutionException | TimeoutException e) {
                    logger.error(e.getMessage(), e);
                    return 0;
                }
            } else {
                return 1;
            }
        } else {
            return -1;
        }
    }


    public void writeRedisRoleMenu(long roleId,
                                   List<RoleMenu> roleMenuList,
                                   List<RoleMenu> roleActionList,
                                   long supplierCode) {
        //查找所有角色对应的菜单，只有目录和菜单，并按角色写入需要的键值
        //用于首页用户的权限导航菜单
        Map<String, String> roleResourcesRedisHashMap = roleMenuList.stream().collect(
                Collectors.toMap(y -> String.valueOf(y.getMenuId()), y -> jsonConvert.convertTo(y))
        );
        redisService.del(keysService.getRedisKey(RedisKeys.KEY_SYSROLEMENUS, supplierCode, true) + roleId);
        if (!roleResourcesRedisHashMap.isEmpty()) {
            redisService.hmset(keysService.getRedisKey(RedisKeys.KEY_SYSROLEMENUS, supplierCode, true) + roleId, roleResourcesRedisHashMap);
        }

        //查找角色对应的全部功能，包括目录、菜单、按钮，并按角色写入需要的键值
        //用于判断用户权限是否存在
        Map<String, String> roleFunctionsRedisHashMap = roleActionList.stream().collect(
                Collectors.toMap(y -> String.valueOf(y.getKey()), y -> jsonConvert.convertTo(y))
        );
        redisService.del(keysService.getRedisKey(RedisKeys.KEY_SYSROLEACTIONS, supplierCode, true) + roleId);
        if (!roleFunctionsRedisHashMap.isEmpty()) {
            redisService.hmset(keysService.getRedisKey(RedisKeys.KEY_SYSROLEACTIONS, supplierCode, true) + roleId, roleFunctionsRedisHashMap);
        }
    }

    /**
     * 重建角色菜单缓存
     *
     * @param currentUser 操作人
     * @return 操作信息
     */
    @Override
    public RetResult<Menu> reindexRoleMenuCache(ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        int iResult = buildRoleCache(false, dataSourceId, supplierCode);

        if (iResult == -1) {
            String redisKey = keysService.getRedisKey(RedisKeys.KEY_SYSLOCK, supplierCode, true) + "rolebuiildcache";
            long ttlTime = redisService.ttl(redisKey);
            return RetResult.<Menu>errorT().retinfo("角色菜单缓存正在处理，请稍后" + ttlTime + "秒后操作");
        }

        return RetResult.successT();
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public RetResult<List<Role>> queryMenuRoles(long idMenu, PageFlipper pageFlipper, String dataSourceId, long supplierCode) {
        RMap params = new RMap();
        params.put("menuId", idMenu);
        List<Role> roleList = systemDao.queryMenuRoles(params, pageFlipper, dataSourceId, supplierCode);

        return RetResult.<List<Role>>successT().retcode(0).result(roleList).page(pageFlipper);
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public RetResult<List<LoginUser>> queryRoleUsers(long idRole, PageFlipper pageFlipper, String dataSourceId, long supplierCode) {
        RMap params = new RMap();
        params.put("roleId", idRole);
        List<LoginUser> userList = systemDao.queryRoleUsers(params, pageFlipper, dataSourceId, supplierCode);

        return RetResult.<List<LoginUser>>successT().retcode(0).result(userList).page(pageFlipper);
    }

    //endregion

    /**
     * 检查用户权限
     *
     * @param userInfo     用户信息
     * @param moduleId     模块
     * @param actionId     操作
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 是否有权限
     */
    @Override
    public Boolean checkUserAuth(ERPTokenUser userInfo, long moduleId, long actionId, String dataSourceId, long supplierCode) {
        List<UserRole> userRoleList = roleService.getUserRoleList(userInfo.getId(), dataSourceId, supplierCode);
        if (userRoleList == null) {
            return false;
        }

        if (userRoleList.isEmpty()) {
            return false;
        }

        if (actionId > 0) {
            //如果功能id大于0，则需要检查菜单和功能
            for (UserRole userRole : userRoleList) {
                if (redisService.hexists(keysService.getRedisKey(RedisKeys.KEY_SYSROLEACTIONS, supplierCode, true) + userRole.getRoleId(), moduleId + "-" + actionId)) {
                    return true;
                }
            }

        } else {
            //如果检查的功能id小于等于0，则只检查是否有菜单即可

            for (UserRole userRole : userRoleList) {
                if (redisService.hexists(keysService.getRedisKey(RedisKeys.KEY_SYSROLEMENUS, supplierCode, true) + userRole.getRoleId(), String.valueOf(moduleId))) {
                    return true;
                }
            }

        }
        return false;
    }

    /**
     * 判断用户角色是否有某个字符串描述的权限
     *
     * @param user         用户
     * @param permission   权限描述，单个权限
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 是否有权限
     */
    @Override
    public int ifPermission(ERPTokenUser user, String permission, List<UserRole> srcRoleList, String dataSourceId, long supplierCode) {

        List<UserRole> userRoleList;
        if (srcRoleList == null) {
            userRoleList = roleService.getUserRoleList(user.getId(), dataSourceId, supplierCode);
        } else {
            userRoleList = srcRoleList;
        }

        if (userRoleList == null) {
            return 0;
        }

        if (userRoleList.isEmpty()) {
            return 0;
        }

        if (StringUtils.isEmpty(permission)) {
            return 0;
        }

        for (UserRole userRole : userRoleList) {
            Map<String, String> menuMap = redisService.hgetAll(keysService.getRedisKey(RedisKeys.KEY_SYSROLEACTIONS, supplierCode, true) + userRole.getRoleId());

            for (String v : menuMap.values()) {
                RoleMenu roleMenu = jsonConvert.convertFrom(RoleMenu.class, v);
                if (permission.equalsIgnoreCase(roleMenu.getMenuPerms())) {
                    return 1;
                }
            }
        }

        return 0;
    }

    /**
     * 判断用户角色是否有某个字符串描述的权限
     *
     * @param user         用户
     * @param permissions  权限描述，多个使用|分割
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 是否有权限
     */
    @Override
    public String ifPermissionList(ERPTokenUser user, String permissions, String dataSourceId, long supplierCode) {
        List<UserRole> userRoleList = roleService.getUserRoleList(user.getId(), dataSourceId, supplierCode);
        if (userRoleList == null) {
            return "";
        }

        if (userRoleList.isEmpty()) {
            return "";
        }

        if (StringUtils.isEmpty(permissions)) {
            return "";
        }

        String[] permissionList = permissions.split("\\|");
        String result = "";
        for (String per : permissionList) {
            int perms = ifPermission(user, per, userRoleList, dataSourceId, supplierCode);
            if (perms == 0) {
                if (StringUtils.isEmpty(result)) {
                    result = "0";
                } else {
                    result = result + "|0";
                }
            } else {
                if (StringUtils.isEmpty(result)) {
                    result = "1";
                } else {
                    result = result + "|1";
                }
            }
        }

        return result;
    }

    //region 系统数据权限

    /**
     * 获取数据权限管理人列表
     *
     * @param searchData   查询条件
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 管理人列表
     */
    @SuppressWarnings({"rawtypes"})
    public RetResult<List<LoginUser>> getDataPurviewControlerList(RMap searchData, PageFlipper pageFlipper, String dataSourceId, long supplierCode) {
        if (searchData == null) {
            return RetResult.<List<LoginUser>>errorT().retinfo("未传入有效的权限数据类型");
        }

        RetResult<List<LoginUser>> userResult = userService.getLoginUserList(searchData, pageFlipper, dataSourceId, supplierCode);
        if (!userResult.isSuccess()) {
            return RetResult.<List<LoginUser>>errorT().retinfo("未传入有效的权限数据类型");
        }

        List<LoginUser> userList = userResult.getResult();
        if (userList.isEmpty()) {
            return RetResult.<List<LoginUser>>successT().result(userList).page(userResult.getPageFlipper());
        }

        long[] userIds = userList.stream().mapToLong(LoginUser::getId).toArray();

        BoolQueryBuilder qb = QueryBuilders.boolQuery()
                .must(QueryBuilders.typeQuery(ESKeys.ESERPDEFAULT_TYPE))
                .must(QueryBuilders.termsQuery("idUser", userIds));

        if (ERPUtils.mapContainKey(searchData, "idData")) {
            qb = qb.must(QueryBuilders.termQuery("idData", searchData.getLong("idData")));
        }

        TermsAggregationBuilder childAggregation = AggregationBuilders.terms("info_idUser").field("idUser").size(userIds.length);
        childAggregation.subAggregation(AggregationBuilders.count("sub_count").field("idCreateUser"));

        SearchResponse childRes = esClient.getClient().prepareSearch(keysService.getESKey(ESKeys.ESERPDATAPURVIEW_INDEX, supplierCode))
                .setQuery(qb)
                .addAggregation(childAggregation)
                .setSize(pageFlipper.getPageSize())
                .execute()
                .actionGet();

        Terms genders = childRes.getAggregations().get("info_idUser");
        for (Terms.Bucket bucket : genders.getBuckets()) {
            long idUser = Long.parseLong(bucket.getKeyAsString());
            ValueCount valueCount = bucket.getAggregations().get("sub_count");
            userList.stream().filter((x) -> (x.getId() == idUser)).findFirst().ifPresent(loginUser -> loginUser.setWorkInCount(Long.valueOf(valueCount.getValue()).intValue()));
        }

        return RetResult.<List<LoginUser>>successT().result(userList).page(userResult.getPageFlipper());
    }

    /**
     * 获取数据权限列表
     * 前端需要做左右布局，左边为权限标识列表，右边为数据
     * 增加时选权限、选控制人，然后选操作人列表
     *
     * @param searchData   查询条件
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 权限数据列表
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public RetResult<List<DataPurview>> getDataPurviewList(RMap searchData, String dataSourceId, long supplierCode) {
        if (searchData == null) {
            return RetResult.<List<DataPurview>>errorT().retinfo("未传入有效的权限数据类型");
        }

        RMap params = new RMap();
        params.putAll(searchData);

        List<DataPurview> dataPurviewList = searchESList(esClient, jsonConvert,
                DataPurview.class,
                params,
                ((sd) -> {
                    BoolQueryBuilder qb = QueryBuilders.boolQuery();
                    qb = qb.must(QueryBuilders.typeQuery(ESKeys.ESERPDEFAULT_TYPE));
                    if (ERPUtils.mapContainKey(params, "idData")) {
                        qb = qb.must(QueryBuilders.termQuery("idData", params.getLong("idData")));
                    }
                    if (ERPUtils.mapContainKey(params, "idUser")) {
                        qb = qb.must(QueryBuilders.termQuery("idUser", params.getLong("idUser")));
                    }
                    if (ERPUtils.mapContainKey(params, "userName")) {
                        qb = qb.must(QueryBuilders.matchQuery("userName", params.getString("userName")).operator(Operator.AND));
                    }
                    if (ERPUtils.mapContainKey(params, "createUserName")) {
                        qb = qb.must(QueryBuilders.matchQuery("createUserName", params.getString("createUserName")).operator(Operator.AND));
                    }

                    return qb;
                }),
                ((item) -> {
                    item.setUserName(userService.getRedisLoginUserName(item.getIdUser(), supplierCode));
                    item.setCreateUserName(userService.getRedisLoginUserName(item.getIdCreateUser(), supplierCode));
                    item.setCreateUserDeptName(userService.getRedisLoginUserDeptName(item.getIdCreateUser(), supplierCode));
                }),
                keysService.getESKey(ESKeys.ESERPDATAPURVIEW_INDEX, supplierCode),
                (() -> SortBuilders.fieldSort("idData").order(SortOrder.ASC)),
                (() -> SortBuilders.fieldSort("idUser").order(SortOrder.ASC)));

        return RetResult.<List<DataPurview>>successT().result(dataPurviewList);
    }

    /**
     * 增加数据权限
     *
     * @param dataPurviewList 权限列表
     * @param currentUser     操作人
     * @param dataSourceId    分库
     * @param supplierCode    分表
     * @return 操作信息
     */
    public RetResult<DataPurview> addDataPurview(List<DataPurview> dataPurviewList, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {

        if (currentUser == null) {
            return RetResult.<DataPurview>errorT().retinfo("未登录");
        }

        if (dataPurviewList == null) {
            return RetResult.<DataPurview>errorT().retinfo("未传入有效数据");
        }

        if (dataPurviewList.isEmpty()) {
            return RetResult.<DataPurview>errorT().retinfo("未传入有效数据");
        }

        dataPurviewList.forEach((x) -> {
            x.setUserName(userService.getRedisLoginUserName(x.getIdUser(), supplierCode));
            x.setCreateUserName(userService.getRedisLoginUserName(x.getIdCreateUser(), supplierCode));
        });

        BulkRequestBuilder bulkRequest = esClient.getClient().prepareBulk().setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);

        Map<String, DataPurview> purviewMap = new HashMap<>();

        dataPurviewList.forEach(x -> {
            x.setEsId(x.getIdData() + "_" + x.getIdUser() + "_" + x.getIdCreateUser());
            IndexRequestBuilder dataRequest = esClient.getClient()
                    .prepareIndex(keysService.getESKey(ESKeys.ESERPDATAPURVIEW_INDEX, supplierCode), ESKeys.ESERPDEFAULT_TYPE)
                    .setId(x.getEsId())
                    .setSource(jsonConvert.convertTo(x), XContentType.JSON);
            bulkRequest.add(dataRequest);
            purviewMap.put(x.getEsId(), x);
        });

        //写入到es索引中
        BulkResponse bulkResponse = bulkRequest.execute().actionGet();
        if (bulkResponse.hasFailures()) {
            logger.error("增加数据权限索引出错：" + bulkResponse.buildFailureMessage());
        } else {
            List<DataPurview> insertPurviewList = new ArrayList<>();

            for (BulkItemResponse bulkItemResponse : bulkResponse) {
                DocWriteResponse itemResponse = bulkItemResponse.getResponse();
                if (itemResponse.getResult() == DocWriteResponse.Result.CREATED) {
                    insertPurviewList.add(purviewMap.get(bulkItemResponse.getId()));
                }
            }
            if (!insertPurviewList.isEmpty()) {
                systemDao.addDataPurview(insertPurviewList, dataSourceId, supplierCode);
            }
            logger.info("增加数据权限索引成功[" + dataPurviewList.size() + "]");
        }

        return RetResult.successT();
    }

    /**
     * 删除数据权限
     *
     * @param dataPurviewList 数据权限
     * @param currentUser     操作人
     * @param dataSourceId    分库
     * @param supplierCode    分表
     * @return 操作信息
     */
    public RetResult<DataPurview> deleteDataPurview(List<DataPurview> dataPurviewList, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (currentUser == null) {
            return RetResult.<DataPurview>errorT().retinfo("未登录");
        }

        if (dataPurviewList == null) {
            return RetResult.<DataPurview>errorT().retinfo("未传入有效数据");
        }

        if (dataPurviewList.isEmpty()) {
            return RetResult.<DataPurview>errorT().retinfo("未传入有效数据");
        }

        String[] dataIndexes = dataPurviewList.stream().map((x) -> x.getIdData() + "_" + x.getIdUser() + "_" + x.getIdCreateUser()).toArray(String[]::new);

        BoolQueryBuilder qbPurview = QueryBuilders.boolQuery()
                .must(QueryBuilders.typeQuery(ESKeys.ESERPDEFAULT_TYPE))
                .must(QueryBuilders.idsQuery().addIds(dataIndexes));
        DeleteByQueryAction.INSTANCE.newRequestBuilder(esClient.getClient())
                .refresh(true)
                .source(keysService.getESKey(ESKeys.ESERPDATAPURVIEW_INDEX, supplierCode))
                .filter(qbPurview)
                .get();

        systemDao.deleteDataPurview(dataPurviewList, dataSourceId, supplierCode);

        return RetResult.successT();
    }

    /**
     * 检查数据权限，提供其他业务使用
     *
     * @param idData       权限类型
     * @param idSrcUser    控制人
     * @param idCreateUser 制单人
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 权限检查
     */
    public boolean checkDataPurview(long idData, long idSrcUser, long idCreateUser, String dataSourceId, long supplierCode) {
        //从es中获取
        BoolQueryBuilder qb = QueryBuilders.boolQuery()
                .must(QueryBuilders.typeQuery(ESKeys.ESERPDEFAULT_TYPE))
                .must(QueryBuilders.idsQuery().addIds(idData + "_" + idSrcUser + "_" + idCreateUser));


        SearchResponse scrollResp = esClient.getClient().prepareSearch(keysService.getESKey(ESKeys.ESERPDATAPURVIEW_INDEX, supplierCode))
                .setQuery(qb)
                .setSize(1)
                .execute()
                .actionGet();
        return scrollResp.getHits().getTotalHits() != 0;
    }

    public List<Long> getPersonDataPurviewList(long idData, long idUser, String dataSourceId, long supplierCode) {
        List<DataPurview> dataPurviewList = searchESList(esClient, jsonConvert,
                DataPurview.class,
                null,
                ((sd) -> {
                    BoolQueryBuilder qb = QueryBuilders.boolQuery();
                    qb = qb.must(QueryBuilders.typeQuery(ESKeys.ESERPDEFAULT_TYPE))
                            .must(QueryBuilders.termQuery("idData", idData))
                            .must(QueryBuilders.termQuery("idUser", idUser));

                    return qb;
                }),
                keysService.getESKey(ESKeys.ESERPDATAPURVIEW_INDEX, supplierCode),
                (() -> SortBuilders.fieldSort("idCreateUser").order(SortOrder.ASC)));

        return dataPurviewList.stream().map(DataPurview::getIdCreateUser).collect(Collectors.toList());
    }

    //endregion
}
