/*
 * Copyright 2010-2013 Ning, Inc.
 * Copyright 2014 Groupon, Inc
 * Copyright 2014 The Billing Project, LLC
 *
 * The Billing Project licenses this file to you under the Apache License, version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at:
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package org.killbill.billing.jaxrs;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.annotation.Nullable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.killbill.billing.osgi.http.DefaultServletRouter;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.ning.http.client.Response;

public class TestPlugin extends TestJaxrsBase {

    private static final String TEST_PLUGIN_NAME = "test-osgi";

    private static final byte[] TEST_PLUGIN_RESPONSE_BYTES = new byte[]{0xC, 0x0, 0xF, 0xF, 0xE, 0xE};

    private static final String TEST_PLUGIN_VALID_GET_PATH = "setGETMarkerToTrue";
    private static final String TEST_PLUGIN_VALID_HEAD_PATH = "setHEADMarkerToTrue";
    private static final String TEST_PLUGIN_VALID_POST_PATH = "setPOSTMarkerToTrue";
    private static final String TEST_PLUGIN_VALID_PUT_PATH = "setPUTMarkerToTrue";
    private static final String TEST_PLUGIN_VALID_DELETE_PATH = "setDELETEMarkerToTrue";
    private static final String TEST_PLUGIN_VALID_OPTIONS_PATH = "setOPTIONSMarkerToTrue";

    private final AtomicBoolean requestGETMarker = new AtomicBoolean(false);
    private final AtomicBoolean requestHEADMarker = new AtomicBoolean(false);
    private final AtomicBoolean requestPOSTMarker = new AtomicBoolean(false);
    private final AtomicBoolean requestPUTMarker = new AtomicBoolean(false);
    private final AtomicBoolean requestDELETEMarker = new AtomicBoolean(false);
    private final AtomicBoolean requestOPTIONSMarker = new AtomicBoolean(false);

    @Override
    @BeforeMethod(groups = "slow")
    public void beforeMethod() throws Exception {
        if (hasFailed()) {
            return;
        }

        super.beforeMethod();
        setupOSGIPlugin();
        resetAllMarkers();
    }

    @Test(groups = "slow")
    public void testPassRequestsToUnknownPlugin() throws Exception {
        final String uri = "pluginDoesNotExist/something";
        Response response;

        // We don't test the output here as it is some Jetty specific HTML blurb

        response = killBillClient.pluginGET(uri);
        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);

        response = killBillClient.pluginHEAD(uri);
        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);

        response = killBillClient.pluginPOST(uri, null);
        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);

        response = killBillClient.pluginPUT(uri, null);
        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);

        response = killBillClient.pluginDELETE(uri);
        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);

        response = killBillClient.pluginOPTIONS(uri);
        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);
    }

    @Test(groups = "slow")
    public void testPassRequestsToKnownPluginButWrongPath() throws Exception {
        final String uri = TEST_PLUGIN_NAME + "/somethingSomething";
        Response response;

        response = killBillClient.pluginGET(uri);
        testAndResetAllMarkers(response, 200, new byte[]{}, false, false, false, false, false, false);

        response = killBillClient.pluginHEAD(uri);
        testAndResetAllMarkers(response, 204, new byte[]{}, false, false, false, false, false, false);

        response = killBillClient.pluginPOST(uri, null);
        testAndResetAllMarkers(response, 200, new byte[]{}, false, false, false, false, false, false);

        response = killBillClient.pluginPUT(uri, null);
        testAndResetAllMarkers(response, 200, new byte[]{}, false, false, false, false, false, false);

        response = killBillClient.pluginDELETE(uri);
        testAndResetAllMarkers(response, 200, new byte[]{}, false, false, false, false, false, false);

        response = killBillClient.pluginOPTIONS(uri);
        testAndResetAllMarkers(response, 200, new byte[]{}, false, false, false, false, false, false);
    }

    @Test(groups = "slow")
    public void testPassRequestsToKnownPluginAndKnownPath() throws Exception {
        Response response;

        response = killBillClient.pluginGET(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_GET_PATH);
        testAndResetAllMarkers(response, 230, TEST_PLUGIN_RESPONSE_BYTES, true, false, false, false, false, false);

        response = killBillClient.pluginHEAD(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_HEAD_PATH);
        testAndResetAllMarkers(response, 204, new byte[]{}, false, true, false, false, false, false);

        response = killBillClient.pluginPOST(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_POST_PATH, null);
        testAndResetAllMarkers(response, 230, TEST_PLUGIN_RESPONSE_BYTES, false, false, true, false, false, false);

        response = killBillClient.pluginPUT(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_PUT_PATH, null);
        testAndResetAllMarkers(response, 230, TEST_PLUGIN_RESPONSE_BYTES, false, false, false, true, false, false);

        response = killBillClient.pluginDELETE(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_DELETE_PATH);
        testAndResetAllMarkers(response, 230, TEST_PLUGIN_RESPONSE_BYTES, false, false, false, false, true, false);

        response = killBillClient.pluginOPTIONS(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_OPTIONS_PATH);
        testAndResetAllMarkers(response, 230, TEST_PLUGIN_RESPONSE_BYTES, false, false, false, false, false, true);
    }

    private void testAndResetAllMarkers(@Nullable final Response response, final int responseCode, @Nullable final byte[] responseBytes, final boolean get, final boolean head,
                                        final boolean post, final boolean put, final boolean delete, final boolean options) throws IOException {
        if (responseCode == 404 || responseCode == 204) {
            Assert.assertNull(response);
        } else {
            Assert.assertNotNull(response);
            Assert.assertEquals(response.getStatusCode(), responseCode);
            if (responseBytes != null) {
                Assert.assertEquals(response.getResponseBodyAsBytes(), responseBytes);
            }
        }

        Assert.assertEquals(requestGETMarker.get(), get);
        Assert.assertEquals(requestHEADMarker.get(), head);
        Assert.assertEquals(requestPOSTMarker.get(), post);
        Assert.assertEquals(requestPUTMarker.get(), put);
        Assert.assertEquals(requestDELETEMarker.get(), delete);
        Assert.assertEquals(requestOPTIONSMarker.get(), options);

        resetAllMarkers();
    }

    private void resetAllMarkers() {
        requestGETMarker.set(false);
        requestHEADMarker.set(false);
        requestPOSTMarker.set(false);
        requestPUTMarker.set(false);
        requestDELETEMarker.set(false);
        requestOPTIONSMarker.set(false);
    }

    private void setupOSGIPlugin() {
        ((DefaultServletRouter) servletRouter).registerServiceFromPath(TEST_PLUGIN_NAME, new HttpServlet() {
            @Override
            protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
                if (("/" + TEST_PLUGIN_VALID_GET_PATH).equals(req.getPathInfo())) {
                    requestGETMarker.set(true);
                    resp.getOutputStream().write(TEST_PLUGIN_RESPONSE_BYTES);
                    resp.setStatus(230);
                }
            }

            @Override
            protected void doHead(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
                if (("/" + TEST_PLUGIN_VALID_HEAD_PATH).equals(req.getPathInfo())) {
                    requestHEADMarker.set(true);
                }
            }

            @Override
            protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
                if (("/" + TEST_PLUGIN_VALID_POST_PATH).equals(req.getPathInfo())) {
                    requestPOSTMarker.set(true);
                    resp.getOutputStream().write(TEST_PLUGIN_RESPONSE_BYTES);
                    resp.setStatus(230);
                }
            }

            @Override
            protected void doPut(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
                if (("/" + TEST_PLUGIN_VALID_PUT_PATH).equals(req.getPathInfo())) {
                    requestPUTMarker.set(true);
                    resp.getOutputStream().write(TEST_PLUGIN_RESPONSE_BYTES);
                    resp.setStatus(230);
                }
            }

            @Override
            protected void doDelete(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
                if (("/" + TEST_PLUGIN_VALID_DELETE_PATH).equals(req.getPathInfo())) {
                    requestDELETEMarker.set(true);
                    resp.getOutputStream().write(TEST_PLUGIN_RESPONSE_BYTES);
                    resp.setStatus(230);
                }
            }

            @Override
            protected void doOptions(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
                if (("/" + TEST_PLUGIN_VALID_OPTIONS_PATH).equals(req.getPathInfo())) {
                    requestOPTIONSMarker.set(true);
                    resp.getOutputStream().write(TEST_PLUGIN_RESPONSE_BYTES);
                    resp.setStatus(230);
                }
            }
        });
    }
}
