// Copyright 2013-2019 Michel Kraemer
//
// Licensed 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 de.undercouch.gradle.tasks.download;

import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.extension.ResponseTransformer;
import com.github.tomakehurst.wiremock.http.HttpHeader;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.Response;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.github.tomakehurst.wiremock.matching.UrlPattern;
import org.apache.commons.io.FileUtils;
import org.gradle.api.tasks.TaskExecutionException;
import org.junit.Rule;
import org.junit.Test;

import javax.servlet.http.HttpServletResponse;
import java.io.File;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.Assert.assertEquals;

/**
 * Tests if the plugin can handle redirects
 * @author Michel Kraemer
 */
public class RedirectTest extends TestBaseWithMockServer {
    private static final String REDIRECT = "redirect";

    /**
     * Run a mock HTTP server with {@link RedirectTransformer}
     */
    @Rule
    public WireMockRule redirectWireMockRule = new WireMockRule(options()
            .dynamicPort()
            .extensions(RedirectTransformer.class.getName())
            .jettyStopTimeout(10000L));

    public static class RedirectTransformer extends ResponseTransformer {
        private Integer redirects = null;

        @Override
        public Response transform(Request request, Response response,
                FileSource files, Parameters parameters) {
            if (redirects == null) {
                redirects = parameters.getInt("redirects");
            }
            String nl;
            if (redirects > 1) {
                redirects--;
                nl = "/" + REDIRECT + "?r=" + redirects;
            } else {
                nl = "/" + TEST_FILE_NAME;
            }
            return Response.Builder.like(response)
                    .but().headers(response.getHeaders()
                            .plus(new HttpHeader("Location", nl)))
                    .build();
        }

        @Override
        public String getName() {
            return "redirect";
        }
    }

    /**
     * Tests if the plugin can handle one redirect
     * @throws Exception if anything goes wrong
     */
    @Test
    public void oneRedirect() throws Exception {
        UrlPattern up1 = urlEqualTo("/" + REDIRECT);
        wireMockRule.stubFor(get(up1)
                .willReturn(aResponse()
                        .withStatus(HttpServletResponse.SC_FOUND)
                        .withHeader("Location", wireMockRule.url(TEST_FILE_NAME))));

        UrlPattern up2 = urlEqualTo("/" + TEST_FILE_NAME);
        wireMockRule.stubFor(get(up2)
                .willReturn(aResponse()
                        .withBody(CONTENTS)));

        Download t = makeProjectAndTask();
        t.src(wireMockRule.url(REDIRECT));
        File dst = folder.newFile();
        t.dest(dst);
        t.execute();

        String dstContents = FileUtils.readFileToString(dst);
        assertEquals(CONTENTS, dstContents);

        wireMockRule.verify(1, getRequestedFor(up1));
        wireMockRule.verify(1, getRequestedFor(up2));
    }
    
    /**
     * Tests if the plugin can handle ten redirects
     * @throws Exception if anything goes wrong
     */
    @Test
    public void tenRedirect() throws Exception {
        UrlPattern up1 = urlPathEqualTo("/" + REDIRECT);
        redirectWireMockRule.stubFor(get(up1)
                .withQueryParam("r", matching("[0-9]+"))
                .willReturn(aResponse()
                        .withStatus(HttpServletResponse.SC_FOUND)
                        .withTransformer("redirect", "redirects", 10)));

        UrlPattern up2 = urlEqualTo("/" + TEST_FILE_NAME);
        redirectWireMockRule.stubFor(get(up2)
                .willReturn(aResponse()
                        .withBody(CONTENTS)));

        Download t = makeProjectAndTask();
        t.src(redirectWireMockRule.url(REDIRECT) + "?r=10");
        File dst = folder.newFile();
        t.dest(dst);
        t.execute();

        String dstContents = FileUtils.readFileToString(dst);
        assertEquals(CONTENTS, dstContents);

        verify(10, getRequestedFor(up1));
        verify(1, getRequestedFor(up2));
    }

   /**
    * Tests if the plugin can handle circular redirects
    * @throws Exception if anything goes wrong
    */
   @Test(expected = TaskExecutionException.class)
   public void circularRedirect() throws Exception {
       UrlPattern up1 = urlPathEqualTo("/" + REDIRECT);
       wireMockRule.stubFor(get(up1)
               .willReturn(aResponse()
                       .withStatus(HttpServletResponse.SC_FOUND)
                       .withHeader("Location", "/" + REDIRECT)));

       Download t = makeProjectAndTask();
       t.src(wireMockRule.url(REDIRECT));
       File dst = folder.newFile();
       t.dest(dst);
       t.execute();
   }

    /**
     * Make sure the plugin fails with too many redirects
     * @throws Exception if anything goes wrong
     */
    @Test(expected = TaskExecutionException.class)
    public void tooManyRedirects() throws Exception {
        UrlPattern up1 = urlPathEqualTo("/" + REDIRECT);
        redirectWireMockRule.stubFor(get(up1)
                .withQueryParam("r", matching("[0-9]+"))
                .willReturn(aResponse()
                        .withStatus(HttpServletResponse.SC_FOUND)
                        .withTransformer("redirect", "redirects", 51)));

        Download t = makeProjectAndTask();
        t.src(redirectWireMockRule.url(REDIRECT) + "?r=52");
        File dst = folder.newFile();
        t.dest(dst);
        t.execute();
    }
}