package p.studio.execution;

import org.junit.jupiter.api.Test;
import p.studio.shipper.StudioShipperLogEntry;
import p.studio.shipper.StudioShipperLogSource;

import java.time.Instant;
import java.util.ArrayList;

import static org.junit.jupiter.api.Assertions.*;

final class StudioExecutionSessionServiceTest {
    @Test
    void enforcesTheMinimumExecutionStateMachine() {
        final StudioExecutionSessionService service = new StudioExecutionSessionService();

        assertEquals(StudioExecutionState.IDLE, service.snapshot().state());
        assertThrows(IllegalStateException.class, service::transitionToRunning);

        service.beginPreparationCycle();
        assertEquals(StudioExecutionState.PREPARING, service.snapshot().state());
        assertThrows(IllegalStateException.class, service::transitionToStopped);

        service.transitionToPrepareFailed();
        assertEquals(StudioExecutionState.PREPARE_FAILED, service.snapshot().state());

        service.beginPreparationCycle();
        service.transitionToConnecting();
        service.transitionToRunning();
        service.transitionToStopped();

        assertEquals(StudioExecutionState.STOPPED, service.snapshot().state());
        assertThrows(IllegalStateException.class, service::transitionToConnecting);
    }

    @Test
    void preservesSourceIdentityAndSequenceAcrossMergedLogs() {
        final StudioExecutionSessionService service = new StudioExecutionSessionService();
        final Instant now = Instant.parse("2026-04-06T10:15:30Z");

        service.beginPreparationCycle();
        service.appendShipperLog(new StudioShipperLogEntry(
                StudioShipperLogSource.BUILD,
                p.studio.shipper.StudioShipperLogLevel.INFO,
                "Compiler started",
                now));
        service.appendShipperLog(new StudioShipperLogEntry(
                StudioShipperLogSource.PACK_VALIDATION,
                p.studio.shipper.StudioShipperLogLevel.ERROR,
                "Pack validation failed",
                now.plusSeconds(1)));
        service.appendShipperLog(new StudioShipperLogEntry(
                StudioShipperLogSource.PACK,
                p.studio.shipper.StudioShipperLogLevel.INFO,
                "Pack emitted",
                now.plusSeconds(2)));
        service.appendRuntimeLog(
                StudioExecutionLogSeverity.INFO,
                "Runtime connected",
                now.plusSeconds(3));

        final var logs = service.snapshot().logs();
        assertEquals(4, logs.size());
        assertEquals(StudioExecutionLogSource.BUILD, logs.get(0).source());
        assertEquals(StudioExecutionLogSource.PACK_VALIDATION, logs.get(1).source());
        assertEquals(StudioExecutionLogSource.PACK, logs.get(2).source());
        assertEquals(StudioExecutionLogSource.RUNTIME, logs.get(3).source());
        assertEquals(1L, logs.get(0).sequence());
        assertEquals(4L, logs.get(3).sequence());
        assertEquals(now.plusSeconds(3), logs.get(3).timestamp());
    }

    @Test
    void subscribersReceiveSnapshotsOutsideWorkspaceLifecycle() {
        final StudioExecutionSessionService service = new StudioExecutionSessionService();
        final ArrayList<StudioExecutionSnapshot> snapshots = new ArrayList<>();

        final var subscription = service.subscribe(snapshots::add);
        service.beginPreparationCycle();
        service.appendShipperLog(StudioShipperLogEntry.info(StudioShipperLogSource.BUILD, "Build ok"));
        service.transitionToPrepareFailed();
        subscription.unsubscribe();

        assertFalse(snapshots.isEmpty());
        assertEquals(StudioExecutionState.IDLE, snapshots.getFirst().state());
        assertEquals(StudioExecutionState.PREPARE_FAILED, snapshots.getLast().state());
        assertTrue(snapshots.getLast().logs().stream().anyMatch(entry -> entry.source() == StudioExecutionLogSource.BUILD));
    }
}
