package com.friday.tooldriver.service.rpa.supplier.smartrpa;

import com.friday.base.common.response.DubboResult;
import com.friday.operation.api.keepalive.constant.smartrpa.ExecAction;
import com.friday.operation.api.keepalive.constant.smartrpa.LicenseState;
import com.friday.operation.api.keepalive.dto.smartrpa.ExecActionDto;
import com.friday.operation.api.keepalive.dto.smartrpa.RobotActionDto;
import com.friday.operation.api.tooldriver.constant.DriverConstant;
import com.friday.operation.api.tooldriver.dto.common.DriverBase;
import com.friday.tooldriver.dto.smartrpa.RobotCacheStateDto;
import com.friday.tooldriver.dto.smartrpa.SmartRobotDto;
import com.friday.tooldriver.mapper.smartrpa.RobotMapper;
import com.friday.tooldriver.mapper.taieoc.RobotGroupRelationMapper;
import com.friday.tooldriver.po.smartrpa.RobotPo;
import com.friday.tooldriver.po.taieoc.RobotGroupRelationPo;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.friday.operation.api.keepalive.constant.smartrpa.JobState.Faulted;
import static com.friday.operation.api.keepalive.constant.smartrpa.RobotState.*;
import static com.friday.tooldriver.constants.smartrpa.SmartRpaErrorCode.*;

import java.lang.Exception;
import java.util.stream.Collectors;

/**
 * @Author YaoYao
 * @Date Created In 13:50 2022/12/6
 * @Description SmartRPA机器管理
 * @Modified By
 */
@Service
@Slf4j
public class RobotManager {

    @Autowired
    private RobotMapper robotMapper;

    @Autowired
    private RobotGroupRelationMapper robotGroupRelationMapper;
    @Autowired(required = false)
    private RedissonClient redissonClient;

    @Value("${spring.profiles.active}")
    private String env;
    @Value("${robot.unresponsive.max.time}")
    private Integer unresponsiveMaxTime;

    /**
     * 创建机器人
     * key = machineCode + machineUser
     */
    public DubboResult<SmartRobotDto> createRobot(SmartRobotDto robotDto){
        if(StringUtils.isAnyEmpty(robotDto.getMachineCode(), robotDto.getMachineLoginUser(), String.valueOf(robotDto.getOcTenantId()))){
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, "非法入参:缺少中控租户ID或机器码和用户名");
        }
        if(StringUtils.isNotEmpty(robotDto.getRobotKey()) && !robotDto.getRobotKey().equals(robotDto.getMachineCode() + robotDto.getMachineLoginUser())){
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, "机器键值不合法");
        }
        if(StringUtils.isEmpty(robotDto.getRobotKey())){
            robotDto.setRobotKey(robotDto.getMachineCode() + robotDto.getMachineLoginUser());
        }
        try {
            RobotPo robot = robotMapper.selectRobotByKey(robotDto.getRobotKey());
            if(Objects.nonNull(robot)){
                return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, "已存在相同键值的机器人");
            }
            RobotPo robotPo = RobotPo.createFromSmartRobotDto(robotDto);
            robotPo.setRobotState(Initialed.getState());
//            robotPo.setLicenseState(LicenseState.Enabled.getState());
            robotMapper.insertRobot(robotPo);
            robotDto.setId(robotPo.getId());
            return DubboResult.newSuccess(robotDto);
        } catch (Exception e){
            log.error("创建SmartRpa工位{}异常", robotDto.getMachineCode() + robotDto.getMachineLoginUser(), e);
            return DubboResult.newFailure(CREATE_ROBOT_ERROR.getCode(), CREATE_ROBOT_ERROR.getMessage());
        }
    }

    /**
     * 根据key删除机器人
     */
    public DubboResult deleteRobotByKey(String key){
        try {
//            20240628 删除工位对应删除工位license
            robotMapper.deleteRobotLicenseByKey(key);
            robotMapper.deleteRobotByKey(key);
            return DubboResult.newSuccess();
        } catch (Exception e){
            log.error("删除SmartRpa工位{}异常", key, e);
            return DubboResult.newFailure(DELETE_ROBOT_ERROR.getCode(), DELETE_ROBOT_ERROR.getMessage());
        }
    }

    /**
     * 根据key查询机器人
     */
    public DubboResult<SmartRobotDto> queryRobotByKey(String key){
        try {
            RobotPo robot = robotMapper.selectRobotByKey(key);
            if(Objects.isNull(robot)){
                return DubboResult.newFailure(ROBOT_NOT_EXIST.getCode(), ROBOT_NOT_EXIST.getMessage());
            }
            return DubboResult.newSuccess(robot.transferToSmartRobotDto());
        } catch (Exception e){
            log.error(QUERY_JOB_ERROR.getMessage(), e);
            return DubboResult.newFailure(QUERY_JOB_ERROR.getCode(), QUERY_JOB_ERROR.getMessage());
        }
    }

    /**
     * 收发许可
     */
    public DubboResult distributeLicense(String robotKey, boolean enable){
        try {
            DubboResult<SmartRobotDto> robotResult = this.queryRobotByKey(robotKey);
            if(!robotResult.success()){
                return DubboResult.newFailure(robotResult.getCode(), robotResult.getMessage());
            }
            SmartRobotDto smartRobot = robotResult.getObject();
            if((enable && !LicenseState.Disabled.getState().equals(smartRobot.getLicenseState())) || (!enable && !LicenseState.Enabled.getState().equals(smartRobot.getLicenseState()))){
                return DubboResult.newFailure(INVALID_DISTRIBUTE_LICENSE.getCode(), INVALID_DISTRIBUTE_LICENSE.getMessage());
            }
            RobotPo robotPo = RobotPo.builder()
                    .robotKey(robotKey)
                    .licenseLastUpdateTime(LocalDateTime.now())
                    .build();
            if(enable){
                robotPo.setLicenseState(LicenseState.ToggleOn.getState());
            } else {
                robotPo.setLicenseState(LicenseState.ToggleOff.getState());
            }
            robotMapper.updateRobotByKey(robotPo);
            return DubboResult.newSuccess();
        } catch (Exception e){
            log.error("SmartRpa:{}收发许可{}异常", robotKey, enable, e);
            return DubboResult.newFailure(DISTRIBUTE_LICENSE_ERROR.getCode(), DISTRIBUTE_LICENSE_ERROR.getMessage());
        }
    }

    public void distributeLicenseConfirm(String robotKey, boolean enable){
        RobotPo robotPo = RobotPo.builder()
                .robotKey(robotKey)
                .build();
        if(enable){
            robotPo.setLicenseState(LicenseState.Enabled.getState());
        } else {
            robotPo.setLicenseState(LicenseState.Disabled.getState());
        }
        robotMapper.updateRobotByKey(robotPo);
    }

    //todo 只允许更新为Maintenance和Available状态
    public void setRobotState(String robotKey, String newState){
        RobotPo robotPo = RobotPo.builder()
                .robotKey(robotKey)
                .robotStateToChange(newState)
                .build();
        robotMapper.updateRobotByKey(robotPo);
    }

    //todo 手动更新机器状态允许的初始状态
    public void setRobotStateConfirm(String robotKey, String newState){
        this.syncRobotState(robotKey, newState);
    }


    //更新robot状态：缓存+持久化
    public void syncRobotState(String state, String robotKey){
        try{
            if(robotStateValid(state) && !state.equals(this.getRobotState(robotKey))){
                RobotPo robot = RobotPo.builder()
                        .robotKey(robotKey)
                        .robotState(state)
                        .build();
                //todo 事务控制
                robotMapper.updateRobotByKey(robot);
                updateRobotStateInCache(robotKey, state);
            }
        } catch (Exception e){
            log.error(HEARTBEAT_SYNC_ERROR.toString(), e);
        }
    }

    private boolean robotStateValid(String state){
        return StringUtils.isNotEmpty(state) && (Available.getState().equals(state) || Busy.getState().equals(state) || Maintenance.getState().equals(state));
    }

    //获取许可收发指令
    public ExecActionDto getLicenseAction(String robotKey){
        ExecActionDto result = null;
        try {
            DubboResult<SmartRobotDto> smartRobotResult = this.queryRobotByKey(robotKey);
            if(smartRobotResult.success()){
                SmartRobotDto robot = smartRobotResult.getObject();
                if(LicenseState.ToggleOn.getState().equals(robot.getLicenseState())){
                    result = ExecActionDto.builder().action(ExecAction.EnableLicense.getAction()).build();
                } else if(LicenseState.ToggleOff.getState().equals(robot.getLicenseState())){
                    result = ExecActionDto.builder().action(ExecAction.DisableLicense.getAction()).build();
                }
            }
        } catch (Exception e) {
            log.error(GET_LICENSE_ACTION_ERROR.toString(robotKey), e);
        }
        return result;
    }

    //获取机器设置状态指令
    public ExecActionDto getRobotStateSetAction(String robotKey){
        RobotActionDto result = null;
        try {
            DubboResult<SmartRobotDto> smartRobotResult = this.queryRobotByKey(robotKey);
            if(smartRobotResult.success()){
                SmartRobotDto robot = smartRobotResult.getObject();
                if(Maintenance.getState().equals(robot.getRobotStateToChange()) || Available.getState().equals(robot.getRobotStateToChange())){
                    result = new RobotActionDto();
                    result.setAction(ExecAction.SetMachineState.getAction());
                    result.setMachineState(robot.getRobotStateToChange());
                }
            }
        } catch (Exception e) {
            log.error(GET_ROBOT_STATE_SET_ACTION_ERROR.toString(robotKey), e);
        }
        return result;
    }

    public void robotActionOvertimeConfirm(String machineCode, String machineUser, String licenseState, String robotState){
        RobotPo robotToUpdate = RobotPo.builder()
                .robotKey(machineCode + machineUser)
                .build();
        if(StringUtils.isNotEmpty(licenseState)){
            robotToUpdate.setLicenseState(licenseState + Faulted.getState());
            robotToUpdate.setLicenseLastUpdateTime(LocalDateTime.now());
        }
        if(StringUtils.isNotEmpty(robotState)){
            robotToUpdate.setRobotStateToChange(robotState + Faulted.getState());
            robotToUpdate.setRobotStateLastUpdateTime(LocalDateTime.now());
        }
        robotToUpdate.setRemark(robotToUpdate.getActionByState() + " Fail:" + ACTION_CONFIRM_OVERTIME.getMessage());
        robotMapper.updateRobotByKey(robotToUpdate);
    }

    public void updateRobotUnresponsiveTime(String robotKey, boolean reset){
        RobotPo robotPo = robotMapper.selectRobotByKey(robotKey);
        if(Objects.isNull(robotPo)){
            return;
        }
        int currentUnresponsiveTime = Objects.isNull(robotPo.getUnresponsiveTime()) ? 0 : robotPo.getUnresponsiveTime();
        int unresponsiveTime = reset ? 0 : currentUnresponsiveTime + 1;
        RobotPo robotToUpdate = RobotPo.builder()
                .robotKey(robotKey)
                .unresponsiveTime(unresponsiveTime)
                .build();
        robotMapper.updateRobotByKey(robotToUpdate);
    }

    public boolean robotUnresponsive(String robotKey){
        RobotPo robotPo = robotMapper.selectRobotByKey(robotKey);
        return Objects.nonNull(robotPo) && Objects.nonNull(robotPo.getUnresponsiveTime()) && robotPo.getUnresponsiveTime() >= unresponsiveMaxTime;
    }

    private void updateRobotStateInCache(String robotKey, String newState){
        RMap<String, String> smartRobotMap = this.redissonClient.getMap(getRobotStateCacheKey());
        if (smartRobotMap.containsKey(robotKey)) {
            smartRobotMap.put(robotKey, newState);
            smartRobotMap.expire(DriverConstant.SmartRobotStateCacheExpiration, TimeUnit.DAYS);
        }
    }

    private String getRobotState(String robotKey) {
        RMap<String, String> smartRobotMap = this.redissonClient.getMap(getRobotStateCacheKey());
        String robotState = Invalid.getState();
        if (!smartRobotMap.containsKey(robotKey)) {
            DubboResult<SmartRobotDto> smartRobotResult = this.queryRobotByKey(robotKey);
            if(smartRobotResult.success() && Objects.nonNull(smartRobotResult.getObject().getRobotState())){
                robotState = smartRobotResult.getObject().getRobotState();
            }
            smartRobotMap.put(robotKey, robotState);
            smartRobotMap.expire(DriverConstant.SmartRobotStateCacheExpiration, TimeUnit.DAYS);
        } else{
            robotState = smartRobotMap.get(robotKey);
        }
        return robotState;
    }

    //同步缓存和数据库机器人状态
    @Scheduled(cron = "0/30 * * * * ?")
    public void syncRobotCacheStateToDB(){
        List<RobotCacheStateDto> cache = this.getRobotStateCache().stream().filter(s -> "Not In Cache".equals(s.getStateInCache())).collect(Collectors.toList());
        for(RobotCacheStateDto state : cache){
            RobotPo robotToUpdate = RobotPo.builder()
                    .robotKey(state.getRobotKey())
                    .robotState(Disconnected.getState())
                    .build();
            robotMapper.updateRobotByKey(robotToUpdate);
        }
        clearRobotStateCache(true, null);
    }

    //获取缓存中鹊桥机器人状态
    public List<RobotCacheStateDto> getRobotStateCache(){
        List<RobotCacheStateDto> results = Lists.newArrayList();
        List<String> robotKeys = this.getAllRobotKeys();
        for(String key : robotKeys){
            results.add(RobotCacheStateDto.builder()
                    .robotKey(key)
                    .stateInCache(this.getRobotStateCache(key))
                    .build());
        }
        return results;
    }

    public List<String> getAllRobotKeys(){
        return robotMapper.selectValidRobotKey();
    }

    private String getRobotStateCache(String robotKey){
        RMap<String, String> smartRobotMap = this.redissonClient.getMap(getRobotStateCacheKey());
        if(smartRobotMap.containsKey(robotKey)){
            return smartRobotMap.get(robotKey);
        }
        return "Not In Cache";
    }

    public String clearRobotStateCache(boolean all, String robotKey){
        RMap<String, String> smartRobotMap = this.redissonClient.getMap(getRobotStateCacheKey());
        if(all){
            smartRobotMap.clear();
            return "OK";
        }
        if(smartRobotMap.containsKey(robotKey)){
            return smartRobotMap.remove(robotKey);
        }
        return "Not In Cache";
    }

    private String getRobotStateCacheKey(){
        String envTag = env;
        if("local".equals(envTag)){
            envTag = "dev";
        }
        return DriverConstant.SmartRpaRobotStateCache + envTag;
    }

    public DubboResult assignRobotToGroup(String robotKey, String groupKey) {
        DubboResult<SmartRobotDto> smartRobotDtoDubboResult = queryRobotByKey(robotKey);
        if(smartRobotDtoDubboResult.success()){
            robotGroupRelationMapper.insertSelective(RobotGroupRelationPo.builder()
                    .groupId(Long.parseLong(groupKey))
                    .robotId(smartRobotDtoDubboResult.getObject().getId())
                    .isValid(true)
                    .createTime(new Date())
                    .updateTime(new Date())
                    .build()
            );
            return DubboResult.newSuccess();
        }
        return smartRobotDtoDubboResult;
    }

    public DubboResult removeRobotFromGroup(String robotKey, String groupKey) {
        SmartRobotDto robotDto = null;
        if(StringUtils.isNotEmpty(robotKey)){
            DubboResult<SmartRobotDto> smartRobotDtoDubboResult = queryRobotByKey(robotKey);
            if(!smartRobotDtoDubboResult.success()){
                return smartRobotDtoDubboResult;
            }
            robotDto = smartRobotDtoDubboResult.getObject();
        }
        robotGroupRelationMapper.deleteByGroupId(ObjectUtils.isEmpty(robotDto)?null:robotDto.getId(), Long.parseLong(groupKey));
        return DubboResult.newSuccess();
    }

    public boolean checkRobotPermission(Long groupId, Long robotId){
        return !CollectionUtils.isEmpty(robotGroupRelationMapper.selectByRobotAndGroup(robotId, groupId));
    }

}
