package com.livingworld.core.simulation;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.livingworld.config.SimulationConfig;
import com.livingworld.regions.RegionCoordinate;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;

class SchedulerBudgetTest {

    @Test
    void oneCycleReturnsAtMostConfiguredRegionBudget() {
        SimulationScheduler scheduler = scheduler(50, 100);
        for (int index = 0; index < 500; index++) {
            scheduler.queueRegion(job(index, index % 10));
        }

        List<RegionUpdateJob> jobs = scheduler.pollJobsForCycle();

        assertEquals(50, jobs.size());
        assertEquals(450, scheduler.getQueuedJobCount());
    }

    @Test
    void higherPriorityJobsAreReturnedFirst() {
        SimulationScheduler scheduler = scheduler(3, 100);
        scheduler.queueRegion(job(0, 1));
        scheduler.queueRegion(job(1, 20));
        scheduler.queueRegion(job(2, 5));
        scheduler.queueRegion(job(3, 10));

        assertEquals(
                List.of(20, 10, 5),
                scheduler.pollJobsForCycle().stream()
                        .map(RegionUpdateJob::priority)
                        .toList());
    }

    @Test
    void jobsOutsideCurrentBudgetRemainQueued() {
        SimulationScheduler scheduler = scheduler(2, 100);
        scheduler.queueRegion(job(0, 3));
        scheduler.queueRegion(job(1, 2));
        scheduler.queueRegion(job(2, 1));

        scheduler.pollJobsForCycle();

        assertEquals(1, scheduler.getQueuedJobCount());
        assertEquals(1, scheduler.pollJobsForCycle().getFirst().priority());
    }

    @Test
    void simulationIntervalStartsAfterTheConfiguredNumberOfTicks() {
        SimulationScheduler scheduler = scheduler(50, 3);

        assertFalse(scheduler.shouldRunSimulationCycle());
        scheduler.onMinecraftTick();
        assertFalse(scheduler.shouldRunSimulationCycle());
        scheduler.onMinecraftTick();
        assertFalse(scheduler.shouldRunSimulationCycle());
        scheduler.onMinecraftTick();
        assertTrue(scheduler.shouldRunSimulationCycle());
        scheduler.onMinecraftTick();
        assertFalse(scheduler.shouldRunSimulationCycle());
    }

    private static SimulationScheduler scheduler(int maxRegionsPerCycle, int intervalTicks) {
        SimulationConfig config = new SimulationConfig();
        config.setMaxRegionsPerCycle(maxRegionsPerCycle);
        config.setSimulationIntervalTicks(intervalTicks);
        return new SimulationScheduler(config);
    }

    private static RegionUpdateJob job(int coordinateX, int priority) {
        return new RegionUpdateJob(
                new RegionCoordinate("minecraft:overworld", coordinateX, 0),
                priority,
                0,
                Set.of(),
                UpdateReason.NORMAL_ROLLING_UPDATE);
    }
}
