package net.northking.cctp.guaca.ws;

import com.alibaba.fastjson2.JSONObject;
import com.hzbank.testteam.autotest.dependencies.authDependency.constants.AuthDependencyConstants;
import com.hzbank.testteam.autotest.dependencies.authDependency.dto.TokenValueDTO;
import com.hzbank.testteam.autotest.dependencies.baseDefineDependency.utils.SpringUtil;
import net.northking.cctp.common.http.ResultWrapper;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleSocket;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.InetGuacamoleSocket;
import org.apache.guacamole.net.SimpleGuacamoleTunnel;
import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
import org.apache.guacamole.websocket.GuacamoleWebSocketTunnelEndpoint;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
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.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;

/**
 * @author pancm
 * @Title: WebSocketTunnel
 * @Description:
 * @Version:1.0.0
 * @Since:jdk1.8
 * @Date 2021/8/2
 **/
@ServerEndpoint(value = "/pub/projectionScreen", subprotocols = "guacamole")
@Component
public class WebSocketTunnel extends GuacamoleWebSocketTunnelEndpoint {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketTunnel.class);


    private static GuacaProxyFeignClient guacaProxyFeignClient;

    @Autowired
    public  void setGuacaProxyFeignClient(GuacaProxyFeignClient guacaProxyFeignClient) {
        WebSocketTunnel.guacaProxyFeignClient = guacaProxyFeignClient;
    }

    private static String guacdIp;

    private static int guacdPort;

    @Value("${guacd.ip}")
    public void setGuacdIp(String guacdIp) {
        WebSocketTunnel.guacdIp = guacdIp;
    }
    @Value("${guacd.port}")
    public void setGuacdPort(int guacdPort) {
        WebSocketTunnel.guacdPort = guacdPort;
    }

    /**
     * Returns a new tunnel for the given session. How this tunnel is created
     * or retrieved is implementation-dependent.
     *
     * @param session        The session associated with the active WebSocket
     *                       connection.
     * @param endpointConfig information associated with the instance of
     *                       the endpoint created for handling this single connection.
     * @return A connected tunnel, or null if no such tunnel exists.
     * @throws GuacamoleException If an error occurs while retrieving the
     *                            tunnel, or if access to the tunnel is denied.
     */
    @Override
    protected GuacamoleTunnel createTunnel(Session session, EndpointConfig endpointConfig) throws GuacamoleException {
        Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
        logger.info("参数接受:{}", JSONObject.toJSONString(requestParameterMap));
        List<String> authorization = requestParameterMap.get("authorization");
        List<String> token = requestParameterMap.get("token");
        if ((token == null || token.size() == 0) || (authorization == null || authorization.size() == 0)){
            this.optClose(session,CloseReason.CloseCodes.NORMAL_CLOSURE,"未携带authorization或者未携带token参数");
            return null;
        }
        String authorizationOne = authorization.get(0);
        if (authorizationOne.contains("?")){
            authorizationOne = authorizationOne.split("\\?")[0];
        }
        String tokenOne = token.get(0);
        if (tokenOne.contains("?")){
            tokenOne = tokenOne.split("\\?")[0];
        }

        //鉴权是否为有效登录
        RedissonClient redisson = SpringUtil.getBean(RedissonClient.class);
        logger.info("开始校验用户，用户authorization：{}", authorizationOne);
        RBucket<TokenValueDTO> tokenValueDTORBucket = redisson.getBucket(AuthDependencyConstants.TOKEN_PREFIX + authorizationOne);
        TokenValueDTO tokenValueDTO = tokenValueDTORBucket.get();
        if (tokenValueDTO == null ) {
            try {
                logger.info("用户已失效，开始关闭连接...");
                session.close();
            } catch (IOException e) {
                this.optClose(session,CloseReason.CloseCodes.NORMAL_CLOSURE,"用户未登录");
            }
            return null;
        }
        //远程调用获取执行机信息
        String account = null;
        String pwd = null;
        String ipAddr = null;
        int width = 1366;
        int height = 768;
        String remoteProtocol = "rdp";
        //默认端口3389
        String remotePort = "3389";
        try {
            ResultWrapper<JSONObject> res = guacaProxyFeignClient.getInfosByToken(authorizationOne,tokenOne);
            if (!res.isSuccess()){
                this.optClose(session,CloseReason.CloseCodes.NORMAL_CLOSURE,"获取设备信息失败");
                logger.error("获取设备信息失败:{}",res.getMessage());
                return null;
            }
            JSONObject infos = res.getData();
            logger.info("远程调用信息:{}",infos);
            account = infos.getString("account");
            pwd = infos.getString("pwd");
            JSONObject engineInfo = infos.getJSONObject("engineInfo");
            ipAddr = engineInfo.getString("ipAddr");
            if (StringUtils.isEmpty(ipAddr)){
                this.optClose(session,CloseReason.CloseCodes.NORMAL_CLOSURE,"获取设备信息失败");
                return null;
            }
            JSONObject cdPcDevice = infos.getJSONObject("cdPcDevice");
            if (null != cdPcDevice){
                width = cdPcDevice.getInteger("width") == null ? 1366 : cdPcDevice.getInteger("width");
                height = cdPcDevice.getInteger("height") == null ? 768 : cdPcDevice.getInteger("height");
                remotePort = cdPcDevice.getString("remotePort") == null ? remotePort : cdPcDevice.getString("remotePort");
                remoteProtocol = cdPcDevice.getString("remoteProtocol") == null? remoteProtocol:cdPcDevice.getString("remoteProtocol");
                remoteProtocol = remoteProtocol.toLowerCase();
            }

        }catch (Exception e){
            this.optClose(session,CloseReason.CloseCodes.NORMAL_CLOSURE,"获取设备信息失败");
            logger.error("远程调用异常:",e);
            return null;
        }
        GuacamoleClientInformation information = new GuacamoleClientInformation();
        information.setOptimalScreenWidth(width);
        information.setOptimalScreenHeight(height);
        logger.info(width + "/" + height);
        String hostname = guacdIp;
        int port = guacdPort;
        GuacamoleConfiguration configuration = new GuacamoleConfiguration();
        configuration.setProtocol(remoteProtocol);
        // 远程windows服务的地址
        configuration.setParameter("hostname", ipAddr);
        configuration.setParameter("port", remotePort);
        configuration.setParameter("password", pwd);
        if ("rdp".equalsIgnoreCase(remoteProtocol)) {
            configuration.setParameter("username", account);
            configuration.setParameter("ignore-cert", "true");
        }

        GuacamoleSocket socket = new ConfiguredGuacamoleSocket(
                new InetGuacamoleSocket(hostname, port),
                configuration,
                information
        );

        GuacamoleTunnel tunnel = new SimpleGuacamoleTunnel(socket);
        return tunnel;
    }

    private void optClose(Session session,CloseReason.CloseCode closeCode,String reasonPhrase) {
        // 判断当前连接是否还在线
        if (session.isOpen()) {
            try {
                // 关闭连接
                if (null == closeCode){
                    session.close();
                }else{
                    //关闭连接并且携带关闭连接原因说明
                    session.close(new CloseReason(closeCode, reasonPhrase));
                }
            } catch (IOException e) {
                logger.error("关闭前端ws异常");
            }
        }

    }

    private String getNowTime() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
    }
}
