package shop.alien.gateway.config;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import shop.alien.entity.store.LawyerUser;
import shop.alien.entity.store.LifeUser;
import shop.alien.entity.store.StoreUser;
import shop.alien.gateway.mapper.LawyerUserGatewayMapper;
import shop.alien.gateway.mapper.LifeUserGatewayMapper;
import shop.alien.gateway.mapper.StoreUserGatewayMapper;
import shop.alien.util.common.JwtUtil;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static org.apache.commons.codec.language.bm.Languages.ANY;

/**
 * Jwt过滤器
 *
 * @author ssk
 * @version 1.0
 * @date 2025/1/21 16:58
 */
@Slf4j
@Data
@Component
public class JwtTokenFilter implements GlobalFilter, Ordered {

    @Value("${jwt.skip-auth-urls}")
    private String[] skipAuthUrls;

    @Autowired
    private BaseRedisService baseRedisService;

    @Autowired
    private StoreUserGatewayMapper storeUserMapper;

    @Autowired
    private LifeUserGatewayMapper lifeUserMapper;

    @Autowired
    private LawyerUserGatewayMapper lawyerUserMapper;

    /**
     * 过滤器
     *
     * @param exchange 请求
     * @param chain    网关
     * @return Mono
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String url = exchange.getRequest().getURI().getPath();
        log.info("JwtTokenFilter.filter?Path={}", url);
        if (Objects.equals(exchange.getRequest().getMethod(), HttpMethod.OPTIONS)) {
            return allowChain(exchange, chain);
        }
        //跳过不需要验证的路径
        if (null != skipAuthUrls && Arrays.asList(skipAuthUrls).contains(url)) {
            return allowChain(exchange, chain);
        }
        if (null != skipAuthUrls && url.contains("/alienStore/socket/")) {
            return chain.filter(exchange);
        }
        if (url.startsWith("/alienStore/webjars") || url.startsWith("/alienSecond/webjars")) {
            return allowChain(exchange, chain);
        }
        //获取token
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        log.info("JwtTokenFilter.filter?Token={}", token);
        JSONObject map = new JSONObject();
        ServerHttpResponse resp = exchange.getResponse();
        if (StringUtils.isBlank(token)) {
            //没有token
            log.error("JwtTokenFilter.filter ERROR 没有Token");
            return authError(resp, "请登录");
        } else {
            //有token
            try {
                JwtUtil.checkToken(token);
                Claims claims = JwtUtil.parseJWT(token);
                JSONObject tokenData = JSONObject.parseObject(claims.get("sub").toString());
                String deviceType = tokenData.getString("userType");
                String phone = tokenData.getString("phone");
                String redisKey;
                //区分
                if ("web".equals(deviceType)) {
                    //管理端单设备登录
                    //不限制
                    return allowChain(exchange, chain);
                } else {
                    redisKey = deviceType + "_" + phone;
                }
                String redisVal = baseRedisService.getString(redisKey);
                if (StringUtils.isEmpty(redisVal) || !token.equals(redisVal)) {
                    if ("store".equals(deviceType) || "storePlatform".equals(deviceType)) {
                        //判断程序是否为用户禁用
                        StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getPhone, phone));
                        if (storeUser.getStatus() == 1) {
                            map.put("msg", "你的账号已被禁用");
                            //别问, 问就是约定俗成
                            map.put("code", 777);
                        }
                        if (!redisVal.equals(token)) {
                            map.put("msg", "账号在别处登录");
                            //别问, 问就是约定俗成
                            map.put("code", 666);
                        }
                    } else if ("user".equals(deviceType)) {
                        //判断程序是否为用户禁用
                        LifeUser lifeUser = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>().eq(LifeUser::getUserPhone, phone));
                        //注销标记, 0:未注销, 1:已注销
                        if (null != lifeUser.getLogoutFlag() && lifeUser.getLogoutFlag() == 1) {
                            map.put("msg", "你的账号已注销");
                            //别问, 问就是约定俗成
                            map.put("code", 777);
                        }
                        if (!redisVal.equals(token)) {
                            map.put("msg", "账号在别处登录");
                            //别问, 问就是约定俗成
                            map.put("code", 666);
                        }
                    }
                    else if ("lawyer".equals(deviceType)) {
                        //判断程序是否为用户禁用
                        LawyerUser lawyerUser = lawyerUserMapper.selectOne(new LambdaQueryWrapper<LawyerUser>().eq(LawyerUser::getPhone, phone));
                        //注销标记, 0:未注销, 1:已注销
                        if (null != lawyerUser.getLogoutFlag() && lawyerUser.getLogoutFlag() == 1) {
                            map.put("msg", "你的账号已注销");
                            //别问, 问就是约定俗成
                            map.put("code", 777);
                        }
                        if (!redisVal.equals(token)) {
                            map.put("msg", "账号在别处登录");
                            //别问, 问就是约定俗成
                            map.put("code", 666);
                        }
                    }
                    map.put("success", false);
                    exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
                    return exchange.getResponse()
                            .writeWith(Mono.just(exchange.getResponse()
                                    .bufferFactory()
                                    .wrap(map.toJSONString().getBytes())));
                }
                return allowChain(exchange, chain);
            } catch (ExpiredJwtException e) {
                if (e.getMessage().contains("Allowed clock skew")) {
                    log.error("JwtTokenFilter.filter ERROR 认证过期");
                    return authError(resp, "认证过期");
                } else {
                    log.error("JwtTokenFilter.filter ERROR 认证失败");
                    return authError(resp, "认证失败");
                }
            } catch (Exception e) {
                log.error("JwtTokenFilter.filter ERROR 认证失败");
                return authError(resp, "认证失败");
            }
        }
    }

    private Mono<Void> allowChain(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            exchange.getResponse().getHeaders().entrySet().stream()
                    .filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
                    .filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
                            || kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)
                            || kv.getKey().equals(HttpHeaders.VARY)))
                    .forEach(kv -> {
                        // Vary只需要去重即可
                        if (kv.getKey().equals(HttpHeaders.VARY))
                            kv.setValue(kv.getValue().stream().distinct().collect(Collectors.toList()));
                        else {
                            List<String> value = new ArrayList<>();
                            if (kv.getValue().contains(ANY)) {  //如果包含*，则取*
                                value.add(ANY);
                                kv.setValue(value);
                            } else {
                                value.add(kv.getValue().get(0)); // 否则默认取第一个
                                kv.setValue(value);
                            }
                        }
                    });
        }));
    }

    /**
     * 认证错误输出
     *
     * @param resp    响应对象
     * @param message 错误信息
     * @return Mono
     */
    private Mono<Void> authError(ServerHttpResponse resp, String message) {
        resp.setStatusCode(HttpStatus.UNAUTHORIZED);
        resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        JSONObject json = new JSONObject();
        json.put("code", HttpStatus.UNAUTHORIZED.value());
        json.put("msg", message);
        json.put("data", "");
        log.error("认证错误响应: {}", json.toJSONString());
        DataBuffer buffer = resp.bufferFactory().wrap(json.toString().getBytes(StandardCharsets.UTF_8));
        return resp.writeWith(Flux.just(buffer));
    }

    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
    }
}
