package com.xy.utils;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xy.collections.list.JArrayList;
import com.xy.collections.list.JList;
import com.xy.config.AuthorizeConfig;
import com.xy.consts.CommConsts;
import com.xy.entity.SysMenuJoinSysRoleMenu;
import com.xy.enums.SaTokenEnum;
import com.xy.error.CommRuntimeException;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 权限工具类
 */
@Component
@RequiredArgsConstructor
public class AuthorizeUtils {


    private static AuthorizeUtils authorizeUtils;


    protected static AuthorizeConfig getAuthorizeConfig() {
        AuthorizeConfig authorizeConfig = SpringBeanUtils.getBean(AuthorizeConfig.class);
        if (authorizeConfig.getIsNative() == null) {
            authorizeConfig.setIsNative(true);
        }
        return authorizeConfig;
    }

    protected static RedisService<List<CacheEntity>> getRedisService() {
        return SpringBeanUtils.getBean(RedisService.class);
    }

    protected static String getKey(Long loginId) {
        return String.format("%s:%d", "menu", loginId);
    }

    private static Object send(String interfaceUri, String token, JSONObject jsonObject) {
        HttpRequest httpRequest = HttpRequest.post(getAuthorizeConfig().getUrl() + interfaceUri)
                .header(CommConsts.TOKEN_NAME, token)
                .timeout(5000);
        if (jsonObject != null) {
            httpRequest.body(jsonObject.toString());
        }
        String result = httpRequest.execute().body();
        R r = JSONUtil.parseObj(result).toBean(R.class);
        if (r.getCode() != 200) {
            throw new CommRuntimeException(r.getMsg());
        }
        return r.getData();
    }

    /**
     * 获取token
     *
     * @return
     */
    public static String getToken() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return null;
        }
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader(CommConsts.TOKEN_NAME);
        return token;
    }

    /**
     * 获取系统ID
     *
     * @return
     */
    public static Long getSysId() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String sysId = request.getHeader(CommConsts.SYS_ID);
        if (StringUtils.isEmpty(sysId)) {
            throw new CommRuntimeException(SaTokenEnum.NO_AUTHORITY.getKey(), SaTokenEnum.NO_AUTHORITY.getName());
        }
        return Long.valueOf(sysId);
    }

    /**
     * 获取请求uri
     *
     * @return
     */
    public static String getUri() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getRequestURI();
    }

    /**
     * 获取登录id
     *
     * @param token  token
     * @param tClass id泛型
     * @param <T>
     * @return
     */
    public static <T> T getLoginId(String token, Class<T> tClass) {
        if (StrUtil.isEmpty(token)) {
            return null;
        }
        Object id;
        //远程调用
        AuthorizeConfig authorizeConfig = getAuthorizeConfig();
        if (!authorizeConfig.getIsNative()) {
            Object obj = send("/authorize/getLoginId", token, null);
            if (obj == null) {
                return null;
            }
            id = obj;
        } else {
            id = StpUtil.getLoginIdByToken(token);
        }
        String idStr = id.toString();
        return tClass.equals(Long.class) ? (T) Long.valueOf(idStr)
                : tClass.equals(Integer.class) ? (T) Integer.valueOf(idStr)
                : tClass.equals(String.class) ? (T) idStr
                : (T) id;
    }

    /**
     * 获取登录id
     *
     * @param tClass
     * @param <T>
     * @return
     */
    public static <T> T getLoginId(Class<T> tClass) {
        return getLoginId(getToken(), tClass);
    }

    /**
     * token续签
     */
    public static void renewTimeout(String token) {
        AuthorizeConfig authorizeConfig = getAuthorizeConfig();
        if (!authorizeConfig.getIsNative()) {
            Object obj = send("/authorize/renewTimeout", token, null);
            if (obj == null) {
                return;
            }
        }
        StpUtil.renewTimeout(token, CommConsts.TOKEN_TIMEOUT);
    }

    /**
     * token续签
     */
    public static void renewTimeout() {
        renewTimeout(getToken());
    }

    /**
     * 获取客户端类型
     *
     * @return
     */
    public static String getClientType(String token) {
        AuthorizeConfig authorizeConfig = getAuthorizeConfig();
        if (!authorizeConfig.getIsNative()) {
            Object obj = send("/authorize/getClientType", token, null);
            if (obj == null) {
                return null;
            }
            return obj.toString();
        }
        return StpUtil.getLoginDevice();
    }

    /**
     * 获取客户端类型
     *
     * @return
     */
    public static String getClientType() {
        return getClientType(getToken());
    }

    /**
     * 获取所有系统ID
     *
     * @param token token
     * @return
     */
    public static List<Long> getSystemIds(String token) {
        {
            //远程调用
            AuthorizeConfig authorizeConfig = getAuthorizeConfig();
            if (!authorizeConfig.getIsNative()) {
                Object obj = send("/authorize/getSystemIds", token, null);
                if (obj == null) {
                    return new ArrayList<>();
                }
                return JSONUtil.parseArray(obj).toList(Long.class);
            }
        }
        Map<String, List<CacheEntity>> map = getRedisService().getMap(getKey(getLoginId(token, Long.class)));
        if (!Emptys.check(map)) {
            return new ArrayList<>();
        }
        List<Long> list = new ArrayList<>(map.size());
        map.forEach((s, cacheEntities) -> list.add(Long.parseLong(s)));
        return list;
    }

    /**
     * 获取所有系统ID
     *
     * @return
     */
    public static List<Long> getSystemIds() {
        return getSystemIds(getToken());
    }

    /**
     * 获取所有角色ID
     *
     * @param token    token
     * @param systemId 系统id
     * @return
     */
    public static List<Long> getRoleIds(String token, Long systemId) {
        {
            //远程调用
            AuthorizeConfig authorizeConfig = getAuthorizeConfig();
            if (!authorizeConfig.getIsNative()) {
                JSONObject jsonObject = new JSONObject().set("systemId", systemId);
                Object obj = send("/authorize/getRoleIds", token, jsonObject);
                if (obj == null) {
                    return new ArrayList<>();
                }
                return JSONUtil.parseArray(obj).toList(Long.class);
            }
        }
        List<AuthorizeUtils.CacheEntity> cacheEntities = getRedisService().getMap(getKey(getLoginId(token, Long.class)), systemId.toString());
        if (!Emptys.check(cacheEntities)) {
            return new ArrayList<>();
        }
        List<Long> list = new ArrayList<>(cacheEntities.size());
        cacheEntities.forEach(cacheEntity -> list.add(cacheEntity.getRoleId()));
        return list;
    }

    /**
     * 获取所有角色ID
     *
     * @return
     */
    public static List<Long> getRoleIds(Long systemId) {
        return getRoleIds(getToken(), systemId);
    }

    /**
     * 获取所有角色code
     *
     * @param token    token
     * @param systemId 系统id
     * @return
     */
    public static List<String> getRoleCodes(String token, Long systemId) {
        {
            //远程调用
            AuthorizeConfig authorizeConfig = getAuthorizeConfig();
            if (!authorizeConfig.getIsNative()) {
                JSONObject jsonObject = new JSONObject().set("systemId", systemId);
                Object obj = send("/authorize/getRoleCodes", token, jsonObject);
                if (obj == null) {
                    return new ArrayList<>();
                }
                return JSONUtil.parseArray(obj).toList(String.class);
            }
        }
        List<AuthorizeUtils.CacheEntity> cacheEntities = getRedisService().getMap(getKey(getLoginId(token, Long.class)), systemId.toString());
        if (!Emptys.check(cacheEntities)) {
            return new ArrayList<>();
        }
        List<String> list = new ArrayList<>(cacheEntities.size());
        cacheEntities.forEach(cacheEntity -> list.add(cacheEntity.getRoleCode()));
        return list;
    }

    /**
     * 获取所有角色code
     *
     * @return
     */
    public static List<String> getRoleCodes(Long systemId) {
        return getRoleCodes(getToken(), systemId);
    }

    /**
     * 是否是管理员
     *
     * @param token    token
     * @param systemId 系统id
     * @return
     */
    public static Boolean isAdmin(String token, Long systemId) {
        {
            //远程调用
            AuthorizeConfig authorizeConfig = getAuthorizeConfig();
            if (!authorizeConfig.getIsNative()) {
                JSONObject jsonObject = new JSONObject().set("systemId", systemId);
                Object obj = send("/authorize/isAdmin", token, jsonObject);
                if (obj == null) {
                    return false;
                }
                return Boolean.valueOf(obj.toString());
            }
        }
        List<String> roleCodes = getRoleCodes(token, systemId);
        if (roleCodes.contains(CommConsts.ADMIN_ROLE_CODE)) {
            return true;
        }
        return false;
    }

    /**
     * 是否是管理员
     *
     * @return
     */
    public static Boolean isAdmin(Long systemId) {
        return isAdmin(getToken(), systemId);
    }

    /**
     * 获取所有菜单接口uri
     *
     * @param token    token
     * @param systemId 系统id
     * @return
     */
    public static List<String> getMenuInterfaceUri(String token, Long systemId) {
        {
            //远程调用
            AuthorizeConfig authorizeConfig = getAuthorizeConfig();
            if (!authorizeConfig.getIsNative()) {
                JSONObject jsonObject = new JSONObject().set("systemId", systemId);
                Object obj = send("/authorize/getMenuInterfaceUri", token, jsonObject);
                if (obj == null) {
                    return new ArrayList<>();
                }
                return JSONUtil.parseArray(obj).toList(String.class);
            }
        }
        JList<String> list = new JArrayList<>();
        //获取redis缓存
        List<AuthorizeUtils.CacheEntity> cacheEntities = getRedisService().getMap(getKey(getLoginId(token, Long.class)), systemId.toString());
        if (!Emptys.check(cacheEntities)) {
            return list;
        }
        //遍历角色
        for (AuthorizeUtils.CacheEntity cacheEntity : cacheEntities) {
            List<SysMenuJoinSysRoleMenu> sysMenuJoinSysRoleMenus = cacheEntity.getList();
            if (!Emptys.check(sysMenuJoinSysRoleMenus)) {
                continue;
            }
            JList<String> property = new JArrayList<>(sysMenuJoinSysRoleMenus).filter()
                    .isNotNull(SysMenuJoinSysRoleMenu::getInterfaceUri).list()
                    .getProperty(SysMenuJoinSysRoleMenu::getInterfaceUri);
            property.forEach(s -> list.addAll(SetUtils.array(s.split(",")).toList()));
        }
        return list.comparing();
    }

    /**
     * 获取所有菜单接口uri
     *
     * @return
     */
    public static List<String> getMenuInterfaceUri(Long systemId) {
        return getMenuInterfaceUri(getToken(), systemId);
    }

    /**
     * 登录鉴权
     *
     * @param token token
     * @return
     */
    public static boolean authByLogin(String token) {
        {
            //远程调用
            AuthorizeConfig authorizeConfig = getAuthorizeConfig();
            if (!authorizeConfig.getIsNative()) {
                Object obj = send("/authorize/authByLogin", token, null);
                if (obj == null) {
                    return false;
                }
                return Boolean.valueOf(obj.toString());
            }
        }
        if (!Emptys.check(token)) {
            return false;
        }
        Object loginIdByToken = StpUtil.getLoginIdByToken(token);
        if (loginIdByToken == null) {
            return false;
        }
        return true;
    }

    /**
     * 登录鉴权
     *
     * @return
     */
    public static boolean authByLogin() {
        return authByLogin(getToken());
    }

    /**
     * 接口鉴权
     *
     * @param token    token
     * @param systemId 系统id
     * @param uri      接口uri
     * @return
     */
    public static boolean authByInterface(String token, Long systemId, String uri) {
        {
            //远程调用
            AuthorizeConfig authorizeConfig = getAuthorizeConfig();
            if (!authorizeConfig.getIsNative()) {
                JSONObject jsonObject = new JSONObject().set("systemId", systemId).set("uri", uri);
                Object obj = send("/authorize/authByInterface", token, jsonObject);
                if (obj == null) {
                    return false;
                }
                return Boolean.valueOf(obj.toString());
            }
        }
        List<String> menuInterfaceUri = getMenuInterfaceUri(token, systemId);
        if (!menuInterfaceUri.contains(uri)) {
            return false;
        }
        return true;
    }

    /**
     * 接口鉴权
     *
     * @param uri
     * @return
     */
    public static boolean authByInterface(Long systemId, String uri) {
        return authByInterface(getToken(), systemId, uri);
    }

    /**
     * 数据鉴权
     *
     * @param token    token
     * @param systemId 系统id
     * @param uri      接口uri
     * @return
     */
    public static boolean authByData(String token, Long systemId, String uri) {
        {
            //远程调用
            AuthorizeConfig authorizeConfig = getAuthorizeConfig();
            if (!authorizeConfig.getIsNative()) {
                JSONObject jsonObject = new JSONObject().set("systemId", systemId).set("uri", uri);
                Object obj = send("/authorize/authByData", token, jsonObject);
                if (obj == null) {
                    return false;
                }
                return Boolean.valueOf(obj.toString());
            }
        }
        //获取redis缓存
        List<AuthorizeUtils.CacheEntity> cacheEntities = getRedisService().getMap(getKey(getLoginId(token, Long.class)), systemId.toString());
        if (!Emptys.check(cacheEntities)) {
            return false;
        }
        //遍历角色
        for (AuthorizeUtils.CacheEntity cacheEntity : cacheEntities) {
            //根据 接口uri和管理员数据查询权限=true 查找角色菜单，找到则有管理员数据查询权限，反之普通查询权限
            SysMenuJoinSysRoleMenu sysMenuJoinSysRoleMenu = new JArrayList<>(cacheEntity.getList()).filter()
                    .like(SysMenuJoinSysRoleMenu::getInterfaceUri, uri)
                    .eq(SysMenuJoinSysRoleMenu::getSelectAdmin, true)
                    .object();
            if (sysMenuJoinSysRoleMenu != null) {
                return true;
            }
        }
        return false;
    }

    /**
     * 数据鉴权
     *
     * @param uri
     * @return
     */
    public static boolean authByData(Long systemId, String uri) {
        return authByData(getToken(), systemId, uri);
    }

    /**
     * 数据鉴权
     *
     * @return
     */
    public static boolean authByData(Long systemId) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return authByData(systemId, request.getRequestURI());
    }

    /**
     * 保存扩展值
     *
     * @param token
     * @param expand
     */
    public static void setExpand(String token, String expand) {
        RedisService<String> redisService = SpringBeanUtils.getBean(RedisService.class);
        redisService.set("satoken:expand:" + token, expand, CommConsts.TOKEN_TIMEOUT);
    }

    /**
     * 获取扩展值
     *
     * @return
     */
    public static String getExpand(String token) {
        RedisService<String> redisService = SpringBeanUtils.getBean(RedisService.class);
        return redisService.get("satoken:expand:" + token);
    }

    /**
     * 扩展值续签
     */
    public static void renewTimeoutExpand(String token) {
        String expand = getExpand(token);
        if (StringUtils.isEmpty(expand)) {
            return;
        }
        RedisService<String> redisService = SpringBeanUtils.getBean(RedisService.class);
        redisService.set("satoken:expand:" + token, expand, CommConsts.TOKEN_TIMEOUT);
    }

    @Data
    @Accessors(chain = true)
    public static class CacheEntity implements Serializable {

        /**
         * 角色id
         */
        private Long roleId;

        /**
         * 角色标识
         */
        private String roleCode;

        /**
         * 菜单集合
         */
        private List<SysMenuJoinSysRoleMenu> list;

    }
}
