package com.hosopy.actioncable;

import com.squareup.okhttp.Response;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.ws.WebSocket;
import com.squareup.okhttp.ws.WebSocketListener;
import okio.Buffer;
import okio.BufferedSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

@RunWith(JUnit4.class)
public class ConnectionTest {

    private static final int TIMEOUT = 10000;

    @Test
    public void createUriAndOptions() throws URISyntaxException {
        new Connection(new URI("ws://example.com:28080"), new Consumer.Options());
    }

    @Test
    public void setListener() throws URISyntaxException {
        new Connection(new URI("ws://example.com:28080"), new Consumer.Options()).setListener(new DefaultConnectionListener());
    }

    @Test(timeout = TIMEOUT)
    public void shouldFireOnOpenWhenConnected() throws InterruptedException, IOException {
        final MockWebServer mockWebServer = new MockWebServer();
        final MockResponse response = new MockResponse();
        response.withWebSocketUpgrade(new DefaultWebSocketListener());
        mockWebServer.enqueue(response);
        mockWebServer.start();

        final URI uri = mockWebServer.url("/").uri();
        final Connection connection = new Connection(uri, new Consumer.Options());

        final BlockingQueue<String> events = new LinkedBlockingQueue<String>();

        connection.setListener(new DefaultConnectionListener() {
            @Override
            public void onOpen() {
                events.offer("onOpen");
            }
        });
        connection.open();

        assertThat(events.take(), is("onOpen"));

        mockWebServer.shutdown();
    }

    @Test(timeout = TIMEOUT)
    public void shouldFireOnMessageWhenMessageReceived() throws InterruptedException, IOException {
        final MockWebServer mockWebServer = new MockWebServer();
        final MockResponse response = new MockResponse();
        response.withWebSocketUpgrade(new DefaultWebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                try {
                    webSocket.sendMessage(WebSocket.PayloadType.TEXT, new Buffer().writeUtf8("{}"));
                } catch (IOException ignored) {
                }
            }
        });
        mockWebServer.enqueue(response);
        mockWebServer.start();

        final URI uri = mockWebServer.url("/").uri();
        final Connection connection = new Connection(uri, new Consumer.Options());

        final BlockingQueue<String> events = new LinkedBlockingQueue<String>();

        connection.setListener(new DefaultConnectionListener() {
            @Override
            public void onMessage(String textMessage) {
                events.offer("onMessage:" + textMessage);
            }
        });
        connection.open();

        assertThat(events.take(), is("onMessage:{}"));

        mockWebServer.shutdown();
    }

    @Test(timeout = TIMEOUT)
    public void shouldFireOnCloseWhenDisconnectedByClient() throws InterruptedException, IOException {
        final MockWebServer mockWebServer = new MockWebServer();
        final MockResponse response = new MockResponse();
        response.withWebSocketUpgrade(new DefaultWebSocketListener());
        mockWebServer.enqueue(response);
        mockWebServer.start();

        final URI uri = mockWebServer.url("/").uri();
        final Connection connection = new Connection(uri, new Consumer.Options());

        final BlockingQueue<String> events = new LinkedBlockingQueue<String>();

        connection.setListener(new DefaultConnectionListener() {
            @Override
            public void onOpen() {
                events.offer("onOpen");
            }

            @Override
            public void onClose() {
                events.offer("onClose");
            }
        });

        connection.open();
        assertThat(events.take(), is("onOpen"));

        connection.close();
        assertThat(events.take(), is("onClose"));

        mockWebServer.shutdown();
    }

    @Test(timeout = TIMEOUT)
    public void shouldFireOnCloseWhenDisconnectedByServer() throws InterruptedException, IOException {
        final MockWebServer mockWebServer = new MockWebServer();
        final MockResponse response = new MockResponse();
        response.withWebSocketUpgrade(new DefaultWebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                try {
                    webSocket.close(1000, "Reason");
                } catch (IOException ignored) {
                }
            }
        });
        mockWebServer.enqueue(response);
        mockWebServer.start();

        final URI uri = mockWebServer.url("/").uri();
        final Connection connection = new Connection(uri, new Consumer.Options());

        final BlockingQueue<String> events = new LinkedBlockingQueue<String>();

        connection.setListener(new DefaultConnectionListener() {
            @Override
            public void onClose() {
                events.offer("onClose");
            }
        });
        connection.open();

        assertThat(events.take(), is("onClose"));

        mockWebServer.shutdown();
    }

    @Test(timeout = TIMEOUT)
    public void shouldFireOnFailureWhenInternalServerErrorReceived() throws InterruptedException, IOException {
        final MockWebServer mockWebServer = new MockWebServer();
        final MockResponse response = new MockResponse();
        response.setResponseCode(500);
        response.setStatus("HTTP/1.1 500 Internal Server Error");
        mockWebServer.enqueue(response);
        mockWebServer.start();

        final URI uri = mockWebServer.url("/").uri();
        final Connection connection = new Connection(uri, new Consumer.Options());

        final BlockingQueue<String> events = new LinkedBlockingQueue<String>();

        connection.setListener(new DefaultConnectionListener() {
            @Override
            public void onFailure(Exception e) {
                events.offer("onFailed");
            }
        });
        connection.open();

        assertThat(events.take(), is("onFailed"));

        mockWebServer.shutdown();
    }

    @Test(timeout = TIMEOUT)
    public void isOpenWhenOnOpenAndCloseByClient() throws InterruptedException, IOException {
        final MockWebServer mockWebServer = new MockWebServer();
        final MockResponse response = new MockResponse();
        response.withWebSocketUpgrade(new DefaultWebSocketListener());
        mockWebServer.enqueue(response);
        mockWebServer.start();

        final URI uri = mockWebServer.url("/").uri();
        final Connection connection = new Connection(uri, new Consumer.Options());

        final BlockingQueue<String> events = new LinkedBlockingQueue<String>();

        connection.setListener(new DefaultConnectionListener() {
            @Override
            public void onOpen() {
                events.offer("onOpen");
            }

            @Override
            public void onClose() {
                events.offer("onClose");
            }
        });

        assertThat(connection.isOpen(), is(false));

        connection.open();

        assertThat(events.take(), is("onOpen"));

        assertThat(connection.isOpen(), is(true));

        connection.close();

        assertThat(events.take(), is("onClose"));

        assertThat(connection.isOpen(), is(false));

        mockWebServer.shutdown();
    }

    @Test(timeout = TIMEOUT)
    public void isOpenWhenOnOpenAndCloseByServer() throws InterruptedException, IOException {
        final MockWebServer mockWebServer = new MockWebServer();
        final MockResponse response = new MockResponse();
        response.withWebSocketUpgrade(new DefaultWebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                try {
                    webSocket.close(1000, "Reason");
                } catch (IOException ignored) {
                }
            }
        });
        mockWebServer.enqueue(response);
        mockWebServer.start();

        final URI uri = mockWebServer.url("/").uri();
        final Connection connection = new Connection(uri, new Consumer.Options());

        final BlockingQueue<String> events = new LinkedBlockingQueue<String>();

        connection.setListener(new DefaultConnectionListener() {
            @Override
            public void onOpen() {
                events.offer("onOpen");
            }

            @Override
            public void onClose() {
                events.offer("onClose");
            }
        });

        assertThat(connection.isOpen(), is(false));

        connection.open();

        assertThat(events.take(), is("onOpen"));

        assertThat(connection.isOpen(), is(true));

        connection.close();

        assertThat(events.take(), is("onClose"));

        assertThat(connection.isOpen(), is(false));

        mockWebServer.shutdown();
    }

    @Test(timeout = TIMEOUT)
    public void isOpenWhenOnOpenAndFailure() throws InterruptedException, IOException {
        final MockWebServer mockWebServer = new MockWebServer();
        final MockResponse response = new MockResponse();
        response.setResponseCode(500);
        response.setStatus("HTTP/1.1 500 Internal Server Error");
        mockWebServer.enqueue(response);
        mockWebServer.start();

        final URI uri = mockWebServer.url("/").uri();
        final Connection connection = new Connection(uri, new Consumer.Options());

        final BlockingQueue<String> events = new LinkedBlockingQueue<String>();

        connection.setListener(new DefaultConnectionListener() {
            @Override
            public void onOpen() {
                events.offer("onOpen");
            }

            @Override
            public void onFailure(Exception e) {
                events.offer("onFailed");
            }
        });

        assertThat(connection.isOpen(), is(false));

        connection.open();

        assertThat(events.take(), is("onFailed"));

        assertThat(connection.isOpen(), is(false));

        mockWebServer.shutdown();
    }

    private static class DefaultConnectionListener implements Connection.Listener {

        @Override
        public void onOpen() {
        }

        @Override
        public void onFailure(Exception e) {
        }

        @Override
        public void onMessage(String textMessage) {
        }

        @Override
        public void onClose() {
        }
    }

    private static class DefaultWebSocketListener implements WebSocketListener {

        @Override
        public void onOpen(WebSocket webSocket, Response response) {
        }

        @Override
        public void onFailure(IOException e, Response response) {
        }

        @Override
        public void onMessage(BufferedSource payload, WebSocket.PayloadType type) throws IOException {
        }

        @Override
        public void onPong(Buffer payload) {
        }

        @Override
        public void onClose(int code, String reason) {
        }
    }
}