package org.jboss.resteasy.test.asynch;

import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.logging.Logger;
import org.jboss.resteasy.test.asynch.resource.AsyncFilterException;
import org.jboss.resteasy.test.asynch.resource.AsyncFilterExceptionMapper;
import org.jboss.resteasy.test.asynch.resource.AsyncPreMatchRequestFilter1;
import org.jboss.resteasy.test.asynch.resource.AsyncPreMatchRequestFilter2;
import org.jboss.resteasy.test.asynch.resource.AsyncPreMatchRequestFilter3;
import org.jboss.resteasy.test.asynch.resource.AsyncRequestFilter;
import org.jboss.resteasy.test.asynch.resource.AsyncRequestFilter1;
import org.jboss.resteasy.test.asynch.resource.AsyncRequestFilter2;
import org.jboss.resteasy.test.asynch.resource.AsyncRequestFilter3;
import org.jboss.resteasy.test.asynch.resource.AsyncRequestFilterResource;
import org.jboss.resteasy.test.asynch.resource.AsyncResponseFilter;
import org.jboss.resteasy.test.asynch.resource.AsyncResponseFilter1;
import org.jboss.resteasy.test.asynch.resource.AsyncResponseFilter2;
import org.jboss.resteasy.test.asynch.resource.AsyncResponseFilter3;
import org.jboss.resteasy.utils.PortProviderUtil;
import org.jboss.resteasy.utils.TestUtil;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

/**
 * @tpSubChapter CDI
 * @tpChapter Integration tests
 * @tpTestCaseDetails Async Request Filter test.
 * @tpSince RESTEasy 4.0.0
 */
@ExtendWith(ArquillianExtension.class)
@RunAsClient
public class AsyncRequestFilterTest {
    protected static final Logger log = Logger.getLogger(AsyncRequestFilterTest.class.getName());

    @Deployment
    public static Archive<?> createTestArchive() {

        WebArchive war = TestUtil.prepareArchive(AsyncRequestFilterTest.class.getSimpleName());
        war.addClasses(AsyncRequestFilterResource.class, AsyncRequestFilter.class, AsyncResponseFilter.class,
                AsyncRequestFilter1.class, AsyncRequestFilter2.class, AsyncRequestFilter3.class,
                AsyncPreMatchRequestFilter1.class, AsyncPreMatchRequestFilter2.class, AsyncPreMatchRequestFilter3.class,
                AsyncResponseFilter1.class, AsyncResponseFilter2.class, AsyncResponseFilter3.class,
                AsyncFilterException.class, AsyncFilterExceptionMapper.class);
        return war;
    }

    private String generateURL(String path) {
        return PortProviderUtil.generateURL(path, AsyncRequestFilterTest.class.getSimpleName());
    }

    /**
     * @tpTestDetails Interceptors work
     * @tpSince RESTEasy 4.0.0
     */
    @Test
    public void testRequestFilters() throws Exception {
        Client client = ClientBuilder.newClient();

        // Create book.
        WebTarget base = client.target(generateURL("/"));

        // all sync

        Response response = base.request()
                .header("Filter1", "sync-pass")
                .header("Filter2", "sync-pass")
                .header("Filter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("Filter1", "sync-fail")
                .header("Filter2", "sync-fail")
                .header("Filter3", "sync-fail")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("Filter1", response.readEntity(String.class));

        response = base.request()
                .header("Filter1", "sync-pass")
                .header("Filter2", "sync-fail")
                .header("Filter3", "sync-fail")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("Filter2", response.readEntity(String.class));

        response = base.request()
                .header("Filter1", "sync-pass")
                .header("Filter2", "sync-pass")
                .header("Filter3", "sync-fail")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("Filter3", response.readEntity(String.class));

        // async
        response = base.request()
                .header("Filter1", "async-pass")
                .header("Filter2", "sync-pass")
                .header("Filter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("Filter1", "async-pass")
                .header("Filter2", "async-pass")
                .header("Filter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("Filter1", "async-pass")
                .header("Filter2", "async-pass")
                .header("Filter3", "async-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("Filter1", "async-pass")
                .header("Filter2", "sync-pass")
                .header("Filter3", "async-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("Filter1", "sync-pass")
                .header("Filter2", "async-pass")
                .header("Filter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        // async failures

        response = base.request()
                .header("Filter1", "async-fail")
                .header("Filter2", "sync-fail")
                .header("Filter3", "sync-fail")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("Filter1", response.readEntity(String.class));

        response = base.request()
                .header("Filter1", "async-pass")
                .header("Filter2", "sync-fail")
                .header("Filter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("Filter2", response.readEntity(String.class));

        response = base.request()
                .header("Filter1", "async-pass")
                .header("Filter2", "async-fail")
                .header("Filter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("Filter2", response.readEntity(String.class));

        // async instantaneous
        response = base.request()
                .header("Filter1", "async-pass-instant")
                .header("Filter2", "sync-pass")
                .header("Filter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("Filter1", "async-fail-instant")
                .header("Filter2", "sync-pass")
                .header("Filter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("Filter1", response.readEntity(String.class));

        client.close();
    }

    /**
     * @tpTestDetails Interceptors work
     * @tpSince RESTEasy 4.0.0
     */
    @Test
    public void testPreMatchRequestFilters() throws Exception {
        Client client = ClientBuilder.newClient();

        // Create book.
        WebTarget base = client.target(generateURL("/"));

        // all sync

        Response response = base.request()
                .header("PreMatchFilter1", "sync-pass")
                .header("PreMatchFilter2", "sync-pass")
                .header("PreMatchFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("PreMatchFilter1", "sync-fail")
                .header("PreMatchFilter2", "sync-fail")
                .header("PreMatchFilter3", "sync-fail")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("PreMatchFilter1", response.readEntity(String.class));

        response = base.request()
                .header("PreMatchFilter1", "sync-pass")
                .header("PreMatchFilter2", "sync-fail")
                .header("PreMatchFilter3", "sync-fail")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("PreMatchFilter2", response.readEntity(String.class));

        response = base.request()
                .header("PreMatchFilter1", "sync-pass")
                .header("PreMatchFilter2", "sync-pass")
                .header("PreMatchFilter3", "sync-fail")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("PreMatchFilter3", response.readEntity(String.class));

        // async
        response = base.request()
                .header("PreMatchFilter1", "async-pass")
                .header("PreMatchFilter2", "sync-pass")
                .header("PreMatchFilter3", "sync-pass")
                .get();
        assertEquals("resource", response.readEntity(String.class));
        assertEquals(200, response.getStatus());

        response = base.request()
                .header("PreMatchFilter1", "async-pass")
                .header("PreMatchFilter2", "async-pass")
                .header("PreMatchFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("PreMatchFilter1", "async-pass")
                .header("PreMatchFilter2", "async-pass")
                .header("PreMatchFilter3", "async-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("PreMatchFilter1", "async-pass")
                .header("PreMatchFilter2", "sync-pass")
                .header("PreMatchFilter3", "async-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("PreMatchFilter1", "sync-pass")
                .header("PreMatchFilter2", "async-pass")
                .header("PreMatchFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        // async failures

        response = base.request()
                .header("PreMatchFilter1", "async-fail")
                .header("PreMatchFilter2", "sync-fail")
                .header("PreMatchFilter3", "sync-fail")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("PreMatchFilter1", response.readEntity(String.class));

        response = base.request()
                .header("PreMatchFilter1", "async-pass")
                .header("PreMatchFilter2", "sync-fail")
                .header("PreMatchFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("PreMatchFilter2", response.readEntity(String.class));

        response = base.request()
                .header("PreMatchFilter1", "async-pass")
                .header("PreMatchFilter2", "async-fail")
                .header("PreMatchFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("PreMatchFilter2", response.readEntity(String.class));

        client.close();
    }

    /**
     * @tpTestDetails Interceptors work
     * @tpSince RESTEasy 4.0.0
     */
    @Test
    public void testResponseFilters() throws Exception {
        Client client = ClientBuilder.newClient();

        // Create book.
        WebTarget base = client.target(generateURL("/"));

        // all sync

        Response response = base.request()
                .header("ResponseFilter1", "sync-pass")
                .header("ResponseFilter2", "sync-pass")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("ResponseFilter1", "sync-fail")
                .header("ResponseFilter2", "sync-pass")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("ResponseFilter1", response.readEntity(String.class));

        response = base.request()
                .header("ResponseFilter1", "sync-pass")
                .header("ResponseFilter2", "sync-fail")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("ResponseFilter2", response.readEntity(String.class));

        response = base.request()
                .header("ResponseFilter1", "sync-pass")
                .header("ResponseFilter2", "sync-pass")
                .header("ResponseFilter3", "sync-fail")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("ResponseFilter3", response.readEntity(String.class));

        // async
        response = base.request()
                .header("ResponseFilter1", "async-pass")
                .header("ResponseFilter2", "sync-pass")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals("resource", response.readEntity(String.class));
        assertEquals(200, response.getStatus());

        response = base.request()
                .header("ResponseFilter1", "async-pass")
                .header("ResponseFilter2", "async-pass")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("ResponseFilter1", "async-pass")
                .header("ResponseFilter2", "async-pass")
                .header("ResponseFilter3", "async-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("ResponseFilter1", "async-pass")
                .header("ResponseFilter2", "sync-pass")
                .header("ResponseFilter3", "async-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("ResponseFilter1", "sync-pass")
                .header("ResponseFilter2", "async-pass")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        // async failures

        response = base.request()
                .header("ResponseFilter1", "async-fail")
                .header("ResponseFilter2", "sync-pass")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("ResponseFilter1", response.readEntity(String.class));

        response = base.request()
                .header("ResponseFilter1", "async-pass")
                .header("ResponseFilter2", "sync-fail")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("ResponseFilter2", response.readEntity(String.class));

        response = base.request()
                .header("ResponseFilter1", "async-pass")
                .header("ResponseFilter2", "async-fail")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("ResponseFilter2", response.readEntity(String.class));

        // async instantaneous
        response = base.request()
                .header("ResponseFilter1", "async-pass-instant")
                .header("ResponseFilter2", "sync-pass")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));

        response = base.request()
                .header("ResponseFilter1", "async-fail-instant")
                .header("ResponseFilter2", "sync-pass")
                .header("ResponseFilter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("ResponseFilter1", response.readEntity(String.class));

        client.close();
    }

    /**
     * @tpTestDetails Interceptors work
     * @tpSince RESTEasy 4.0.0
     */
    @Test
    public void testResponseFilters2() throws Exception {
        Client client = ClientBuilder.newClient();

        // Create book.
        WebTarget base = client.target(generateURL("/async"));

        // async way later
        Response response = base.request()
                .header("ResponseFilter1", "sync-pass")
                .header("ResponseFilter2", "sync-pass")
                .header("ResponseFilter3", "async-fail-late")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("ResponseFilter3", response.readEntity(String.class));

        client.close();
    }

    /**
     * @tpTestDetails Async filters work with resume(Throwable) wrt filters/callbacks/complete
     * @tpSince RESTEasy 4.0.0
     */
    @Test
    public void testResponseFiltersThrow() throws Exception {
        Client client = ClientBuilder.newClient();

        testResponseFilterThrow(client, "/callback-async", false);
        testResponseFilterThrow(client, "/callback", false);

        testResponseFilterThrow(client, "/callback-async", true);
        testResponseFilterThrow(client, "/callback", true);

        client.close();
    }

    private void testResponseFilterThrow(Client client, String target, boolean useExceptionMapper) {
        WebTarget base = client.target(generateURL(target));

        // throw in response filter
        Response response = base.request()
                .header("ResponseFilter1", "sync-pass")
                .header("ResponseFilter2", "sync-pass")
                .header("UseExceptionMapper", useExceptionMapper)
                .header("ResponseFilter3", "async-throw-late")
                .get();
        // this is 500 even with exception mapper because exceptions in response filters are not mapped
        assertEquals(500, response.getStatus());

        try {
            // give a chance to CI to run the callbacks
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }

        // check that callbacks were called
        response = base.request().get();
        assertEquals(200, response.getStatus());
        if (useExceptionMapper)
            assertEquals("org.jboss.resteasy.test.asynch.resource.AsyncFilterException: ouch",
                    response.getHeaders().getFirst("ResponseFilterCallbackResponseFilter3"));
        else
            assertEquals("java.lang.Throwable: ouch", response.getHeaders().getFirst("ResponseFilterCallbackResponseFilter3"));

        // throw in request filter
        response = base.request()
                .header("Filter1", "sync-pass")
                .header("Filter2", "sync-pass")
                .header("UseExceptionMapper", useExceptionMapper)
                .header("Filter3", "async-throw-late")
                .get();
        if (useExceptionMapper) {
            assertEquals(Status.ACCEPTED.getStatusCode(), response.getStatus());
            assertEquals("exception was mapped", response.readEntity(String.class));
        } else {
            assertEquals(500, response.getStatus());
        }

        try {
            // give a chance to CI to run the callbacks
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }

        // check that callbacks were called
        response = base.request().get();
        assertEquals(200, response.getStatus());
        if (useExceptionMapper)
            assertEquals("org.jboss.resteasy.test.asynch.resource.AsyncFilterException: ouch",
                    response.getHeaders().getFirst("RequestFilterCallbackFilter3"));
        else
            assertEquals("java.lang.Throwable: ouch", response.getHeaders().getFirst("RequestFilterCallbackFilter3"));

    }

    /**
     * @tpTestDetails Interceptors work with non-Response resource methods
     * @tpSince RESTEasy 4.0.0
     */
    @Test
    public void testRequestFiltersGuessReturnType() throws Exception {
        Client client = ClientBuilder.newClient();

        // Create book.
        WebTarget base = client.target(generateURL("/non-response"));

        Response response = base.request()
                .header("Filter1", "async-pass")
                .header("Filter2", "sync-pass")
                .header("Filter3", "sync-pass")
                .get();
        assertEquals(200, response.getStatus());
        assertEquals("resource", response.readEntity(String.class));
        client.close();
    }
}
