package p.studio.lsp.services.protocol;

import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InitializedParams;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.junit.jupiter.api.Test;
import p.studio.lsp.messages.BaselineDocumentAnalysis;
import p.studio.lsp.messages.BaselineHover;
import p.studio.lsp.messages.BaselineServerDescription;
import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.services.LanguageServiceBridge;
import p.studio.lsp.services.protocol.mapping.ProtocolMessageMapper;

import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletionException;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

class PrometeuLanguageServerTest {

    @Test
    void initializeDelegatesThroughBridgeAndMapper() {
        final RecordingBridge bridge = new RecordingBridge();
        final RecordingMapper mapper = new RecordingMapper();
        final InitializeResult expected = new InitializeResult();
        mapper.initializeResult = expected;

        final PrometeuLanguageServer server = new PrometeuLanguageServer(
                new LspProjectContext("demo", "pbs", Path.of(".")),
                bridge,
                mapper,
                new NoopTextDocumentService(),
                new PrometeuWorkspaceService());

        final InitializeParams params = new InitializeParams();
        params.setRootUri(Path.of(".").toAbsolutePath().normalize().toUri().toString());

        final InitializeResult result = server.initialize(params).join();

        assertSame(expected, result);
        assertEquals(1, bridge.describeServerCalls);
        assertSame(bridge.serverDescription, mapper.lastServerDescription);
    }

    @Test
    void initializeRejectsClientBoundToDifferentProjectRoot() {
        final RecordingBridge bridge = new RecordingBridge();
        final RecordingMapper mapper = new RecordingMapper();
        final PrometeuLanguageServer server = new PrometeuLanguageServer(
                new LspProjectContext("demo", "pbs", Path.of("/tmp/studio-project")),
                bridge,
                mapper,
                new NoopTextDocumentService(),
                new PrometeuWorkspaceService());

        final InitializeParams params = new InitializeParams();
        params.setRootUri(Path.of("/tmp/another-project").toUri().toString());

        final CompletionException failure = org.junit.jupiter.api.Assertions.assertThrows(
                CompletionException.class,
                () -> server.initialize(params).join());

        final ResponseErrorException responseError = assertInstanceOf(ResponseErrorException.class, failure.getCause());
        assertTrue(responseError.getMessage().contains("/tmp/studio-project"));
        assertEquals(0, bridge.describeServerCalls);
    }

    private static final class RecordingBridge implements LanguageServiceBridge {
        private int describeServerCalls;
        private final BaselineServerDescription serverDescription = new BaselineServerDescription("demo", "1", true);

        @Override
        public BaselineServerDescription describeServer(final LspProjectContext project) {
            describeServerCalls++;
            return serverDescription;
        }

        @Override
        public BaselineDocumentAnalysis analyzeDocument(final LspProjectContext project, final String documentUri, final String text) {
            return new BaselineDocumentAnalysis(List.of());
        }

        @Override
        public BaselineHover hover(final LspProjectContext project, final String documentUri, final int line, final int character) {
            return new BaselineHover("hover");
        }

        @Override
        public String onSave(final LspProjectContext project, final String documentUri) {
            return "saved";
        }
    }

    private static final class RecordingMapper implements ProtocolMessageMapper {
        private InitializeResult initializeResult;
        private BaselineServerDescription lastServerDescription;

        @Override
        public InitializeResult mapInitializeResult(final BaselineServerDescription description) {
            lastServerDescription = description;
            return initializeResult;
        }

        @Override
        public PublishDiagnosticsParams mapDiagnostics(final String uri, final BaselineDocumentAnalysis analysis) {
            throw new UnsupportedOperationException();
        }

        @Override
        public PublishDiagnosticsParams emptyDiagnostics(final String uri) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Hover mapHover(final BaselineHover hover) {
            throw new UnsupportedOperationException();
        }

        @Override
        public MessageParams mapInfoMessage(final String message) {
            final MessageParams params = new MessageParams();
            params.setMessage(message);
            return params;
        }

        @Override
        public MessageParams mapErrorMessage(final String message) {
            final MessageParams params = new MessageParams();
            params.setMessage(message);
            return params;
        }

        @Override
        public MessageParams mapWarningMessage(final String message) {
            final MessageParams params = new MessageParams();
            params.setMessage(message);
            return params;
        }
    }

    private static final class NoopTextDocumentService implements TextDocumentService {
        @Override
        public void didOpen(final org.eclipse.lsp4j.DidOpenTextDocumentParams params) {
        }

        @Override
        public void didChange(final org.eclipse.lsp4j.DidChangeTextDocumentParams params) {
        }

        @Override
        public void didClose(final org.eclipse.lsp4j.DidCloseTextDocumentParams params) {
        }

        @Override
        public void didSave(final org.eclipse.lsp4j.DidSaveTextDocumentParams params) {
        }
    }
}
