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 (isSkipAuthUrl(url)) {
            return allowChain(exchange, chain);
        }
        if (null != skipAuthUrls && url.contains("/alienStore/socket/")) {
            return chain.filter(exchange);
        }
        // 跳过AI语音识别接口的认证
        if (url.contains("/alienStore/aiUp/GetAIUpload")) {
            log.info("跳过AI语音识别接口认证: {}", url);
            return allowChain(exchange, chain);
        }
        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 openid = tokenData.getString("openid");
                String redisKey;
                String redisVal = null;
                //区分
                if ("web".equals(deviceType)) {
                    //管理端单设备登录
                    //不限制
                    return allowChain(exchange, chain);
                } else if ("user".equals(deviceType) || "miniprogram_user".equals(deviceType)) {
                    // 用户/小程序：openid 仍为单 string；手机号维度支持多设备 Set（user_sessions: / miniprogram_user_sessions:）并兼容旧 string key
                    if (StringUtils.isNotBlank(openid)) {
                        String val = baseRedisService.getString("miniprogram_user_token:" + openid);
                        if (StringUtils.isNotBlank(val) && token.equals(val)) {
                            redisVal = val;
                        }
                    }
                    if (redisVal == null && StringUtils.isNotBlank(phone)) {
                        if (baseRedisService.isSetMember("miniprogram_user_sessions:" + phone, token)) {
                            redisVal = token;
                        } else {
                            String mpVal = baseRedisService.getString("miniprogram_user_" + phone);
                            if (StringUtils.isNotBlank(mpVal) && token.equals(mpVal)) {
                                redisVal = mpVal;
                            }
                        }
                    }
                    if (redisVal == null && StringUtils.isNotBlank(phone)) {
                        if (baseRedisService.isSetMember("user_sessions:" + phone, token)) {
                            redisVal = token;
                        } else {
                            String userVal = baseRedisService.getString("user_" + phone);
                            if (StringUtils.isNotBlank(userVal) && token.equals(userVal)) {
                                redisVal = userVal;
                            }
                        }
                    }
                } else {
                    redisKey = deviceType + "_" + phone;
                    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 != null && storeUser.getStatus() == 1) {
                            map.put("msg", "你的账号已被禁用");
                            //别问, 问就是约定俗成
                            map.put("code", 777);
                        }
                        if (storeUser != null && storeUser.getLogoutFlag() != null
                                && StoreUser.LOGOUT_FLAG_DONE == storeUser.getLogoutFlag()
                                && !Integer.valueOf(-1).equals(storeUser.getStatus())) {
                            map.put("msg", "你的账号已注销");
                            map.put("code", 777);
                        }
                        if (!token.equals(redisVal)) {
                            map.put("msg", "账号在别处登录");
                            //别问, 问就是约定俗成
                            map.put("code", 666);
                        }
                    } else if ("user".equals(deviceType) || "miniprogram_user".equals(deviceType)) {
                        //判断程序是否为用户禁用/注销（支持按 userId 查，兼容无手机号的小程序用户）
                        LifeUser lifeUser = null;
                        if (StringUtils.isNotBlank(phone)) {
                            lifeUser = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>().eq(LifeUser::getUserPhone, phone));
                        }
                        if (lifeUser == null && tokenData.containsKey("userId") && StringUtils.isNotBlank(tokenData.getString("userId"))) {
                            try {
                                lifeUser = lifeUserMapper.selectById(Integer.parseInt(tokenData.getString("userId")));
                            } catch (NumberFormatException ignored) { }
                        }
                        if (lifeUser != null) {
                            //注销标记, 0:未注销, 1:已注销
                            if (null != lifeUser.getLogoutFlag() && lifeUser.getLogoutFlag() == 1) {
                                map.put("msg", "你的账号已注销");
                                map.put("code", 777);
                            }
                        }
                        if (!token.equals(redisVal)) {
                            map.put("msg", "账号在别处登录");
                            map.put("code", 666);
                        }
                    } else if ("lawyer".equals(deviceType)) {
                        LawyerUser lawyerUser = lawyerUserMapper.selectOne(new LambdaQueryWrapper<LawyerUser>().eq(LawyerUser::getPhone, phone));
                        if (lawyerUser != null && lawyerUser.getLogoutFlag() != null
                                && LawyerUser.LOGOUT_FLAG_DONE == lawyerUser.getLogoutFlag()
                                && (lawyerUser.getDeleteFlag() == null || lawyerUser.getDeleteFlag() == 0)) {
                            map.put("msg", "你的账号已注销");
                            map.put("code", 777);
                        }
                        if (!token.equals(redisVal)) {
                            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, "认证失败");
            }
        }
    }

    /**
     * 判断请求路径是否在白名单内（免登录）。
     * 配置项以 "**" 结尾时做前缀匹配，否则做精确匹配。
     * 例如：/aliendining/store/info/detail/** 可放行 /aliendining/store/info/detail/1、/aliendining/store/info/detail/123 等。
     */
    private boolean isSkipAuthUrl(String requestPath) {
        if (skipAuthUrls == null || skipAuthUrls.length == 0) {
            return false;
        }
        for (String skip : skipAuthUrls) {
            if (skip == null) continue;
            String trimmed = skip.trim();
            if (trimmed.endsWith("**")) {
                String prefix = trimmed.substring(0, trimmed.length() - 2);
                if (requestPath.startsWith(prefix)) {
                    return true;
                }
            } else if (requestPath.equals(trimmed)) {
                return true;
            }
        }
        return false;
    }

    private Mono<Void> allowChain(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            try {
                HttpHeaders headers = exchange.getResponse().getHeaders();
                for (String name : Arrays.asList(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, HttpHeaders.VARY)) {
                    List<String> values = headers.get(name);
                    if (values == null || values.size() <= 1) continue;
                    List<String> newValue;
                    if (HttpHeaders.VARY.equals(name)) {
                        newValue = values.stream().distinct().collect(Collectors.toList());
                    } else {
                        newValue = new ArrayList<>();
                        newValue.add(values.contains(ANY) ? ANY : values.get(0));
                    }
                    headers.put(name, newValue);
                }
            } catch (Exception e) {
                // 响应已提交或 headers 不可写时忽略，避免 UnsupportedOperationException 等打断链路
                log.debug("allowChain 整理 CORS 头失败（可能响应已提交）: {}", e.getMessage());
            }
        }));
    }

    /**
     * 认证错误输出
     *
     * @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;
    }
}
