package com.tofly.scada.service.impl;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tofly.common.core.util.DateUtil;
import com.tofly.common.core.util.StringUtil;
import com.tofly.common.oauth.util.SecurityUtils;
import com.tofly.scada.entity.*;
import com.tofly.scada.entity.vo.IOSever.IOServerDataInfoVo;
import com.tofly.scada.entity.vo.IOSever.IOServerParObjsDto;
import com.tofly.scada.entity.vo.IOSever.IOServerResultVo;
import com.tofly.scada.mapper.ScadaMapper;
import com.tofly.scada.mapper.ScadaMonitorMapper;
import com.tofly.scada.service.*;
import com.tofly.scada.util.HttpClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;


@Service
@Slf4j
public class IOServerServiceImpl implements IOServerService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DeviceArchiveManageService deviceArchiveManageService;

    @Autowired
    private ScadaMapper scadaMapper;

    @Autowired
    private ScadaMonitorService monitorService;

    @Autowired
    private ScadaIoserverHisService ioserverHisService;

    @Value("${ioserver.username}")
    private String username;
    @Value("${ioserver.password}")
    private String password;
    @Value("${ioserver.authorizationUrl}")
    private String authorizationUrl;
    @Value("${ioserver.heartbeatUrl}")
    private String heartbeatUrl;
    @Value("${ioserver.batchRealValueUrl}")
    private String batchRealValueUrl;
    @Value("${ioserver.realVariablesUrl}")
    private String realVariablesUrl;


    private final Map<String, String> headers = new HashMap<>(12);

    private final String REDIS_IOSERVER_TOKEN = "REDIS_IOSERVER_TOKEN";


    public String getAuthorization() {
        Map<String, Object> parMap = new HashMap<>(12);
        parMap.put("username", username);
        parMap.put("password", password);
        try {
            String s = HttpClientUtil.doPostJson(authorizationUrl, headers, JSON.toJSONString(parMap));
            log.info("IOServer获取token,返回参数{}", s);
            IOServerResultVo ioServerResultVo = JSONObject.parseObject(s, IOServerResultVo.class);
            if (null != ioServerResultVo && 0 == ioServerResultVo.getCode()) {
                JSONObject jsonObject = JSONObject.parseObject(ioServerResultVo.getData().toString());
                return (String) jsonObject.get("Authorization");
            } else {
                return "";
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 注销token
     *
     * @param authorization
     * @return
     */
    public void outAuthorization(String authorization) throws IOException {
        headers.put("Authorization", authorization);
        String s = HttpClientUtil.doPostJson(authorizationUrl, headers, "");
        log.info("IOServer注销token,返回参数{}", s);
    }

    public String getHeartbeat(String authorization) throws IOException {
        Map<String, Object> parMap = new HashMap<>(12);
        headers.put("Authorization", authorization);

        log.info("IOServer获取token心跳状态,请求参数Token {}", authorization);
        String s = HttpClientUtil.doPostJson(heartbeatUrl, headers, JSON.toJSONString(parMap));
        log.info("IOServer获取token心跳状态,返回参数{}", s);
        IOServerResultVo ioServerResultVo = JSONObject.parseObject(s, IOServerResultVo.class);
        if (null != ioServerResultVo && 0 == ioServerResultVo.getCode()) {
            return authorization;
        } else {
            return "";
        }


    }

    /**
     * 判断当前redis中token是否保持心跳,若保持则继续使用当前token,否则重新请求
     *
     * @return
     */
    public String checkHeartbeat() throws IOException {
        String authorization = "";
        Boolean aBoolean = redisTemplate.hasKey(REDIS_IOSERVER_TOKEN);
        if (null != aBoolean && aBoolean) {
            String s = redisTemplate.opsForValue().get(REDIS_IOSERVER_TOKEN);
            // 心跳监测
            String heartbeat = getHeartbeat(s);
            // 不过期
            if (StringUtil.isNotEmpty(heartbeat)) {
                authorization = s;
            } else {
                // 当前token失效 重新请求
                authorization = getAuthorization();
                redisTemplate.opsForValue().set(REDIS_IOSERVER_TOKEN, authorization);
            }

        } else {
            // redis未缓存 重新请求
            authorization = getAuthorization();
            if (StringUtil.isNotEmpty(authorization)) {
                redisTemplate.opsForValue().set(REDIS_IOSERVER_TOKEN, authorization);

            }
        }
        headers.put("Authorization", authorization);
        return authorization;
    }


    @Override
    public List<IOServerDataInfoVo> getBatchRealValue(List<String> codeList) throws IOException {
        // token监测
        checkHeartbeat();
        // 请求参数封装
        Map<String, Object> parMap = new HashMap<>(12);
        List<IOServerParObjsDto> dtoList = new ArrayList<>();
        codeList.forEach(i -> {
            IOServerParObjsDto dto = new IOServerParObjsDto();
            dto.setN(i);
            dtoList.add(dto);
        });
        parMap.put("objs", JSON.parseArray(JSON.toJSONString(dtoList)));
        String par = JSON.toJSONString(parMap);
        log.info("IOServer获取指定变量列表实时值,请求参数{}", par);
        String s = HttpClientUtil.doPostJson(batchRealValueUrl, headers, par);
        log.info("IOServer获取指定变量列表实时值,返回参数{}", s);
        IOServerResultVo ioServerResultVo = JSONObject.parseObject(s, IOServerResultVo.class);
        if (null != ioServerResultVo && 0 == ioServerResultVo.getCode()) {
            return JSONObject.parseArray(ioServerResultVo.getData().toString(), IOServerDataInfoVo.class);
        }
//        outAuthorization(headers.get("Authorization"));
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String realVariables(List<IOServerParObjsDto> codeList) throws IOException {
        // token监测
        checkHeartbeat();
        // 请求参数封装
        Map<String, Object> parMap = new HashMap<>(12);
        parMap.put("objs", JSON.parseArray(JSON.toJSONString(codeList)));
        String par = JSON.toJSONString(parMap);
        log.info("IOServer实时数据回写,请求参数{}", par);
        String s = HttpClientUtil.doPostJson(realVariablesUrl, headers, par);
        log.info("IOServer实时数据回写,返回参数{}", s);
        IOServerResultVo ioServerResultVo = JSONObject.parseObject(s, IOServerResultVo.class);
        if (null != ioServerResultVo && 0 == ioServerResultVo.getCode()) {
            JSONObject objects = JSONObject.parseObject(JSON.toJSONString(ioServerResultVo.getData()));
//            objects.put("tag_oks", new String[]{"PosSet_FV1_1"});
            Object oks = objects.get("tag_oks");

            // 获取操作成功的指标列表,存入历史表 插入(更新)实时记录
            if (null != oks) {
                List<String> filterCode = JSON.parseArray(oks.toString(), String.class);
                // 存入IOServer监测设备操作数据记录表
                List<IOServerDataInfoVo> dataInfoVos = JSONObject.parseArray(JSON.toJSONString(codeList), IOServerDataInfoVo.class);
                // 过滤未修改成功的设备
                List<IOServerDataInfoVo> infoVos = dataInfoVos.stream().filter(i -> filterCode.contains(i.getN())).collect(Collectors.toList());
                // 存入历史表 插入(更新)实时记录
                // 获取当前实时表对应阀门变量id
                Map<String, Long> collect = monitorService.list(new QueryWrapper<ScadaMonitor>().lambda()
                        .in(ScadaMonitor::getCode, dataInfoVos.stream().map(IOServerDataInfoVo::getN)
                                .collect(Collectors.toList())).select(ScadaMonitor::getId, ScadaMonitor::getCode)).stream().collect(Collectors.toMap(ScadaMonitor::getCode, ScadaMonitor::getId, ((k, v) -> v)));
                infoVos.forEach(i -> {
                    String n = i.getN();
                    String v = i.getV();
                    if("false".equals(v)){
                        v = "0";
                    }else if("true".equals(v)){
                        v = "1";
                    }
                    Date t = new Date();
                    Scada scada = new Scada();
                    scada.setCode(n);
                    scada.setValue(v);
                    scada.setScadaTime(t);
                    scada.setUpdateTime(t);
                    ScadaMonitor scadaMonitor = new ScadaMonitor();
                    scadaMonitor.setCode(n);
                    scadaMonitor.setValue(v);
                    scadaMonitor.setScadaTime(t);
                    scadaMonitor.setUpdateTime(t);
                    ScadaIoserverHis his = new ScadaIoserverHis();
                    his.setCode(n);
                    his.setValue(v);
                    his.setOperationTime(t);
                    his.setOperationUser(SecurityUtils.getUserId());
                    scadaMapper.insert(scada);
                    if (collect.containsKey(n)) {
                        scadaMonitor.setId(collect.get(n));
                        monitorService.updateById(scadaMonitor);
                    } else {
                        monitorService.save(scadaMonitor);
                    }
                    ioserverHisService.save(his);
                });
            }
        }
//        outAuthorization(headers.get("Authorization"));
        return JSON.toJSONString(ioServerResultVo);
    }

    @Transactional(rollbackFor = Exception.class)
//    @Scheduled(cron = "${ioserver.cron.realvalue}")
    public void synRealValue() throws IOException {
        // token监测
        checkHeartbeat();
        // 请求参数封装
        Map<String, Object> parMap = new HashMap<>(12);
        // 获取设备对应所有阀门的变量code 数组形式
        List<IOServerParObjsDto> ioServerDeviceIndexList = deviceArchiveManageService.getIOServerDeviceIndexList();
        parMap.put("objs", JSON.parseArray(JSON.toJSONString(ioServerDeviceIndexList)));
        String par = JSON.toJSONString(parMap);
        log.info("IOServer同步设备实时值,请求参数{}", par);

        String s = HttpClientUtil.doPostJson(batchRealValueUrl, headers, par);
        log.info("IOServer同步设备实时值,返回参数{}", s);
        IOServerResultVo ioServerResultVo = JSONObject.parseObject(s, IOServerResultVo.class);
        if (null != ioServerResultVo && 0 == ioServerResultVo.getCode()) {
            List<IOServerDataInfoVo> dataInfoVos = JSONObject.parseArray(ioServerResultVo.getData().toString(), IOServerDataInfoVo.class);
            // 存入历史表 插入(更新)实时记录
            // 获取当前实时表对应阀门变量id
            Map<String, Long> collect = monitorService.list(new QueryWrapper<ScadaMonitor>().lambda()
                    .in(ScadaMonitor::getCode, ioServerDeviceIndexList.stream().map(IOServerParObjsDto::getN)
                            .collect(Collectors.toList())).select(ScadaMonitor::getId, ScadaMonitor::getCode)).stream().collect(Collectors.toMap(ScadaMonitor::getCode, ScadaMonitor::getId, ((k, v) -> v)));
            dataInfoVos.forEach(i -> {
                String n = i.getN();
                String v = i.getV();
                Date t = i.getT();
                Scada scada = new Scada();
                scada.setCode(n);
                scada.setValue(v);
                scada.setScadaTime(t);
                scada.setUpdateTime(new Date());
                ScadaMonitor scadaMonitor = new ScadaMonitor();
                scadaMonitor.setCode(n);
                scadaMonitor.setValue(v);
                scadaMonitor.setScadaTime(t);
                scadaMonitor.setUpdateTime(new Date());
                scadaMapper.insert(scada);
                if (collect.containsKey(n)) {
                    scadaMonitor.setId(collect.get(n));
                    monitorService.updateById(scadaMonitor);
                } else {
                    monitorService.save(scadaMonitor);
                }
            });
        }
//        outAuthorization(headers.get("Authorization"));

    }


}




