package org.jooby;

import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import static org.easymock.EasyMock.expect;
import org.jooby.WebSocket.CloseStatus;
import org.jooby.test.MockUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.LinkedList;
import java.util.Map;
import java.util.Optional;

@RunWith(PowerMockRunner.class)
@PrepareForTest({WebSocket.class, LoggerFactory.class })
public class WebSocketTest {

  static class WebSocketMock implements WebSocket {

    @Override
    public void close(final CloseStatus status) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void resume() {
      throw new UnsupportedOperationException();
    }

    @Override
    public void pause() {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean isOpen() {
      throw new UnsupportedOperationException();
    }

    @Override
    public void terminate() throws Exception {
      throw new UnsupportedOperationException();
    }

    @Override
    public void send(final Object data, final SuccessCallback success, final OnError err)
        throws Exception {
      throw new UnsupportedOperationException();
    }

    @Override
    public void broadcast(final Object data, final SuccessCallback success, final OnError err)
        throws Exception {
      throw new UnsupportedOperationException();
    }

    @Override
    public void onMessage(final OnMessage<Mutant> callback) throws Exception {
      throw new UnsupportedOperationException();
    }

    @Override
    public String path() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String pattern() {
      throw new UnsupportedOperationException();
    }

    @Override
    public Map<Object, String> vars() {
      throw new UnsupportedOperationException();
    }

    @Override
    public MediaType consumes() {
      throw new UnsupportedOperationException();
    }

    @Override
    public MediaType produces() {
      throw new UnsupportedOperationException();
    }

    @Override
    public <T> T require(final Key<T> key) {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      throw new UnsupportedOperationException();
    }

    @Override
    public void onError(final OnError callback) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void onClose(final OnClose callback) throws Exception {
      throw new UnsupportedOperationException();
    }

    @Override public <T> T get(String name) {
      throw new UnsupportedOperationException();
    }

    @Override public <T> Optional<T> ifGet(String name) {
      throw new UnsupportedOperationException();
    }

    @Nullable @Override public WebSocket set(String name, Object value) {
      throw new UnsupportedOperationException();
    }

    @Override public WebSocket unset() {
      throw new UnsupportedOperationException();
    }

    @Override public <T> Optional<T> unset(String name) {
      throw new UnsupportedOperationException();
    }

    @Override public Map<String, Object> attributes() {
      throw new UnsupportedOperationException();
    }
  }

  @Test
  public void noopSuccess() throws Exception {
    WebSocket.SUCCESS.invoke();
  }

  @Test
  public void err() throws Exception {
    Exception ex = new Exception();
    new MockUnit(Logger.class)
        .expect(unit -> {
          Logger log = unit.get(Logger.class);
          log.error("error while sending data", ex);

          unit.mockStatic(LoggerFactory.class);
          expect(LoggerFactory.getLogger(WebSocket.class)).andReturn(log);
        })
        .run(unit -> {
          WebSocket.ERR.onError(ex);
        });
  }

  @Test(expected = IllegalArgumentException.class)
  public void tooLowCode() throws Exception {
    CloseStatus.of(200);
  }

  @Test(expected = IllegalArgumentException.class)
  public void tooHighCode() throws Exception {
    CloseStatus.of(5001);
  }

  @Test
  public void closeStatus() throws Exception {
    assertEquals(1000, WebSocket.NORMAL.code());
    assertEquals("Normal", WebSocket.NORMAL.reason());
    assertEquals("1000 (Normal)", WebSocket.NORMAL.toString());
    assertEquals("1000", WebSocket.CloseStatus.of(1000).toString());

    assertEquals(1001, WebSocket.GOING_AWAY.code());
    assertEquals("Going away", WebSocket.GOING_AWAY.reason());

    assertEquals(1002, WebSocket.PROTOCOL_ERROR.code());
    assertEquals("Protocol error", WebSocket.PROTOCOL_ERROR.reason());

    assertEquals(1003, WebSocket.NOT_ACCEPTABLE.code());
    assertEquals("Not acceptable", WebSocket.NOT_ACCEPTABLE.reason());

    assertEquals(1007, WebSocket.BAD_DATA.code());
    assertEquals("Bad data", WebSocket.BAD_DATA.reason());

    assertEquals(1008, WebSocket.POLICY_VIOLATION.code());
    assertEquals("Policy violation", WebSocket.POLICY_VIOLATION.reason());

    assertEquals(1009, WebSocket.TOO_BIG_TO_PROCESS.code());
    assertEquals("Too big to process", WebSocket.TOO_BIG_TO_PROCESS.reason());

    assertEquals(1010, WebSocket.REQUIRED_EXTENSION.code());
    assertEquals("Required extension", WebSocket.REQUIRED_EXTENSION.reason());

    assertEquals(1011, WebSocket.SERVER_ERROR.code());
    assertEquals("Server error", WebSocket.SERVER_ERROR.reason());

    assertEquals(1012, WebSocket.SERVICE_RESTARTED.code());
    assertEquals("Service restarted", WebSocket.SERVICE_RESTARTED.reason());

    assertEquals(1013, WebSocket.SERVICE_OVERLOAD.code());
    assertEquals("Service overload", WebSocket.SERVICE_OVERLOAD.reason());
  }

  @Test
  public void closeCodeAndReason() throws Exception {
    LinkedList<WebSocket.CloseStatus> statusList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void close(final CloseStatus status) {
        assertEquals(1004, status.code());
        assertEquals("My reason", status.reason());
        statusList.add(status);
      }
    };
    ws.close(1004, "My reason");
    assertTrue(statusList.size() > 0);
  }

  @Test
  public void closeStatusCode() throws Exception {
    LinkedList<WebSocket.CloseStatus> statusList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void close(final CloseStatus status) {
        assertEquals(1007, status.code());
        assertEquals(null, status.reason());
        statusList.add(status);
      }
    };
    ws.close(1007);
    assertTrue(statusList.size() > 0);
  }

  @Test
  public void close() throws Exception {

    LinkedList<WebSocket.CloseStatus> statusList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void close(final CloseStatus status) {
        assertEquals(1000, status.code());
        assertEquals("Normal", status.reason());
        statusList.add(status);
      }
    };
    ws.close(WebSocket.NORMAL);
    assertTrue(statusList.size() > 0);
  }

  @Test
  public void closeDefault() throws Exception {

    LinkedList<WebSocket.CloseStatus> statusList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void close(final CloseStatus status) {
        assertEquals(1000, status.code());
        assertEquals("Normal", status.reason());
        statusList.add(status);
      }
    };
    ws.close();
    assertTrue(statusList.size() > 0);
  }

  @SuppressWarnings("resource")
  @Test
  public void send() throws Exception {
    Object data = new Object();
    WebSocket.SuccessCallback SUCCESS_ = WebSocket.SUCCESS;
    WebSocket.OnError ERR_ = WebSocket.ERR;
    LinkedList<Object> dataList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void send(final Object data, final SuccessCallback success, final OnError err)
          throws Exception {
        dataList.add(data);
        assertEquals(SUCCESS_, success);
        assertEquals(ERR_, err);
      }
    };
    ws.send(data);
    assertTrue(dataList.size() > 0);
    assertEquals(data, dataList.getFirst());
  }

  @SuppressWarnings("resource")
  @Test
  public void broadcast() throws Exception {
    Object data = new Object();
    WebSocket.SuccessCallback SUCCESS_ = WebSocket.SUCCESS;
    WebSocket.OnError ERR_ = WebSocket.ERR;
    LinkedList<Object> dataList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void broadcast(final Object data, final SuccessCallback success, final OnError err)
          throws Exception {
        dataList.add(data);
        assertEquals(SUCCESS_, success);
        assertEquals(ERR_, err);
      }
    };
    ws.broadcast(data);
    assertTrue(dataList.size() > 0);
    assertEquals(data, dataList.getFirst());
  }

  @SuppressWarnings("resource")
  @Test
  public void sendCustomSuccess() throws Exception {
    Object data = new Object();
    WebSocket.SuccessCallback SUCCESS_ = () -> {
    };
    WebSocket.OnError ERR_ = WebSocket.ERR;
    LinkedList<Object> dataList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void send(final Object data, final SuccessCallback success, final OnError err)
          throws Exception {
        dataList.add(data);
        assertEquals(SUCCESS_, success);
        assertEquals(ERR_, err);
      }
    };
    ws.send(data, SUCCESS_);
    assertTrue(dataList.size() > 0);
    assertEquals(data, dataList.getFirst());
  }

  @SuppressWarnings("resource")
  @Test
  public void broadcastCustomSuccess() throws Exception {
    Object data = new Object();
    WebSocket.SuccessCallback SUCCESS_ = () -> {
    };
    WebSocket.OnError ERR_ = WebSocket.ERR;
    LinkedList<Object> dataList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void broadcast(final Object data, final SuccessCallback success, final OnError err)
          throws Exception {
        dataList.add(data);
        assertEquals(SUCCESS_, success);
        assertEquals(ERR_, err);
      }
    };
    ws.broadcast(data, SUCCESS_);
    assertTrue(dataList.size() > 0);
    assertEquals(data, dataList.getFirst());
  }

  @SuppressWarnings("resource")
  @Test
  public void sendCustomErr() throws Exception {
    Object data = new Object();
    WebSocket.SuccessCallback SUCCESS_ = WebSocket.SUCCESS;
    WebSocket.OnError ERR_ = (ex) -> {
    };
    LinkedList<Object> dataList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void send(final Object data, final SuccessCallback success, final OnError err)
          throws Exception {
        dataList.add(data);
        assertEquals(SUCCESS_, success);
        assertEquals(ERR_, err);
      }
    };
    ws.send(data, ERR_);
    assertTrue(dataList.size() > 0);
    assertEquals(data, dataList.getFirst());
  }

  @SuppressWarnings("resource")
  @Test
  public void broadcastCustomErr() throws Exception {
    Object data = new Object();
    WebSocket.SuccessCallback SUCCESS_ = WebSocket.SUCCESS;
    WebSocket.OnError ERR_ = (ex) -> {
    };
    LinkedList<Object> dataList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void broadcast(final Object data, final SuccessCallback success, final OnError err)
          throws Exception {
        dataList.add(data);
        assertEquals(SUCCESS_, success);
        assertEquals(ERR_, err);
      }
    };
    ws.broadcast(data, ERR_);
    assertTrue(dataList.size() > 0);
    assertEquals(data, dataList.getFirst());
  }

  @SuppressWarnings("resource")
  @Test
  public void sendCustomSuccessAndErr() throws Exception {
    Object data = new Object();
    WebSocket.SuccessCallback SUCCESS_ = () -> {
    };
    WebSocket.OnError ERR_ = (ex) -> {
    };
    LinkedList<Object> dataList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void send(final Object data, final SuccessCallback success, final OnError err)
          throws Exception {
        dataList.add(data);
        assertEquals(SUCCESS_, success);
        assertEquals(ERR_, err);
      }
    };
    ws.send(data, SUCCESS_, ERR_);
    assertTrue(dataList.size() > 0);
    assertEquals(data, dataList.getFirst());
  }

  @SuppressWarnings("resource")
  @Test
  public void broadcastCustomSuccessAndErr() throws Exception {
    Object data = new Object();
    WebSocket.SuccessCallback SUCCESS_ = () -> {
    };
    WebSocket.OnError ERR_ = (ex) -> {
    };
    LinkedList<Object> dataList = new LinkedList<>();
    WebSocket ws = new WebSocketMock() {
      @Override
      public void broadcast(final Object data, final SuccessCallback success, final OnError err)
          throws Exception {
        dataList.add(data);
        assertEquals(SUCCESS_, success);
        assertEquals(ERR_, err);
      }
    };
    ws.broadcast(data, SUCCESS_, ERR_);
    assertTrue(dataList.size() > 0);
    assertEquals(data, dataList.getFirst());
  }

  @SuppressWarnings("resource")
  @Test
  public void getInstance() throws Exception {
    Object instance = new Object();
    WebSocket ws = new WebSocketMock() {
      @SuppressWarnings("unchecked")
      @Override
      public <T> T require(final Key<T> key) {
        return (T) instance;
      }
    };
    assertEquals(instance, ws.require(WebSocket.class));
    assertEquals(instance, ws.require(TypeLiteral.get(String.class)));
  }

}
