package com.sckw.gateway.filter;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.sckw.core.model.constant.Global;
import com.sckw.core.model.enums.ClientTypeEnum;
import com.sckw.core.model.enums.SystemTypeEnum;
import com.sckw.core.utils.CollectionUtils;
import com.sckw.core.utils.EncryUtil;
import com.sckw.core.utils.NumberUtils;
import com.sckw.core.utils.StringUtils;
import com.sckw.core.web.config.CustomConfig;
import com.sckw.core.web.constant.CommonConstants;
import com.sckw.core.web.constant.HttpStatus;
import com.sckw.core.web.constant.RequestConstant;
import com.sckw.core.web.model.LoginEntInfo;
import com.sckw.core.web.model.LoginUserInfo;
import com.sckw.gateway.pojo.HttpResult;
import com.sckw.redis.utils.RedissonUtils;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

@Slf4j
@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {

    @Autowired
    CustomConfig customConfig;

    /**
     * 直接放行不需要校验Access-Token的请求
     */
    private static final List<String> EXCLUDEPATH = new ArrayList<>();

    /**
     * 直接放行不需要校验Access-Special的请求
     */
    private static final List<String> IMPORT_PASS_PATH = new ArrayList<>();

    /**
     * 需要校验token但不用接口权限校验的请求
     */
    private static final List<String> WITHOUTPATH = new ArrayList<>();

    private static final String REGISTER = "/kwsEnt/register";
    private final PathMatcher pathMatcher = new AntPathMatcher();

    /**
     * @desc: 初始化放行路径
     * @author: czh
     */
    @PostConstruct
    private void initExcludePath() {
        String links = customConfig.getLinks();
        if (StringUtils.isNotBlank(links)) {
            EXCLUDEPATH.addAll(Arrays.asList(links.split(Global.COMMA)));
        }

        String specialLinks = customConfig.getSpecialLinks();
        if (StringUtils.isNotBlank(specialLinks)) {
            IMPORT_PASS_PATH.addAll(Arrays.asList(specialLinks.split(Global.COMMA)));
        }
//        String withoutLinks = customConfig.getWithoutLinks();
//        if (StringUtils.isNotBlank(withoutLinks)) {
//            WITHOUTPATH.addAll(Arrays.asList(withoutLinks.split(Global.COMMA)));
//        }
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String token = request.getHeaders().getFirst(RequestConstant.TOKEN);
        String clientType = request.getHeaders().getFirst(RequestConstant.CLIENT_TYPE);
        String systemTypeStr = request.getHeaders().getFirst(RequestConstant.SYSTEM_TYPE);
        Integer systemType = systemTypeStr == null ? null : Integer.parseInt(systemTypeStr);
//        String accessSpecial = request.getHeader(RequestConstant.ACCESS_SPECIAL);
        String requestUri = request.getPath().value();
        boolean isExcluded = EXCLUDEPATH.stream()
                .anyMatch(pattern -> pathMatcher.match(pattern, requestUri));

        /*1、非token校验接口放行*/
        if (isExcluded) {
            if (StrUtil.isNotBlank(token)) {
                Map<String, Object> tokenMap = EncryUtil.descryV2(Global.PRI_KEY, token);
                if (tokenMap != null) {
                    Long userId = StringUtils.isNotBlank(tokenMap.get("userId")) ? NumberUtils.parseLong(tokenMap.get("userId")) : null;
                    String key = Global.getFullUserLoginKey(systemType, userId);

                    String userInfoStr = RedissonUtils.getString(key);
                    LoginUserInfo loginUserInfo = StringUtils.isNotBlank(userInfoStr) ? com.alibaba.fastjson.JSON.parseObject(userInfoStr, LoginUserInfo.class) : null;
                    if (Objects.nonNull(loginUserInfo)) {
                        loginUserInfo.setClientType(clientType);
                        String loginEntStr = RedissonUtils.getString(Global.getFullUserEntKey(loginUserInfo.getEntId()));
                        LoginEntInfo loginEntInfo = StringUtils.isNotBlank(loginEntStr) ? com.alibaba.fastjson.JSON.parseObject(loginEntStr, LoginEntInfo.class) : null;
                        ServerWebExchange build = buildNewExchange(exchange,loginUserInfo,loginEntInfo);
                        return chain.filter(build);
                    }
                }
            }
            return chain.filter(exchange);
        }

        // 添加对Swagger相关路径的放行
        if (requestUri.startsWith("/swagger-ui") ||
                requestUri.startsWith("/v3/api-docs") || requestUri.startsWith("/doc.htm")||
                requestUri.startsWith("/webjars/")||requestUri.startsWith(CommonConstants.RESOURCE_PREFIX)) {
            return chain.filter(exchange);
        }

        /*2、校验token**/
        /*2.1、校验token非空*/
        HttpResult result = checkBlank(token, clientType, systemType, requestUri);
        if (result.getCode() != HttpStatus.SUCCESS_CODE) {
            return writeResponse(response,result.getCode(),result.getMsg());
        }

        /*2.2、token解析*/
        Map<String, Object> tokenMap = EncryUtil.descryV2(Global.PRI_KEY, token);
        if (tokenMap == null) {
            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.TOKEN_INVALID_MESSAGE);
        }

        /*2.3、从redis获取用户登录token*/
        Long userId = StringUtils.isNotBlank(tokenMap.get("userId")) ? NumberUtils.parseLong(tokenMap.get("userId")) : null;
        String key = Global.getFullUserTokenKey(clientType, userId);
        String redisUserToken = RedissonUtils.getString(key);
        if (StringUtils.isBlank(redisUserToken)) {
            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.TOKEN_INVALID_MESSAGE);
        }

        /*2.4、请求token和redis中token不一致,说明账号在别处登录了*/
        if (!token.equals(redisUserToken)) {
            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.ACCOUNT_OTHER_LOGIN_MESSAGE);
        }

        /*3、校验登录用户信息*/
        key = Global.getFullUserLoginKey(systemType, userId);
        String userInfoStr = RedissonUtils.getString(key);
        LoginUserInfo loginUserInfo = StringUtils.isNotBlank(userInfoStr) ? JSON.parseObject(userInfoStr, LoginUserInfo.class) : null;
        if (Objects.isNull(loginUserInfo)) {
            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.TOKEN_INVALID_MESSAGE);
        }
        loginUserInfo.setClientType(clientType);

        //校验用户账号是否冻结
        if (loginUserInfo.getStatus() == Global.YES) {
            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,"您的账号已被冻结，请联系系统管理员！");
        }

        /*4、登录中的企业信息*/
        String loginEntStr = RedissonUtils.getString(Global.getFullUserEntKey(loginUserInfo.getEntId()));
        LoginEntInfo loginEntInfo = StringUtils.isNotBlank(loginEntStr) ? JSON.parseObject(loginEntStr, LoginEntInfo.class) : null;

        //运营端
        if (SystemTypeEnum.MANAGE.getCode().equals(systemType)) {
            //redis 获取客户经理绑定企业ID
            String managerKey = Global.getCustomerManagerUserLoginKey(SystemTypeEnum.MANAGE.getCode(), loginUserInfo.getId());
            RSet<Object> set = RedissonUtils.getSet(managerKey);
            if (Objects.nonNull(set)) {
                List authUserIdList = JSONObject.parseObject(set.toString(), List.class);
                if (CollectionUtils.isNotEmpty(authUserIdList)) {
                    loginUserInfo.setAuthEntIdList(authUserIdList);
                }
            }
            //重置缓存有效期
            RedissonUtils.putString(Global.getFullUserTokenKey(clientType, userId), token, ClientTypeEnum.expireTime(clientType));
            RedissonUtils.putString(Global.getFullUserLoginKey(systemType, loginUserInfo.getId()), JSON.toJSONString(loginUserInfo), Global.APP_TOKEN_EXPIRE);
            RedissonUtils.putString(Global.getFullUserEntKey(loginEntInfo.getId()), JSON.toJSONString(loginEntInfo), Global.APP_TOKEN_EXPIRE);
            //将用户信息和企业信息放入header方便后续微服务获取
            ServerWebExchange build = buildNewExchange(exchange,loginUserInfo,loginEntInfo);
            return chain.filter(build);
        }

        //非运营端
        if ((StringUtils.isBlank(loginEntStr) || loginEntInfo == null)) {
            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.UN_LOGIN_MESSAGE);
        } else {
            //校验用户企业是否冻结
            if (loginEntInfo.getStatus() == Global.YES) {
                return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,"您所属企业已被冻结，请联系系统管理员！");
            }

            //校验用户企业审批状态
            if (!loginEntInfo.getValid() && !REGISTER.equals(requestUri)) {
                if (loginEntInfo.getApproval() == Global.NO) {
                    return writeResponse(response,HttpStatus.CODE_60603,HttpStatus.ENTCERTIFICATES_NOT_REGISTER);
                }

                if (loginEntInfo.getApproval() == Global.NUMERICAL_THREE) {
                    return writeResponse(response,HttpStatus.CODE_60603,HttpStatus.ENTCERTIFICATES_NOT_PASS);
                }

                return writeResponse(response,HttpStatus.CODE_60603,HttpStatus.ENTCERTIFICATES_INVAILD);
            }
        }

        /*5、请求权限校验*/
        //非管理员有接口权限才放行
        if (loginUserInfo.getIsMain() == Global.NO
                && !WITHOUTPATH.contains(requestUri)
                && !checkMenu(clientType, loginUserInfo.getId(), requestUri)) {
            return writeResponse(response,HttpStatus.AUTHORITY_NO_CODE,HttpStatus.ACCESS_FIAL);
        }
        RedissonUtils.putString(Global.getFullUserLoginKey(systemType, loginUserInfo.getId()), JSON.toJSONString(loginUserInfo), Global.APP_TOKEN_EXPIRE);
        RedissonUtils.putString(Global.getFullUserEntKey(loginEntInfo.getId()), JSON.toJSONString(loginEntInfo), Global.APP_TOKEN_EXPIRE);
        RedissonUtils.putString(Global.getFullUserTokenKey(clientType, userId), token, ClientTypeEnum.expireTime(clientType));
        //将用户信息和企业信息放入header方便后续微服务获取
        ServerWebExchange build = buildNewExchange(exchange,loginUserInfo,loginEntInfo);
        return chain.filter(build);
    }

    private ServerWebExchange buildNewExchange(ServerWebExchange exchange, LoginUserInfo loginUserInfo, LoginEntInfo loginEntInfo) {
        SimplePropertyPreFilter filter = new SimplePropertyPreFilter("id","entTypes", "firmName","approval","status","special");
        String userInfoStrEncode = URLEncoder.encode(JSON.toJSONString(loginUserInfo), StandardCharsets.UTF_8);
        String entInfoStrEncode = URLEncoder.encode(JSON.toJSONString(loginEntInfo,filter), StandardCharsets.UTF_8);
        ServerHttpRequest newRequest = exchange.getRequest().mutate()
                .headers(h -> {
                    h.add(Global.USER_INFO_STR_ENCODE, userInfoStrEncode);
                    h.add(Global.ENT_INFO_STR_ENCODE, entInfoStrEncode);
                    h.remove(RequestConstant.TOKEN);
                }).build();
        return exchange.mutate().request(newRequest).build();
    }

    /**
     * 构建返回内容
     *
     * @param response ServerHttpResponse
     * @param code     返回码
     * @param msg     返回数据
     * @return Mono
     */
    protected Mono<Void> writeResponse(ServerHttpResponse response, Integer code, String msg) {
        JSONObject message = new JSONObject();
        message.put("code", code);
        message.put("msg", msg);
        byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(org.springframework.http.HttpStatus.OK);
        // 指定编码，否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }


    /**
     * @param token         token
     * @param clientType    客户端类型
     * @param systemType    系统类型
     * @param requestUri    请求地址
     * @return 校验结果
     * @desc Hearder内容校验
     * @author zk
     * @date 2023/12/14
     **/
    private HttpResult checkBlank(String token, String clientType, Integer systemType, String requestUri) {
        if (StringUtils.isBlank(token)) {
            return HttpResult.error(HttpStatus.PARAMETERS_MISSING_CODE, HttpStatus.UN_LOGIN_MESSAGE);
        }
        if (StringUtils.isBlank(clientType)) {
            return HttpResult.error(HttpStatus.PARAMETERS_MISSING_CODE, HttpStatus.INVALID_REQUEST);
        }
        if (StringUtils.isBlank(systemType)) {
            return HttpResult.error(HttpStatus.PARAMETERS_MISSING_CODE, HttpStatus.INVALID_REQUEST);
        }
        return HttpResult.ok();
    }

    /**
     * @param userId 用户菜单权限key  url 当前请求url
     * @return boolean
     * @desc: 校验url权限
     * @author: czh
     * @date: 2023/6/28
     */
    private boolean checkMenu(String clientType, Long userId, String url) {
        return true;
        //return RedissonUtils.contains(Global.REDIS_SYS_MENU_PREFIX + clientType + Global.COLON + userId, url);
    }

    @Override
    public int getOrder() {
        return 2;
    }
}
