package p.studio.projectsessions;

import org.junit.jupiter.api.Test;
import p.studio.lsp.api.LspServerLifecycle;
import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerConfiguration;
import p.studio.projects.ProjectReference;
import p.studio.projectstate.ProjectLocalStudioSetup;
import p.studio.projectstate.ProjectLocalStudioSetupService;
import p.studio.projectstate.ProjectLocalStudioState;
import p.studio.projectstate.ProjectLocalStudioStateService;

import java.nio.file.Path;

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

final class StudioProjectSessionFactoryTest {
    @Test
    void openCreatesProjectSessionAndBootsLspServer() {
        final RecordingProjectLocalStudioStateService stateService = new RecordingProjectLocalStudioStateService();
        final RecordingProjectLocalStudioSetupService setupService = new RecordingProjectLocalStudioSetupService();
        final RecordingLspServerLifecycle lspServerLifecycle = new RecordingLspServerLifecycle();
        final StudioProjectSessionFactory sessionFactory = new StudioProjectSessionFactory(
                stateService,
                setupService,
                lspServerLifecycle);
        final ProjectReference projectReference = new ProjectReference(
                "Example",
                "1.0.0",
                "pbs",
                1,
                Path.of("/tmp/example"));

        final StudioProjectSession session = sessionFactory.open(projectReference);

        assertSame(projectReference, session.projectReference());
        assertSame(stateService.loadedState, session.projectLocalStudioState());
        assertSame(setupService.loadedSetup, session.projectLocalStudioSetup());
        assertEquals("IDLE", session.executionSession().snapshot().state().name());
        assertSame(projectReference, stateService.loadedProjectReference);
        assertSame(projectReference, setupService.loadedProjectReference);
        assertEquals(1, lspServerLifecycle.bootCalls);
        assertEquals(projectReference.languageId(), lspServerLifecycle.lastProjectContext.languageId());
        assertEquals(projectReference.rootPath().toAbsolutePath().normalize(), lspServerLifecycle.lastProjectContext.projectRoot());
    }

    @Test
    void openSurfacesLspBootFailure() {
        final RecordingProjectLocalStudioStateService stateService = new RecordingProjectLocalStudioStateService();
        final RecordingProjectLocalStudioSetupService setupService = new RecordingProjectLocalStudioSetupService();
        final StudioProjectSessionFactory sessionFactory = new StudioProjectSessionFactory(
                stateService,
                setupService,
                new FailingLspServerLifecycle());

        final IllegalStateException failure = assertThrows(IllegalStateException.class, () -> sessionFactory.open(new ProjectReference(
                "Example",
                "1.0.0",
                "pbs",
                1,
                Path.of("/tmp/example"))));

        assertEquals("boom", failure.getMessage());
    }

    private static final class RecordingProjectLocalStudioStateService extends ProjectLocalStudioStateService {
        private ProjectReference loadedProjectReference;
        private final ProjectLocalStudioState loadedState = new ProjectLocalStudioState(
                ProjectLocalStudioState.CURRENT_VERSION,
                ProjectLocalStudioState.ShellLayout.defaults(),
                new ProjectLocalStudioState.OpenShellState("ASSETS"));

        @Override
        public ProjectLocalStudioState load(final ProjectReference projectReference) {
            this.loadedProjectReference = projectReference;
            return loadedState;
        }
    }

    private static final class RecordingProjectLocalStudioSetupService extends ProjectLocalStudioSetupService {
        private ProjectReference loadedProjectReference;
        private final ProjectLocalStudioSetup loadedSetup = new ProjectLocalStudioSetup(
                null,
                new ProjectLocalStudioSetup.EditorIndentationSetup("spaces", 8));

        @Override
        public ProjectLocalStudioSetup load(final ProjectReference projectReference) {
            this.loadedProjectReference = projectReference;
            return loadedSetup;
        }
    }

    private static final class RecordingLspServerLifecycle implements LspServerLifecycle {
        private int bootCalls;
        private LspProjectContext lastProjectContext;

        @Override
        public void bootServer(LspServerConfiguration serverConfiguration, LspProjectContext context) {
            bootCalls++;
            lastProjectContext = context;
        }

        @Override
        public void shutdownServer(final LspProjectContext project) {
        }
    }

    private static final class FailingLspServerLifecycle implements LspServerLifecycle {
        @Override
        public void bootServer(LspServerConfiguration serverConfiguration, LspProjectContext context) {
            throw new IllegalStateException("boom");
        }

        @Override
        public void shutdownServer(final LspProjectContext project) {
        }
    }
}
