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

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.friday.base.common.response.DubboResult;
import com.friday.operation.api.distributor.dto.StandardRpaInput;
import com.friday.operation.api.keepalive.constant.smartrpa.ExecAction;
import com.friday.operation.api.keepalive.constant.smartrpa.JobState;
import com.friday.operation.api.keepalive.dto.smartrpa.ActionConfirmReq;
import com.friday.operation.api.keepalive.dto.smartrpa.ExecActionDto;
import com.friday.operation.api.keepalive.dto.smartrpa.JobActionDto;
import com.friday.tooldriver.dto.smartrpa.JobSelectFilter;
import com.friday.tooldriver.dto.smartrpa.SmartJobDto;
import com.friday.tooldriver.mapper.smartrpa.JobExecMapper;
import com.friday.tooldriver.po.smartrpa.JobPo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

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

/**
 * @Author YaoYao
 * @Date Created In 10:45 2022/12/6
 * @Description SmartRPA任务执行管理
 * @Modified By
 */
@Service
@Slf4j
public class JobExecManager {

    @Autowired
    private JobExecMapper jobExecMapper;

    //启动任务
    public DubboResult<SmartJobDto> startJob(SmartJobDto jobDto){
        try {
            jobDto.setState(Pending.getState());
            jobDto.setSyncState(false);
            JobPo jobPo = jobDto.transferToJobPo();
            jobExecMapper.insertJob(jobPo);
            jobDto.setId(jobPo.getId());
            return DubboResult.newSuccess(jobDto);
        } catch (Exception e){
            log.error(START_JOB_ERROR.getMessage(), e);
            return DubboResult.newFailure(START_JOB_ERROR.getCode(), START_JOB_ERROR.getMessage());
        }
    }

    //获取任务详情
    public DubboResult<SmartJobDto> queryJobByKey(Integer jobId){
        try {
            JobPo job = jobExecMapper.selectJobById(jobId);
            if(Objects.isNull(job)){
                return DubboResult.newFailure(QUERY_JOB_ERROR.getCode(), QUERY_JOB_ERROR.getMessage());
            }
            return DubboResult.newSuccess(job.transferToSmartJobDto());
        } catch (Exception e){
            log.error(QUERY_JOB_ERROR.toString(String.valueOf(jobId)), e);
            return DubboResult.newFailure(QUERY_JOB_ERROR.getCode(), QUERY_JOB_ERROR.getMessage());
        }
    }

    //重启任务
    public DubboResult<SmartJobDto> restartJobByKey(Integer jobId){
        DubboResult<SmartJobDto> jobResult = this.queryJobByKey(jobId);
        if(!jobResult.success()){
            return DubboResult.newFailure(jobResult.getCode(), jobResult.getMessage());
        }
        SmartJobDto oldJob = jobResult.getObject();
        SmartJobDto newJob = SmartJobDto.builder()
                .source(oldJob.getSource())
                .robotKey(oldJob.getRobotKey())
                .processReleaseKey(oldJob.getProcessReleaseKey())
                .inputArguments(oldJob.getInputArguments())
                .build();
        return this.startJob(newJob);
    }

    //终止任务
    public DubboResult stopJob(Integer jobId){
        try {
            JobPo job = jobExecMapper.selectJobById(jobId);
            if(Objects.isNull(job)){
                return DubboResult.newFailure(QUERY_JOB_ERROR.getCode(), QUERY_JOB_ERROR.getMessage());
            }
            if(!job.transferToSmartJobDto().canStop()){
                return DubboResult.newFailure(JOB_CANNOT_STOP.getCode(), JOB_CANNOT_STOP.getMessage());
            }
            if(Pending.getState().equals(job.getState())){
                jobExecMapper.updateJobById(createJobPoFromId(jobId, Terminated));
            } else {
                jobExecMapper.updateJobById(createJobPoFromId(jobId, Terminating));
            }
            return DubboResult.newSuccess();
        } catch (Exception e){
            log.error(STOP_JOB_ERROR.toString(String.valueOf(jobId)), e);
            return DubboResult.newFailure(STOP_JOB_ERROR.getCode(), STOP_JOB_ERROR.getMessage());
        }
    }

    //暂停任务
    public DubboResult pauseJob(Integer jobId){
        try {
            JobPo job = jobExecMapper.selectJobById(jobId);
            if(Objects.isNull(job)){
                return DubboResult.newFailure(QUERY_JOB_ERROR.getCode(), QUERY_JOB_ERROR.getMessage());
            }
            if(!job.transferToSmartJobDto().canPause()){
                return DubboResult.newFailure(JOB_CANNOT_PAUSE.getCode(), JOB_CANNOT_PAUSE.getMessage());
            }
            jobExecMapper.updateJobById(createJobPoFromId(jobId, Pausing));
            return DubboResult.newSuccess();
        } catch (Exception e){
            log.error(PAUSE_JOB_ERROR.toString(String.valueOf(jobId)), e);
            return DubboResult.newFailure(PAUSE_JOB_ERROR.getCode(), PAUSE_JOB_ERROR.getMessage());
        }
    }

    //恢复任务
    public DubboResult resumeJob(Integer jobId){
        try {
            JobPo job = jobExecMapper.selectJobById(jobId);
            if(Objects.isNull(job)){
                return DubboResult.newFailure(QUERY_JOB_ERROR.getCode(), QUERY_JOB_ERROR.getMessage());
            }
            if(!job.transferToSmartJobDto().canResume()){
                return DubboResult.newFailure(JOB_CANNOT_RESUME.getCode(), JOB_CANNOT_RESUME.getMessage());
            }
            jobExecMapper.updateJobById(createJobPoFromId(jobId, Resuming));
            return DubboResult.newSuccess();
        } catch (Exception e){
            log.error(RESUME_JOB_ERROR.toString(String.valueOf(jobId)), e);
            return DubboResult.newFailure(RESUME_JOB_ERROR.getCode(), RESUME_JOB_ERROR.getMessage());
        }
    }

    public void startJobConfirm(ActionConfirmReq confirm){
        JobPo job = jobExecMapper.selectJobById(Integer.parseInt(confirm.getJobId()));
        if(Objects.isNull(job)){
            return;
        }
        if(job.pending()){
            JobPo jobPo = this.createJobPoFromAction(confirm, Running);
            if(confirm.actionSuccess()){
                jobPo.setStartTime(LocalDateTime.now());
            }
            jobExecMapper.updateJobById(jobPo);
        }
    }

    public void stopJobConfirm(ActionConfirmReq confirm){
        JobPo job = jobExecMapper.selectJobById(Integer.parseInt(confirm.getJobId()));
        if(Objects.isNull(job)){
            return;
        }
        if(job.terminating()){
            JobPo jobPo = this.createJobPoFromAction(confirm, Terminated);
            if(confirm.actionSuccess()){
                jobPo.setEndTime(LocalDateTime.now());
            }
            jobExecMapper.updateJobById(jobPo);
        }
    }

    public void pauseJobConfirm(ActionConfirmReq confirm){
        JobPo job = jobExecMapper.selectJobById(Integer.parseInt(confirm.getJobId()));
        if(Objects.isNull(job)){
            return;
        }
        if(job.pausing()){
            jobExecMapper.updateJobById(createJobPoFromAction(confirm, Paused));
        }
    }

    public void resumeJobConfirm(ActionConfirmReq confirm){
        JobPo job = jobExecMapper.selectJobById(Integer.parseInt(confirm.getJobId()));
        if(Objects.isNull(job)){
            return;
        }
        if(job.resuming()){
            jobExecMapper.updateJobById(createJobPoFromAction(confirm, Running));
        }
    }

    public void jobActionOvertimeConfirm(Integer jobId, boolean syncState){
        JobPo jobToUpdate = JobPo.builder()
                .id(jobId)
                .build();
        if(syncState){
            jobToUpdate.setSyncState(false);
        }
        jobToUpdate.setState(Faulted.getState());
        jobToUpdate.setStateLastUpdateTime(LocalDateTime.now());
        jobToUpdate.setRemark(jobToUpdate.getActionByState() + " Fail:" + ACTION_CONFIRM_OVERTIME.getMessage());
        jobExecMapper.updateJobById(jobToUpdate);
    }

    public void jobRunningOvertime(Integer jobId){
        JobPo jobToUpdate = JobPo.builder()
                .id(jobId)
                .syncState(true)
                .lastSyncReqTime(LocalDateTime.now())
                .build();
        jobToUpdate.setRemark("Running overtime, waiting for state sync");
        jobExecMapper.updateJobById(jobToUpdate);
    }

    private JobPo createJobPoFromAction(ActionConfirmReq confirm, JobState state){
        return JobPo.builder()
                .id(Integer.parseInt(confirm.getJobId()))
                .state(confirm.actionSuccess() ? state.getState() : Faulted.getState())
                .stateLastUpdateTime(LocalDateTime.now())
                .remark(confirm.actionSuccess() ? null : confirm.getAction() + " Fail:" + confirm.getMsg())
                .build();
    }

    private JobPo createJobPoFromId(Integer jobId, JobState state){
        return JobPo.builder()
                .id(jobId)
                .state(state.getState())
                .stateLastUpdateTime(LocalDateTime.now())
                .build();
    }

    //心跳层更新任务状态
    public void syncJobState(String state, String jobId){
        JobPo job = jobExecMapper.selectJobById(Integer.parseInt(jobId));
        if(Objects.isNull(job)){
            return;
        }
        if(job.pending() && Running.getState().equals(state)){
            jobExecMapper.updateJobById(JobPo.builder()
                    .id(job.getId())
                    .state(state)
                    .startTime(LocalDateTime.now())
                    .stateLastUpdateTime(LocalDateTime.now())
                    .build());
        }
    }

    //执行动作确认更新任务状态
    public void syncJobState(ActionConfirmReq confirmReq){
        JobPo job = jobExecMapper.selectJobById(Integer.parseInt(confirmReq.getJobId()));
        if(Objects.isNull(job)){
            return;
        }
        JobPo updateJob = JobPo.builder()
                .id(job.getId())
                .state(confirmReq.getJobState())
                .stateLastUpdateTime(LocalDateTime.now())
                .build();
        updateJob.setSyncState(false);
        if(Objects.isNull(job.getStartTime()) && StringUtils.isNotEmpty(confirmReq.getStartTime())){
            updateJob.setStartTime(LocalDateTime.parse(confirmReq.getStartTime().replace(" ", "T")));
        }
        if(StringUtils.isEmpty(confirmReq.getEndTime())){
            if(confirmReq.confirmJobEnded() && (!job.ended() && job.started() || Objects.isNull(job.getEndTime()))){
                updateJob.setEndTime(LocalDateTime.now());
            }
        } else if(Objects.nonNull(job.getStartTime())){
            updateJob.setEndTime(LocalDateTime.parse(confirmReq.getEndTime().replace(" ", "T")));
        }
        if(!confirmReq.actionSuccess() && StringUtils.isNotEmpty(confirmReq.getMsg())){
            String confirmMsg = String.join(":", LocalDateTime.now().toString(), confirmReq.getMsg());
            String remark = StringUtils.isEmpty(job.getRemark()) ? confirmMsg : String.join("//", job.getRemark(), confirmMsg);
            updateJob.setRemark(remark);
        }
        jobExecMapper.updateJobById(updateJob);
    }

    //获取待执行任务指令（启动终止暂停继续）
    public List<ExecActionDto> getJobExecActions(String robotKey){
        List<JobPo> jobsToSyncState = this.selectJobsToExecute(robotKey);
        return jobsToSyncState.stream().map(this::transferJobPoToAction).collect(Collectors.toList());
    }

    private JobActionDto transferJobPoToAction(JobPo jobPo){
        JobActionDto jobAction = new JobActionDto();
        jobAction.setAction(this.transJobStateToAction(jobPo.getState()));
        jobAction.setJobId(String.valueOf(jobPo.getId()));
        if(ExecAction.Start.getAction().equals(jobAction.getAction())){
            StandardRpaInput rpaInput = JSON.parseObject(jobPo.getInputArguments(), StandardRpaInput.class);
            jobAction.setJobInputArguments(rpaInput);
        }
        return jobAction;
    }

    //获取待更新状态任务指令
    public List<ExecActionDto> getJobStateSyncActions(String robotKey){
        List<JobPo> jobsToSyncState = this.selectJobsToSyncState(robotKey);
        return jobsToSyncState.stream().map(jobPo -> ExecActionDto.builder()
                .action(ExecAction.UpdateJobState.getAction())
                .jobId(String.valueOf(jobPo.getId()))
                .build()).collect(Collectors.toList());
    }

    public List<JobPo> selectJobsToSyncState(String robotKey){
        JobSelectFilter filter = JobSelectFilter.builder()
                .robotKey(robotKey)
                .syncState(true)
                .createTimeQueryStart(LocalDateTime.now().minusMonths(1))
                .valid(true)
                .build();
        return jobExecMapper.selectJobs(filter);
    }

    public List<JobPo> selectJobsToExecute(String robotKey){
        JobSelectFilter filter = JobSelectFilter.builder()
                .robotKey(robotKey)
                .stateList(Lists.newArrayList(Pausing.getState(), Pending.getState(), Resuming.getState(), Terminating.getState()))
                .createTimeQueryStart(LocalDateTime.now().minusDays(7))
                .valid(true)
                .build();
        return jobExecMapper.selectJobs(filter);
    }

    public List<JobPo> selectRunningJobs(String robotKey){
        JobSelectFilter filter = JobSelectFilter.builder()
                .robotKey(robotKey)
                .stateList(Lists.newArrayList(Running.getState()))
                .createTimeQueryStart(LocalDateTime.now().minusDays(7))
                .valid(true)
                .build();
        return jobExecMapper.selectJobs(filter);
    }

    public String transJobStateToAction(String state){
        if(Pending.getState().equals(state)){
            return ExecAction.Start.getAction();
        } else if(Terminating.getState().equals(state)){
            return ExecAction.Terminate.getAction();
        } else if(Pausing.getState().equals(state)){
            return ExecAction.Pause.getAction();
        } else if(Resuming.getState().equals(state)){
            return ExecAction.Resume.getAction();
        }
        return Invalid.getState();
    }

}
