package com.njzscloud.common.sichen.service;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njzscloud.common.core.ex.Exceptions;
import com.njzscloud.common.core.tuple.Tuple2;
import com.njzscloud.common.core.utils.CallerUtil;
import com.njzscloud.common.mp.support.PageParam;
import com.njzscloud.common.mp.support.PageResult;
import com.njzscloud.common.sichen.config.TaskProperties;
import com.njzscloud.common.sichen.contant.TaskLogLevel;
import com.njzscloud.common.sichen.contant.TaskStatus;
import com.njzscloud.common.sichen.mapper.TaskScheduleRecodeMapper;
import com.njzscloud.common.sichen.pojo.entity.TaskExecuteLogEntity;
import com.njzscloud.common.sichen.pojo.entity.TaskScheduleRecodeEntity;
import com.njzscloud.common.sichen.pojo.param.SearchTaskScheduleRecodeParam;
import com.njzscloud.common.sichen.support.Task;
import com.njzscloud.common.sichen.support.TaskHandle;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

@Slf4j
public class TaskScheduleRecodeService extends ServiceImpl<TaskScheduleRecodeMapper, TaskScheduleRecodeEntity> implements IService<TaskScheduleRecodeEntity> {
    public final LinkedBlockingQueue<Runnable> logQueue;
    private final TaskExecuteLogService taskExecuteLogService;
    private final TaskProperties taskProperties;
    private final Thread recodeLogThread;

    /**
     * 新增
     */
    public void add(TaskScheduleRecodeEntity taskScheduleRecodeEntity) {
        this.save(taskScheduleRecodeEntity);
    }

    /**
     * 修改
     */
    public void modify(TaskScheduleRecodeEntity taskScheduleRecodeEntity) {
        this.updateById(taskScheduleRecodeEntity);
    }

    /**
     * 删除
     */
    @Transactional(rollbackFor = Exception.class)
    public void del(List<Long> ids) {
        this.removeBatchByIds(ids);
        taskExecuteLogService.remove(Wrappers.<TaskExecuteLogEntity>lambdaQuery().in(TaskExecuteLogEntity::getScheduleId, ids));
    }

    /**
     * 详情
     */
    public TaskScheduleRecodeEntity detail(Long id) {
        return this.getById(id);
    }

    /**
     * 分页查询
     */
    public PageResult<TaskScheduleRecodeEntity> paging(PageParam pageParam, SearchTaskScheduleRecodeParam searchTaskScheduleRecodeParam) {
        Long taskId = searchTaskScheduleRecodeParam.getTaskId();
        LocalDateTime startTime = searchTaskScheduleRecodeParam.getStartTime();
        LocalDateTime endTime = searchTaskScheduleRecodeParam.getEndTime();
        TaskStatus taskStatus = searchTaskScheduleRecodeParam.getTaskStatus();
        Boolean manually = searchTaskScheduleRecodeParam.getManually();
        Assert.notNull(taskId, () -> Exceptions.clierr("未指定任务"));
        return PageResult.of(this.page(pageParam.toPage(), Wrappers.<TaskScheduleRecodeEntity>lambdaQuery()
            .eq(TaskScheduleRecodeEntity::getTaskId, taskId)
            .ge(startTime != null, TaskScheduleRecodeEntity::getScheduleTime, startTime)
            .le(endTime != null, TaskScheduleRecodeEntity::getScheduleTime, endTime)
            .eq(taskStatus != null, TaskScheduleRecodeEntity::getTaskStatus, taskStatus)
            .eq(manually != null, TaskScheduleRecodeEntity::getManually, manually)
        ));
    }

    public TaskScheduleRecodeService(TaskProperties taskProperties, TaskExecuteLogService taskExecuteLogService) {
        this.taskProperties = taskProperties;
        this.taskExecuteLogService = taskExecuteLogService;
        logQueue = new LinkedBlockingQueue<>(Integer.MAX_VALUE);
        recodeLogThread = Thread.ofVirtual()
            .name("任务日志记录")
            .start(this::recodeLog);
        TaskProperties.Logger logger = taskProperties.getLogger();
        if (!logger.isEnable()) {
            log.info("任务日志未启用");
        } else {
            log.info("全局任务日志等级为：{}", logger.getLogLevel());
        }
    }

    private void recodeLog() {
        while (true) {
            try {
                logQueue.take().run();
            } catch (InterruptedException e) {
                log.info("线程中断，日志记录停止");
                break;
            } catch (Exception e) {
                log.error("日志记录失败", e);
            }
        }
    }

    public void stop() {
        ThreadUtil.interrupt(recodeLogThread, true);
    }

    public void schedule(TaskHandle taskHandle) {
        logQueue.offer(() -> this.save(new TaskScheduleRecodeEntity()
            .setId(taskHandle.getScheduleId())
            .setTaskId(taskHandle.getTaskId())
            .setTaskName(taskHandle.getTaskName())
            .setMemo(taskHandle.getMemo())
            .setFn(taskHandle.getFn())
            .setScheduleType(taskHandle.getScheduleType())
            .setScheduleConf(taskHandle.getScheduleConf())
            .setScheduleTime(taskHandle.getScheduleTime())
            .setTaskStatus(taskHandle.getTaskStatus())
            .setBuiltin(taskHandle.isBuiltin())
            .setManually(taskHandle.isManually())));
    }

    public void pending(TaskHandle taskHandle) {
        logQueue.offer(() -> this.updateById(new TaskScheduleRecodeEntity()
            .setId(taskHandle.getScheduleId())
            .setTaskStatus(taskHandle.getTaskStatus())));
    }

    public void start(TaskHandle taskHandle) {
        logQueue.offer(() -> this.updateById(new TaskScheduleRecodeEntity()
            .setId(taskHandle.getScheduleId())
            .setStartTime(taskHandle.getStartTime())
            .setTaskStatus(taskHandle.getTaskStatus())));
    }

    public void end(TaskHandle taskHandle) {
        logQueue.offer(() -> this.updateById(new TaskScheduleRecodeEntity()
            .setId(taskHandle.getScheduleId())
            .setEndTime(taskHandle.getEndTime())
            .setTaskStatus(taskHandle.getTaskStatus())));
    }

    public void log(TaskHandle taskHandle, TaskLogLevel logLevel, Throwable throwable, String msg, Object... params) {
        TaskProperties.Logger logger = taskProperties.getLogger();
        if (!logger.isEnable()) return;
        TaskLogLevel globalLogLevel = logger.getLogLevel();
        if (logLevel.ordinal() < globalLogLevel.ordinal()) return;
        LocalDateTime logTime = LocalDateTime.now();

        logQueue.offer(() -> {
            Integer lineNumber = null;
            String place = "";
            String err = "";
            if (throwable != null) {
                Tuple2<String, CallerUtil.Caller> tuple2 = CallerUtil.printStackTrace(throwable);
                err = tuple2.get_0();
                CallerUtil.Caller caller = tuple2.get_1();
                String className = caller.className();
                String methodName = caller.methodName();
                lineNumber = caller.lineNumber();
                place = className + "#" + methodName;

            }
            taskExecuteLogService.save(new TaskExecuteLogEntity()
                .setScheduleId(taskHandle.getScheduleId())
                .setLogLevel(logLevel)
                .setMsg(StrUtil.format(msg, params))
                .setPlace(place)
                .setLine(lineNumber)
                .setErr(err)
                .setLogTime(logTime));
        });
    }

    @Task(id = 1, name = "日志清理", logLevel = TaskLogLevel.INFO, cron = "1 0 0 * * ?")
    public void clearLog() {
        TaskHandle.info("执行日志清理任务");
        TaskProperties.Logger logger = taskProperties.getLogger();
        int history = logger.getHistory();
        if (history < 1) history = 1;
        DateTime historyTime = DateUtil.beginOfDay(DateUtil.offsetDay(new DateTime(), -history));
        TaskHandle.info("删除 {} 之前的日志", historyTime);
        taskExecuteLogService.remove(Wrappers.lambdaQuery(TaskExecuteLogEntity.class).lt(TaskExecuteLogEntity::getLogTime, historyTime));
        TaskHandle.info("删除日志清理完成");
    }
}
