package com.sonic.common.client;

import com.gamers.bs.client.util.ParameterFormatter;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import lombok.*;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

public interface JobmanClient {

    /**
     * 执行Job. 并使用{@code lockPolicy}的锁策略.
     *
     * @param jobName
     * @param lockPolicy 锁策略, {@link JobmanClient.LockPolicy}
     * @param runner     任务逻辑
     */
    void run(String jobName, LockPolicy lockPolicy, Function<JobContext, JobResult> runner);

    /**
     * 执行Job. 优先使用globalLockPolicy, 没有情况下默认使用redis的锁策略. 并将锁持有{@code lockDurationSeconds}秒. 在该时间内都将由本实例执行job
     *
     * @param jobName
     * @param lockDurationSeconds 锁持有时间, 在持有时间内job由该实例调度. 单位秒
     * @param runner              任务逻辑
     */
    void run(String jobName, Long lockDurationSeconds, Function<JobContext, JobResult> runner);

    /**
     * 执行job, 优先使用globalLockPolicy, 没有情况下默认使用redis的锁策略. 默认持有锁5分钟
     *
     * @param jobName
     * @param runner
     */
    default void run(String jobName, Function<JobContext, JobResult> runner) {
        run(jobName, TimeUnit.MINUTES.toSeconds(5), runner);
    }

    /**
     * 用于判断 @{code dateTime} 是否满足 @{code cornExpression}的执行条件
     *
     * @param cornExpression
     * @return
     */
    boolean isCronSatisfied(String cornExpression, LocalDateTime dateTime);

    @Slf4j
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString(exclude = "logs")
    class JobContext {
        String jobName;
        @Builder.Default
        List<String> logs = Lists.newArrayList();
        LocalDateTime startTime;
        LocalDateTime endTime;

        public void log(String message, Object... objects) {
            String logText = objects == null ? message : ParameterFormatter.format(message, objects);
            if (objects != null && objects[objects.length - 1] instanceof Throwable) {
                logText += "\n" + Throwables.getStackTraceAsString((Throwable) objects[objects.length - 1]);
                log.error("=== running job ===, jobName = {}, log = {}", jobName, logText);
            } else {
                log.info("=== running job ===, jobName = {}, log = {}", jobName, logText);
            }

            logs.add(logText);
        }

        public void log(String message) {
            log(message, null);
        }
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    class JobResult {
        @Builder.Default
        JobStatus jobStatus = JobStatus.SUCCESS;
        @Builder.Default
        Integer loadCount = 0;
        Map<String, ?> ext;

        public static JobResult success(Integer loadCount) {
            return JobResult.builder()
                    .loadCount(loadCount)
                    .jobStatus(loadCount > 0 ? JobStatus.SUCCESS : JobStatus.EMPTY)
                    .build();
        }

        public static JobResult fail() {
            return JobResult.builder().jobStatus(JobStatus.FAIL).build();
        }

        public static JobResult empty() {
            return JobResult.builder().jobStatus(JobStatus.EMPTY).build();
        }
    }

    @Getter
    @AllArgsConstructor
    enum JobStatus {
        SUCCESS,
        FAIL,
        //如果没有数据更新, 状态设置为空
        EMPTY,
        ;
    }

    interface LockPolicy {
        /**
         * 尝试获得锁, 返回true则可以获得锁并执行业务逻辑. false不会继续执行业务逻辑
         * 调用者可以实现该接口以自定义策略
         *
         * @return
         */
        boolean tryLock();

        /**
         * 容器销毁时回调方法. 用于清理LockPolicy的资源.
         */
        default void destroy() {
        }
    }
}
