package net.northking.cctp.se.device.bean;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import net.northking.cctp.common.enums.FileBusinessTypeEnum;
import net.northking.cctp.common.http.ResultWrapper;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.pc.driver.dto.EngineResultDto;
import net.northking.cctp.pc.driver.socket.PCDriver;
import net.northking.cctp.se.file.Attachment;
import net.northking.cctp.se.util.HttpUtils;
import net.northking.cctp.se.util.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;

import java.io.File;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

@Slf4j
public class PCDeviceConnection extends AbstractDeviceConnection {

    public static final int DEFAULT_TIMEOUT = 31;

    public static final String TENANT_ID = "tenantId";
    public static final String ENV_TYPE = "envType";
    public static final String BROWSER_TYPE = "browserType";
    public static final String PROJECT_ID = "projectId";
    public static final String ENV_ID = "envId";
    public static final String SYS_NAME = "sysName";
    public static final String URL = "url";
    public static final String TEST_SYS_ADDRESS = "testSysAddress";
    public static final String DEVICE_ID = "deviceId";
    public static String userName = "deviceId";
    public static String password = "deviceId";
    private int width = 1366;   // 默认分辨率 宽
    private int height = 768;   // 默认分辨率 高

    private String testSysRemoteAddress;

    public PCDeviceConnection(String deviceToken,DeviceInfo deviceInfo) throws Exception{
        super(deviceToken,deviceInfo);
        if (deviceInfo.getOtherInfo().get("width") != null && deviceInfo.getOtherInfo().get("height") != null) {
            this.width = Integer.parseInt(deviceInfo.getOtherInfo().get("width"));
            this.height = Integer.parseInt(deviceInfo.getOtherInfo().get("height"));
        }        
		if (deviceInfo.getOtherInfo().get("userName") != null && deviceInfo.getOtherInfo().get("password") != null) {
			this.userName = deviceInfo.getOtherInfo().get("userName");
			this.password = deviceInfo.getOtherInfo().get("password");
        }
		this.testSysRemoteAddress = deviceInfo.getOtherInfo().get(TEST_SYS_ADDRESS);
    }

    @Override
    protected void initDriver() throws MalformedURLException {
        // 创建webSocket连接
        if (ObjectUtil.isNull(this.deviceInfo)){
            throw new RuntimeException("设备信息不存在");
        }
        String address = this.deviceInfo.getConnectAddress();
        if (StrUtil.isBlank(address)){
            throw new RuntimeException("设备地址信息不存在");
        }
        this.deviceDriver = new PCDeviceDriver(address);
    }

    @Override
    public void initConfig(Map<String, Object> config) {
        // 初始化pc执行环境
        log.info("初始化pc执行环境...");
        int envType = EnvType.CS.code;
        if (config.get(PCDeviceConnection.PROJECT_ID) != null
                && config.get(PCDeviceConnection.ENV_ID) != null){
            envType = EnvType.BS.code;
        }

        log.info("pc执行环境类型 => {}", envType);
        // 是否调试，默认false
        boolean debug = false;

        if(null != config.get("debug")){
            debug = (Boolean) config.get("debug");
        }
        Map<String, Object> initEnvMap = new HashMap<>(5);
        String cmd = "InitEnv";
        initEnvMap.put("cmd", cmd);
        initEnvMap.put("seqId", IdUtil.simpleUUID());
        initEnvMap.put("envType", envType);
        // 判断B/S
        if (envType == EnvType.BS.code) {
            // 默认是ie
            int browserType = 1;
            if (ObjectUtil.isNotNull(config.get(BROWSER_TYPE))) {
                browserType = Integer.parseInt(config.get(BROWSER_TYPE).toString());
            }
            Object projectIdObj = config.get(PROJECT_ID);
            if (projectIdObj == null || StrUtil.isBlankIfStr(projectIdObj)){
                throw new ExecuteException("获取被测系统地址参数[ " + PROJECT_ID +" ]为空");
            }
            Object envIdObj = config.get(ENV_ID);
            if (envIdObj == null || StrUtil.isBlankIfStr(envIdObj)){
                throw new ExecuteException("获取被测系统地址参数[ " + ENV_ID +" ]为空");
            }
            Object sysNameObj = config.get(SYS_NAME);
            if (sysNameObj == null || StrUtil.isBlankIfStr(sysNameObj)){
                throw new ExecuteException("获取被测系统地址参数[ " + SYS_NAME +" ]为空");
            }
            String url = getSysUrl(config);
            if (StrUtil.isBlank(url)){
                throw new ExecuteException("获取的被测系统地址为空");
            }
            log.debug("浏览器打开网址：{}，浏览器类型为：{}", url, browserType);
            initEnvMap.put("browserType", browserType);
            initEnvMap.put("url", url);
        }
        initEnvMap.put("debug", debug);

        initEnvMap.put("width", width);
        initEnvMap.put("height", height);
        initEnvMap.put("userName", this.userName);
        initEnvMap.put("password", this.password);

        // 发送webSocket消息至PC执行机
        log.info("pc init env send => " + JSONUtil.toJsonStr(initEnvMap));
        commonSendAndReturn(cmd, initEnvMap, (PCDriver) this.deviceDriver);
    }

    private String getSysUrl(Map<String, Object> config){
        Map<String, String> params = new HashMap<>();
        params.put("projectId", config.get(PROJECT_ID).toString());
        params.put("envId", config.get(ENV_ID).toString());
        params.put("sysName", config.get(SYS_NAME).toString());
        log.debug("查询被测系统URL参数 => {}", JSONUtil.toJsonStr(params));
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(params);
        ResultWrapper result = null;
        try {
            result = HttpUtils.doPost(testSysRemoteAddress, entity, ResultWrapper.class);
        }catch (Exception e){
            log.error("根据项目环境和系统名称获取被测系统url异常", e);
        }
        if (result == null){
            throw new ExecuteException("获取被测系统地址失败");
        }
        if (!result.isSuccess()) {
            throw new ExecuteException("获取被测系统地址失败，"+  result.getMessage());
        }
        if (result.getData() == null) {
            throw new ExecuteException("获取被测系统地址失败，该环境下的被测系统地址为空，请检查");
        }
        return result.getData().toString();
    }

    @Override
    public void restart(Map<String, Object> config){
        // 默认是ie
        int browserType = 1;
        String url = "";
        if (CollUtil.isNotEmpty(config)){
            if (ObjectUtil.isNotNull(config.get(BROWSER_TYPE))){
                browserType = Integer.parseInt(config.get(BROWSER_TYPE).toString());
            }
            url = getSysUrl(config);
        }
        Map<String, Object> restartMap = new HashMap<>(4);
        String cmd = "RestartBrowser";
        restartMap.put("cmd", cmd);
        restartMap.put("seqId", IdUtil.simpleUUID());
        restartMap.put("browserType", browserType);
        restartMap.put("url", url);

        // 发送webSocket消息至PC执行机
        log.info("pc restart browser send => " + JSONUtil.toJsonStr(restartMap));
        commonSendAndReturn(cmd, restartMap, (PCDriver) this.deviceDriver);
    }

    @Override
    public void startRecord() {
        Map<String, Object> recordingMap = new HashMap<>(2);
        String cmd = "Recording";
        recordingMap.put("cmd", cmd);
        recordingMap.put("seqId", IdUtil.simpleUUID());
        recordingMap.put("switch", "on");
        // 发送webSocket消息至PC执行机
        log.info("pc recording send => " + JSONUtil.toJsonStr(recordingMap));
        commonSendAndReturn(cmd, recordingMap, (PCDriver) this.deviceDriver);
    }

    @Override
    public String stopRecord(Map<String, Object> initData) {
        if (ObjectUtil.isNull(initData.get(TENANT_ID))){
            throw new RuntimeException("租户编号不能为空");
        }
        if (ObjectUtil.isNull(initData.get("save"))){
            throw new RuntimeException("视频保存标识不能为空");
        }
        Map<String, Object> recordingMap = new HashMap<>(2);
        String cmd = "Recording";
        recordingMap.put("cmd", cmd);
        recordingMap.put("seqId", IdUtil.simpleUUID());
        recordingMap.put("switch", "off");
        // 发送webSocket消息至PC执行机
        log.info("pc recording send => " + JSONUtil.toJsonStr(recordingMap));
        String offResult = commonSendAndReturn(cmd, recordingMap, (PCDriver) this.deviceDriver);
        if (StrUtil.isBlank(offResult)){
            log.error("offResult => " + JSONUtil.toJsonStr(offResult));
            throw new RuntimeException("停止录制组件结果异常");
        }
        Boolean save = (Boolean) initData.get("save");
        if (!save){
            log.info("无需保存视频文件");
            return "";
        }
        EngineResultDto engineResult = JSONUtil.toBean(offResult, EngineResultDto.class);
        String localPath = engineResult.getValue();
        log.debug("localPath = " + localPath);
        if (StrUtil.isEmpty(localPath)) {
            throw new RuntimeException("视频地址不能为空");
        }
        File file = FileUtil.newFile(localPath);
        if (FileUtil.isEmpty(file)) {
            throw new RuntimeException("视频文件不能为空");
        }
        String tenantId = initData.get(TENANT_ID).toString();
        try {
            // 改成http上传 2023.3.1 jieying.li
//            NKFile nkFile = simpleStorageService.upload(tenantId, FileUtil.newFile(localPath), true);
            Attachment upload = HttpUtils.upload(localPath, tenantId, (String) initData.get("taskId"), FileBusinessTypeEnum.PC_TASK_VIDEO.getCode());
            if (ObjectUtil.isNull(upload) || StringUtils.isBlank(upload.getId())){
                throw new RuntimeException("视频文件上传信息为空");
            }
            return upload.getUrlPath();
        }catch (Exception e){
            log.error("文件上传失败", e);
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public void startLog() {

    }

    @Override
    public String stopLog() {
        return null;
    }

    @Override
    public String snapshot(Map<String, Object> initData) {
        if (ObjectUtil.isNull(initData.get(TENANT_ID))){
            throw new RuntimeException("租户编号不能为空");
        }
        Map<String, Object> takeScreenMap = new HashMap<>(2);
        String cmd = "EngineTakeScreen";
        takeScreenMap.put("cmd", cmd);
        takeScreenMap.put("seqId", IdUtil.simpleUUID());
        // 发送webSocket消息至PC执行机
        log.info("pc snapshot send => " + JSONUtil.toJsonStr(takeScreenMap));
        String takeScreenResult = commonSendAndReturn(cmd, takeScreenMap, (PCDriver) this.deviceDriver);
        if (StrUtil.isBlank(takeScreenResult)){
            log.error("takeScreenResult => " + JSONUtil.toJsonStr(takeScreenResult));
            throw new RuntimeException("截图组件结果异常");
        }
        EngineResultDto engineResult = JSONUtil.toBean(takeScreenResult, EngineResultDto.class);
        String localPath = engineResult.getValue();
        log.debug("localPath = " + localPath);
        if (StrUtil.isEmpty(localPath)) {
            throw new RuntimeException("截图地址不能为空");
        }
        String tenantId = initData.get(TENANT_ID).toString();
        try {
            log.debug("截图上传入参tenantId：{}",tenantId);
            log.debug("截图上传入参initData：{}", JSONObject.toJSONString(initData));
            log.debug("截图入参taskId,{}", initData.get("taskId"));
            Attachment upload = HttpUtils.upload(localPath, tenantId, (String)initData.get("taskId"), FileBusinessTypeEnum.PC_TASK_SCREENSHOT.getCode());
            if (ObjectUtil.isNull(upload) || StringUtils.isBlank(upload.getId())){
                throw new RuntimeException("截图文件上传信息为空");
            }
            return upload.getUrlPath();
        }catch (Exception e){
            log.error("截图上传失败", e);
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public String snapshotAllScreen(String tenantId, String taskId) {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put(TENANT_ID, tenantId);
        dataMap.put("taskId",taskId);
        return snapshot(dataMap);
    }

    @Override
    public void stopPlay(Map<String, Object> config){
        Map<String, Object> stopPlayMap = new HashMap<>(2);
        String cmd = "StopPlay";
        stopPlayMap.put("cmd", cmd);
        stopPlayMap.put("seqId", IdUtil.simpleUUID());
        // 发送webSocket消息至PC执行机
        log.info("pc stopPlay send => " + JSONUtil.toJsonStr(stopPlayMap));
        commonSendAndReturn(cmd, stopPlayMap, (PCDriver) this.deviceDriver);
    }

    /**
     * 通用发送与返回结果处理
     * @param params 发送参数
     * @param pcDriver 驱动
     * @return 结果
     */
    private static String commonSendAndReturn(String cmd, Map<String, Object> params, PCDriver pcDriver){
        try {
            pcDriver.semaphore = new Semaphore(0);
            pcDriver.send(JSONUtil.toJsonStr(params));
            if (!pcDriver.semaphore.tryAcquire(DEFAULT_TIMEOUT, TimeUnit.SECONDS)) {
                throw new RuntimeException("指令[" + cmd + "]执行超时");
            }
            if (StrUtil.isBlank(pcDriver.resResult)) {
                throw new RuntimeException("指令[" + cmd + "]执行结果为空");
            }
            EngineResultDto engineResult = JSONUtil.toBean(pcDriver.resResult, EngineResultDto.class);
            if (!engineResult.getState().equals(0)){
                throw new RuntimeException(engineResult.getMsg());
            }
            log.debug("receive robot result to engine -> {}", JsonUtils.toJson(engineResult));
            return JSONUtil.toJsonStr(engineResult);
        }catch (Exception e){
            log.error("通讯发生异常", e);
            throw new RuntimeException(e.getMessage());
        }
    }

    public enum EnvType {
        BS(1, "B/S环境"),
        CS(2, "C/S环境");

        private final Integer code;

        private final String msg;

        EnvType(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }

        public Integer getCode() {
            return code;
        }

        public String getMsg() {
            return msg;
        }
    }
}
