package com.microservices.gateway.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.CacheConstants;
import com.microservices.common.core.constant.ExceptionMsgConstants;
import com.microservices.common.core.constant.TokenConstants;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.CookieUtil;
import com.microservices.common.core.utils.ServletUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.redis.service.RedisService;
import com.microservices.gateway.service.ThirdPartyToolService;
import com.microservices.gateway.utils.CustomExecutorFactory;
import com.microservices.system.api.RemoteGatewayService;
import feign.Response;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author OTTO
 */
@Service
public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
    private static final Logger log = LoggerFactory.getLogger(ThirdPartyToolServiceImpl.class);
    @Value("${thirdPartyTools.nacos.auth.username:}")
    public String nacosUsername;
    @Value("${thirdPartyTools.nacos.auth.password:}")
    public String nacosPassword;
    @Value("${thirdPartyTools.sentinel.auth.username:}")
    public String sentinelUsername;
    @Value("${thirdPartyTools.sentinel.auth.password:}")
    public String sentinelPassword;
    @Value("${thirdPartyTools.portainer.auth.username:}")
    public String portainerUsername;
    @Value("${thirdPartyTools.portainer.auth.password:}")
    public String portainerPassword;
    @Value("${thirdPartyTools.portainer.endpoints:}")
    public Integer portainerEndpoints;
    ThreadPoolExecutor threadPoolExecutor = CustomExecutorFactory.threadPoolExecutor;
    @Lazy
    @Autowired
    private RemoteGatewayService remoteGatewayService;
    @Autowired
    private RedisService redisService;

    /**
     * 处理第三方工具请求
     */
    @Override
    public Mono<Void> handleRequestForThirdPartyTools(ServerWebExchange exchange, ServerHttpRequest request, ServerHttpRequest.Builder mutate) {
        String url = request.getURI().getPath();
        String lowerUrl = url.toLowerCase();
        if (StringUtils.isEmpty(lowerUrl)) {
            return null;
        }
        // 处理Sentinel请求
        if (lowerUrl.startsWith("/sentinel")) {
            // 检查Cookie中是否携带sentinel cookie
            String cookie = request.getHeaders().getFirst(TokenConstants.Cookie);
            if (StringUtils.isNotEmpty(CookieUtil.getCookieValue(cookie, TokenConstants.Sentinel_Token_Key))) {
                cookie = CookieUtil.removeCookieKey(cookie, TokenConstants.Sentinel_Token_Key);
            }
            String sentinelCookie = null;
            if (redisService.hasKey(CacheConstants.SENTINEL_TOKEN)) {
                sentinelCookie = redisService.getCacheObject(CacheConstants.SENTINEL_TOKEN);
            } else {
                // 网关采用异步架构，所以此处需要通过异步请求获取Sentinel Token
                Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginSentinel(sentinelUsername, sentinelPassword));
                try {
                    Response response = future.get(1, TimeUnit.SECONDS);
                    Map<String, Collection<String>> header = response.headers();
                    if (header != null && header.containsKey("set-cookie")) {
                        sentinelCookie = header.get("set-cookie").iterator().next();
                        //Sentinel登录状态默认30分钟失效，将Sentinel Token缓存过期时间调整为28分钟
                        redisService.setCacheObject(CacheConstants.SENTINEL_TOKEN, sentinelCookie, 28L, TimeUnit.MINUTES);
                    }
                } catch (TimeoutException e) {
                    log.error("获取Sentinel Token超时");
                } catch (InterruptedException | ExecutionException e) {
                    log.error("获取Sentinel Token失败：{}", e.getMessage());
                }
            }
            if (StringUtils.isNotEmpty(sentinelCookie)) {
                ServletUtils.removeHeader(mutate, TokenConstants.Cookie);
                mutate.header(TokenConstants.Cookie, String.format("%s;%s;", cookie, sentinelCookie));
            } else {
                throw new ServiceException("Sentinel服务获取Token失败");
            }
        }
        // 处理Nacos请求
        if (lowerUrl.startsWith("/nacos")) {
            String nacosToken = null;
            if (redisService.hasKey(CacheConstants.NACOS_TOKEN)) {
                nacosToken = redisService.getCacheObject(CacheConstants.NACOS_TOKEN);
            } else {
                Map<String, String> nacosMap = new HashMap<>();
                nacosMap.put("username", nacosUsername);
                nacosMap.put("password", nacosPassword);
                // 网关采用异步架构，所以此处需要通过异步请求获取Nacos Token
                Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginNacos(nacosMap));
                try {
                    Response res = future.get(1, TimeUnit.SECONDS);
                    StringWriter writer = new StringWriter();
                    IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name());
                    String str = writer.toString();
                    if (!JSON.isValid(str)) {
                        return exceptionResponse(exchange, str, res.status());
                    }
                    JSONObject resJsonObject = JSONObject.parseObject(str);
                    if (resJsonObject == null) {
                        return exceptionResponse(exchange, str, res.status());
                    }
                    nacosToken = resJsonObject.getString("accessToken");
                    Long tokenTtl = resJsonObject.getLong("tokenTtl");
                    if (nacosToken != null && tokenTtl != null) {
                        redisService.setCacheObject(CacheConstants.NACOS_TOKEN, nacosToken, tokenTtl - 10, TimeUnit.SECONDS);
                    }
                } catch (TimeoutException e) {
                    log.error("获取Nacos Token超时");
                } catch (InterruptedException | ExecutionException | IOException | NullPointerException e) {
                    log.error("获取Nacos Token失败：{}", e.getMessage());
                }
            }
            if (StringUtils.isNotEmpty(nacosToken)) {
                mutate.header(TokenConstants.Nacos_Token_Key, nacosToken);
            } else {
                throw new ServiceException("Nacos服务获取Token失败");
            }
        }

        // 处理portainer请求
        if (lowerUrl.startsWith("/portainer")) {
            // 处理portainer指定endpoints的问题
            if (lowerUrl.startsWith("/portainer/api/endpoints/")) {
                String regex = "^/portainer/api/endpoints/\\d+(/?.*)";
                if (Pattern.matches(regex, lowerUrl)) {
                    // 替换掉endpoints
                    regex = "(^/portainer/api/endpoints/)(\\d+)(/?.*)";
                    Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
                    Matcher matcher = pattern.matcher(url);
                    if (matcher.find()) {
                        // 替换数字部分
                        url = matcher.replaceAll(matcher.group(1) + portainerEndpoints + matcher.group(3));
                        try {
                            mutate.uri(new URI(url));
                        } catch (URISyntaxException e) {
                            log.error("替换Portainer请求中Endpoints时发生异常：{}", e.getMessage());
                        }
                    }
                }
            }

            String portainerToken = null;
            String cookie = request.getHeaders().getFirst(TokenConstants.Cookie);
            if (StringUtils.isNotEmpty(CookieUtil.getCookieValue(cookie, TokenConstants.Portainer_Token_Key))) {
                cookie = CookieUtil.removeCookieKey(cookie, TokenConstants.Portainer_Token_Key);
            }

            if (redisService.hasKey(CacheConstants.PORTAINER_TOKEN)) {
                portainerToken = redisService.getCacheObject(CacheConstants.PORTAINER_TOKEN);
            } else {
                // 网关采用异步架构，所以此处需要通过异步请求获取Portainer Token
                JSONObject loginPortainerBody = new JSONObject();
                loginPortainerBody.put("username", portainerUsername);
                loginPortainerBody.put("password", portainerPassword);
                Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginPortainer(loginPortainerBody));
                try {
                    Response res = future.get(1, TimeUnit.SECONDS);
                    StringWriter writer = new StringWriter();
                    IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name());
                    String str = writer.toString();
                    if (!JSON.isValid(str)) {
                        return exceptionResponse(exchange, str, res.status());
                    }
                    JSONObject resJsonObject = JSONObject.parseObject(str);
                    portainerToken = resJsonObject.getString("jwt");
                    if (portainerToken == null) {
                        return exceptionResponse(exchange, str, res.status());
                    }
                    String[] tokenSplit = portainerToken.split("\\.");
                    String tokenBodyStr = new String(Base64.getDecoder().decode(tokenSplit[1]), StandardCharsets.UTF_8);
                    JSONObject tokenBody = JSONObject.parseObject(tokenBodyStr);
                    Long expiration = tokenBody.getLong("exp");
                    Long now = new Date().getTime() / 1000;

                    long portainerTokenTtl = expiration - now;
                    if (portainerTokenTtl > 20) {
                        redisService.setCacheObject(CacheConstants.PORTAINER_TOKEN, portainerToken, portainerTokenTtl - 10, TimeUnit.SECONDS);
                    }
                } catch (TimeoutException e) {
                    log.error("获取Portainer Token超时");
                } catch (InterruptedException | ExecutionException | IOException | NullPointerException e) {
                    log.error("获取Portainer Token失败：{}", e.getMessage());
                }
            }
            if (StringUtils.isNotEmpty(portainerToken)) {
                ServletUtils.removeHeader(mutate, TokenConstants.Cookie);
                ServletUtils.removeHeader(mutate, TokenConstants.AUTHENTICATION);
                mutate.header(TokenConstants.AUTHENTICATION, TokenConstants.PREFIX + portainerToken);
                mutate.header(TokenConstants.Cookie, String.format("%s;%s;", cookie, portainerToken));
            } else {
                throw new ServiceException("Portainer服务获取Token失败");
            }
        }
        return null;
    }


    private Mono<Void> exceptionResponse(ServerWebExchange exchange, String msg, int code) {
        log.error("[第三方工具请求处理异常]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), msg);
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), ExceptionMsgConstants.SYSTEM_EXEC_ERROR, code);
    }
}
