package com.easyagents.scheduler.quartz;

import com.easyagents.scheduler.ConcurrencyPolicy;
import com.easyagents.scheduler.MisfirePolicy;
import com.easyagents.scheduler.OnceSchedulePlan;
import com.easyagents.scheduler.ScheduleDefinition;
import com.easyagents.scheduler.ScheduleHandler;
import com.easyagents.scheduler.ScheduleId;
import com.easyagents.scheduler.ScheduleRefireException;
import org.junit.Test;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;

import java.lang.reflect.Proxy;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/** {@link QuartzRuntime} 失败控制语义测试。 */
public class QuartzRuntimeTest {

    /** JobStore 无法保存延迟触发时不得把当前 fire 当作正常完成。 */
    @Test
    public void shouldRefireInPlaceWhenPersistentRetryCannotBeStored() throws Exception {
        ScheduleDefinition definition = new ScheduleDefinition(
            new ScheduleId("test", "retry-store-failure"),
            "handler",
            new OnceSchedulePlan(Instant.parse("2099-01-01T00:00:00Z")),
            MisfirePolicy.FIRE_ONCE_NOW,
            ConcurrencyPolicy.DISALLOW,
            true,
            Map.of(),
            "retry store failure"
        );
        JobDetail job = QuartzScheduleMapper.toJobDetail(definition);
        Scheduler scheduler = proxy(Scheduler.class, (proxy, method, arguments) -> {
            if ("scheduleJob".equals(method.getName())
                    && arguments != null && arguments.length == 1
                    && arguments[0] instanceof Trigger) {
                throw new SchedulerException("job store unavailable");
            }
            if ("isShutdown".equals(method.getName())) return false;
            return defaultValue(method.getReturnType());
        });
        Date fireTime = new Date();
        JobExecutionContext context = proxy(JobExecutionContext.class,
            (proxy, method, arguments) -> switch (method.getName()) {
                case "getMergedJobDataMap" -> job.getJobDataMap();
                case "getJobDetail" -> job;
                case "getScheduler" -> scheduler;
                case "getFireTime", "getScheduledFireTime" -> fireTime;
                case "getFireInstanceId" -> "fire-1";
                case "isRecovering" -> false;
                case "getRefireCount" -> 0;
                default -> defaultValue(method.getReturnType());
            });
        QuartzRuntime runtime = new QuartzRuntime(List.of(new ScheduleHandler() {
            @Override
            public String code() {
                return "handler";
            }

            @Override
            public void execute(com.easyagents.scheduler.ScheduleFireContext context) {
                throw new ScheduleRefireException("registration unavailable",
                    new IllegalStateException("database unavailable"), 0,
                    Duration.ofMillis(1));
            }
        }), List.of());

        try {
            runtime.execute(context);
            fail("expected refire request");
        } catch (JobExecutionException exception) {
            assertTrue(exception.refireImmediately());
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> T proxy(Class<T> type, java.lang.reflect.InvocationHandler handler) {
        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type}, handler);
    }

    private static Object defaultValue(Class<?> type) {
        if (!type.isPrimitive()) return null;
        if (type == boolean.class) return false;
        if (type == byte.class) return (byte) 0;
        if (type == short.class) return (short) 0;
        if (type == int.class) return 0;
        if (type == long.class) return 0L;
        if (type == float.class) return 0F;
        if (type == double.class) return 0D;
        if (type == char.class) return '\0';
        return null;
    }
}
