package org.jooby.servlet;

import static org.easymock.EasyMock.expect;

import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jooby.Jooby;
import org.jooby.spi.HttpHandler;
import org.jooby.test.MockUnit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import com.typesafe.config.Config;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ServletHandler.class, ServletServletRequest.class, ServletServletResponse.class })
public class ServletHandlerTest {

  MockUnit.Block init = unit -> {
    HttpHandler dispatcher = unit.get(HttpHandler.class);

    Config config = unit.mock(Config.class);
    expect(config.getString("application.tmpdir")).andReturn("target");

    Jooby app = unit.mock(Jooby.class);
    expect(app.require(HttpHandler.class)).andReturn(dispatcher);
    expect(app.require(Config.class)).andReturn(config);

    ServletContext ctx = unit.mock(ServletContext.class);
    expect(ctx.getAttribute(Jooby.class.getName())).andReturn(app);

    ServletConfig servletConfig = unit.get(ServletConfig.class);

    expect(servletConfig.getServletContext()).andReturn(ctx);
  };

  @Test
  public void initMethodMustAskForDependencies() throws Exception {
    new MockUnit(ServletConfig.class, HttpHandler.class)
        .expect(init)
        .run(unit ->
            new ServletHandler()
                .init(unit.get(ServletConfig.class))
        );
  }

  @Test
  public void serviceShouldDispatchToHandler() throws Exception {
    new MockUnit(ServletConfig.class, HttpHandler.class, HttpServletRequest.class,
        HttpServletResponse.class)
        .expect(init)
        .expect(
            unit -> {
              HttpHandler dispatcher = unit.get(HttpHandler.class);

              ServletServletRequest req = unit.mockConstructor(ServletServletRequest.class,
                  new Class[]{HttpServletRequest.class, String.class },
                  unit.get(HttpServletRequest.class), "target");
              ServletServletResponse rsp = unit.mockConstructor(ServletServletResponse.class,
                  new Class[]{HttpServletRequest.class, HttpServletResponse.class },
                  unit.get(HttpServletRequest.class), unit.get(HttpServletResponse.class));

              dispatcher.handle(req, rsp);
            })
        .run(unit -> {
          ServletHandler handler = new ServletHandler();
          handler.init(unit.get(ServletConfig.class));
          handler.service(unit.get(HttpServletRequest.class), unit.get(HttpServletResponse.class));
        });
  }

  @Test(expected = IllegalStateException.class)
  public void serviceShouldCatchExceptionAndRethrowAsRuntime() throws Exception {
    HttpHandler dispatcher = (request, response) -> {
      throw new Exception("intentional err");
    };

    new MockUnit(ServletConfig.class, HttpServletRequest.class,
        HttpServletResponse.class)
        .expect(unit -> {
          Config config = unit.mock(Config.class);
          expect(config.getString("application.tmpdir")).andReturn("target");

          Jooby app = unit.mock(Jooby.class);
          expect(app.require(HttpHandler.class)).andReturn(dispatcher);
          expect(app.require(Config.class)).andReturn(config);

          ServletContext ctx = unit.mock(ServletContext.class);
          expect(ctx.getAttribute(Jooby.class.getName())).andReturn(app);

          ServletConfig servletConfig = unit.get(ServletConfig.class);

          expect(servletConfig.getServletContext()).andReturn(ctx);
        })
        .expect(unit -> {
          unit.mockConstructor(ServletServletRequest.class,
              new Class[]{HttpServletRequest.class, String.class },
              unit.get(HttpServletRequest.class), "target");
          unit.mockConstructor(ServletServletResponse.class,
              new Class[]{HttpServletRequest.class, HttpServletResponse.class },
              unit.get(HttpServletRequest.class), unit.get(HttpServletResponse.class));
        })
        .expect(unit -> {
          HttpServletRequest req = unit.get(HttpServletRequest.class);
          expect(req.getRequestURI()).andReturn("/");
        })
        .run(unit -> {
          ServletHandler handler = new ServletHandler();
          handler.init(unit.get(ServletConfig.class));
          handler.service(unit.get(HttpServletRequest.class), unit.get(HttpServletResponse.class));
        });
  }

  @Test(expected = IOException.class)
  public void serviceShouldCatchIOExceptionAndRethrow() throws Exception {
    HttpHandler dispatcher = (request, response) -> {
      throw new IOException("intentional err");
    };

    new MockUnit(ServletConfig.class, HttpServletRequest.class,
        HttpServletResponse.class)
        .expect(unit -> {
          Config config = unit.mock(Config.class);
          expect(config.getString("application.tmpdir")).andReturn("target");

          Jooby app = unit.mock(Jooby.class);
          expect(app.require(HttpHandler.class)).andReturn(dispatcher);
          expect(app.require(Config.class)).andReturn(config);

          ServletContext ctx = unit.mock(ServletContext.class);
          expect(ctx.getAttribute(Jooby.class.getName())).andReturn(app);

          ServletConfig servletConfig = unit.get(ServletConfig.class);

          expect(servletConfig.getServletContext()).andReturn(ctx);
        })
        .expect(unit -> {
          unit.mockConstructor(ServletServletRequest.class,
              new Class[]{HttpServletRequest.class, String.class },
              unit.get(HttpServletRequest.class), "target");
          unit.mockConstructor(ServletServletResponse.class,
              new Class[]{HttpServletRequest.class, HttpServletResponse.class },
              unit.get(HttpServletRequest.class), unit.get(HttpServletResponse.class));
        })
        .expect(unit -> {
          HttpServletRequest req = unit.get(HttpServletRequest.class);
          expect(req.getRequestURI()).andReturn("/");
        })
        .run(unit -> {
          ServletHandler handler = new ServletHandler();
          handler.init(unit.get(ServletConfig.class));
          handler.service(unit.get(HttpServletRequest.class), unit.get(HttpServletResponse.class));
        });
  }

  @Test(expected = ServletException.class)
  public void serviceShouldCatchServletExceptionAndRethrow() throws Exception {
    HttpHandler dispatcher = (request, response) -> {
      throw new ServletException("intentional err");
    };

    new MockUnit(ServletConfig.class, HttpServletRequest.class,
        HttpServletResponse.class)
        .expect(unit -> {
          Config config = unit.mock(Config.class);
          expect(config.getString("application.tmpdir")).andReturn("target");

          Jooby app = unit.mock(Jooby.class);
          expect(app.require(HttpHandler.class)).andReturn(dispatcher);
          expect(app.require(Config.class)).andReturn(config);

          ServletContext ctx = unit.mock(ServletContext.class);
          expect(ctx.getAttribute(Jooby.class.getName())).andReturn(app);

          ServletConfig servletConfig = unit.get(ServletConfig.class);

          expect(servletConfig.getServletContext()).andReturn(ctx);
        })
        .expect(unit -> {
          unit.mockConstructor(ServletServletRequest.class,
              new Class[]{HttpServletRequest.class, String.class },
              unit.get(HttpServletRequest.class), "target");
          unit.mockConstructor(ServletServletResponse.class,
              new Class[]{HttpServletRequest.class, HttpServletResponse.class },
              unit.get(HttpServletRequest.class), unit.get(HttpServletResponse.class));
        })
        .expect(unit -> {
          HttpServletRequest req = unit.get(HttpServletRequest.class);
          expect(req.getRequestURI()).andReturn("/");
        })
        .run(unit -> {
          ServletHandler handler = new ServletHandler();
          handler.init(unit.get(ServletConfig.class));
          handler.service(unit.get(HttpServletRequest.class), unit.get(HttpServletResponse.class));
        });
  }
}
