package org.jooby.internal.jetty;

import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.isA;

import java.io.IOException;

import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
import org.jooby.servlet.ServletServletRequest;
import org.jooby.servlet.ServletServletResponse;
import org.jooby.spi.HttpHandler;
import org.jooby.spi.NativeWebSocket;
import org.jooby.test.MockUnit;
import org.jooby.test.MockUnit.Block;
import org.junit.Test;

public class JettyHandlerTest {

  private Block wsStopTimeout = unit -> {
    WebSocketServerFactory ws = unit.get(WebSocketServerFactory.class);
    ws.setStopTimeout(30000L);
  };

  @Test
  public void handleShouldSetMultipartConfig() throws Exception {
    new MockUnit(Request.class, HttpHandler.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("Multipart/Form-Data");

              request.setAttribute(eq(Request.MULTIPART_CONFIG_ELEMENT),
                  isA(MultipartConfigElement.class));
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");

            })
            .expect(unit -> {
              HttpHandler dispatcher = unit.get(HttpHandler.class);
              dispatcher.handle(isA(ServletServletRequest.class),
                  isA(ServletServletResponse.class));
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(unit.get(HttpHandler.class), unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            });
  }

  @Test
  public void handleShouldIgnoreMultipartConfig() throws Exception {
    new MockUnit(Request.class, HttpHandler.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("application/json");
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");
            })
            .expect(unit -> {
              HttpHandler dispatcher = unit.get(HttpHandler.class);
              dispatcher.handle(isA(ServletServletRequest.class),
                  isA(ServletServletResponse.class));
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(unit.get(HttpHandler.class), unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            });
  }

  @Test
  public void handleWsUpgrade() throws Exception {
    new MockUnit(Request.class, HttpHandler.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class, NativeWebSocket.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("application/json");
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");
            })
            .expect(unit -> {
              HttpServletRequest req = unit.get(HttpServletRequest.class);
              HttpServletResponse rsp = unit.get(HttpServletResponse.class);
              NativeWebSocket ws = unit.get(NativeWebSocket.class);

              WebSocketServerFactory factory = unit.get(WebSocketServerFactory.class);

              expect(factory.isUpgradeRequest(req, rsp)).andReturn(true);

              expect(factory.acceptWebSocket(req, rsp)).andReturn(true);

              expect(req.getAttribute(JettyWebSocket.class.getName())).andReturn(ws);
              req.removeAttribute(JettyWebSocket.class.getName());
            })
            .expect(unit -> {
              HttpHandler dispatcher = unit.get(HttpHandler.class);
              dispatcher.handle(unit.capture(ServletServletRequest.class),
                  unit.capture(ServletServletResponse.class));
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(unit.get(HttpHandler.class), unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            }, unit -> {
              ServletServletRequest req = unit.captured(ServletServletRequest.class).get(0);
              req.upgrade(NativeWebSocket.class);
            });
  }

  @Test(expected = UnsupportedOperationException.class)
  public void handleThrowUnsupportedOperationExceptionWhenWsIsMissing() throws Exception {
    new MockUnit(Request.class, HttpHandler.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class, NativeWebSocket.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("application/json");
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");
            })
            .expect(unit -> {
              HttpServletRequest req = unit.get(HttpServletRequest.class);
              HttpServletResponse rsp = unit.get(HttpServletResponse.class);

              WebSocketServerFactory factory = unit.get(WebSocketServerFactory.class);

              expect(factory.isUpgradeRequest(req, rsp)).andReturn(true);

              expect(factory.acceptWebSocket(req, rsp)).andReturn(true);

              expect(req.getAttribute(JettyWebSocket.class.getName())).andReturn(null);
            })
            .expect(unit -> {
              HttpHandler dispatcher = unit.get(HttpHandler.class);
              dispatcher.handle(unit.capture(ServletServletRequest.class),
                  unit.capture(ServletServletResponse.class));
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(unit.get(HttpHandler.class), unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            }, unit -> {
              ServletServletRequest req = unit.captured(ServletServletRequest.class).get(0);
              req.upgrade(NativeWebSocket.class);
            });
  }

  @Test(expected = UnsupportedOperationException.class)
  public void handleThrowUnsupportedOperationExceptionOnNoWebSocketRequest() throws Exception {
    new MockUnit(Request.class, HttpHandler.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class, NativeWebSocket.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("application/json");
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");
            })
            .expect(unit -> {
              HttpServletRequest req = unit.get(HttpServletRequest.class);
              HttpServletResponse rsp = unit.get(HttpServletResponse.class);

              WebSocketServerFactory factory = unit.get(WebSocketServerFactory.class);

              expect(factory.isUpgradeRequest(req, rsp)).andReturn(false);
            })
            .expect(unit -> {
              HttpHandler dispatcher = unit.get(HttpHandler.class);
              dispatcher.handle(unit.capture(ServletServletRequest.class),
                  unit.capture(ServletServletResponse.class));
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(unit.get(HttpHandler.class), unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            }, unit -> {
              ServletServletRequest req = unit.captured(ServletServletRequest.class).get(0);
              req.upgrade(NativeWebSocket.class);
            });
  }

  @Test(expected = UnsupportedOperationException.class)
  public void handleThrowUnsupportedOperationExceptionOnHankshakeRejection() throws Exception {
    new MockUnit(Request.class, HttpHandler.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class, NativeWebSocket.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("application/json");
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");
            })
            .expect(unit -> {
              HttpServletRequest req = unit.get(HttpServletRequest.class);
              HttpServletResponse rsp = unit.get(HttpServletResponse.class);

              WebSocketServerFactory factory = unit.get(WebSocketServerFactory.class);

              expect(factory.isUpgradeRequest(req, rsp)).andReturn(true);

              expect(factory.acceptWebSocket(req, rsp)).andReturn(false);
            })
            .expect(unit -> {
              HttpHandler dispatcher = unit.get(HttpHandler.class);
              dispatcher.handle(unit.capture(ServletServletRequest.class),
                  unit.capture(ServletServletResponse.class));
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(unit.get(HttpHandler.class), unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            }, unit -> {
              ServletServletRequest req = unit.captured(ServletServletRequest.class).get(0);
              req.upgrade(NativeWebSocket.class);
            });
  }

  @Test(expected = UnsupportedOperationException.class)
  public void handleThrowUnsupportedOperationExceptionOnWrongType() throws Exception {
    new MockUnit(Request.class, HttpHandler.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class, NativeWebSocket.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("application/json");
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");
            })
            .expect(unit -> {
              HttpHandler dispatcher = unit.get(HttpHandler.class);
              dispatcher.handle(unit.capture(ServletServletRequest.class),
                  unit.capture(ServletServletResponse.class));
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(unit.get(HttpHandler.class), unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            }, unit -> {
              ServletServletRequest req = unit.captured(ServletServletRequest.class).get(0);
              req.upgrade(JettyHandlerTest.class);
            });
  }

  @Test(expected = ServletException.class)
  public void handleShouldReThrowServletException() throws Exception {
    HttpHandler dispatcher = (request, response) -> {
      throw new ServletException("intentional err");
    };
    new MockUnit(Request.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("application/json");
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");
            })
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(false);
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(dispatcher, unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            });
  }

  @Test(expected = IOException.class)
  public void handleShouldReThrowIOException() throws Exception {
    HttpHandler dispatcher = (request, response) -> {
      throw new IOException("intentional err");
    };
    new MockUnit(Request.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("application/json");
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");
            })
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(false);
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(dispatcher, unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            });
  }

  @Test(expected = IllegalArgumentException.class)
  public void handleShouldReThrowIllegalArgumentException() throws Exception {
    HttpHandler dispatcher = (request, response) -> {
      throw new IllegalArgumentException("intentional err");
    };
    new MockUnit(Request.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("application/json");
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");
            })
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(false);
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(dispatcher, unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            });
  }

  @Test(expected = IllegalStateException.class)
  public void handleShouldReThrowIllegalStateException() throws Exception {
    HttpHandler dispatcher = (request, response) -> {
      throw new Exception("intentional err");
    };
    new MockUnit(Request.class, WebSocketServerFactory.class,
        HttpServletRequest.class, HttpServletResponse.class)
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(true);

              expect(request.getContentType()).andReturn("application/json");
            })
            .expect(unit -> {
              HttpServletRequest request = unit.get(HttpServletRequest.class);

              expect(request.getPathInfo()).andReturn("/");
              expect(request.getContextPath()).andReturn("");
            })
            .expect(unit -> {
              Request request = unit.get(Request.class);

              request.setHandled(false);
            })
            .expect(wsStopTimeout)
            .run(unit -> {
              new JettyHandler(dispatcher, unit.get(WebSocketServerFactory.class),
                  "target", -1)
                      .handle("/", unit.get(Request.class),
                          unit.get(HttpServletRequest.class),
                          unit.get(HttpServletResponse.class));
            });
  }
}
