package com.atlassian.stash.internal.web;

import com.google.common.collect.ImmutableMap;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockRequestDispatcher;
import org.tuckey.web.filters.urlrewrite.*;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import static java.lang.String.format;
import static org.junit.Assert.*;

/**
 * Test to check the rewrite rules in {@code urlrewrite.xml}.
 *
 * @see <a href="http://sujitpal.blogspot.com.au/2006/08/junit-test-for-urlrewritefilter.html">http://sujitpal.blogspot.com.au/2006/08/junit-test-for-urlrewritefilter.html</a>
 */
public class UrlRewriteRulesTest {

    private static final String REWRITE_RULES = UrlRewriteFilter.DEFAULT_WEB_CONF_PATH;

    private final Map<String, String> simpleRewrites = ImmutableMap.<String, String>builder()
        // rewrite rules for resources
        .put("/index.html", "/mvc/home")
        .put("/;jsessionid=47A23963C8B8125CDD13FF39148E4A9D", "/mvc/home;jsessionid=47A23963C8B8125CDD13FF39148E4A9D")
        .put("/s/test/_/download/test2", "/download/test2")
        .put("/static/WEB-INF/web.xml", "/mvc/static/WEB-INF/web.xml")

        // rewrite rule to Spring MVC
        .put("/login.do", "/mvc/login.do")
        .put("/signup.do", "/mvc/signup.do")
        .build();

    private final Map<String, String> pathRewrites = ImmutableMap.<String, String>builder()
        .put("/projects/PRJ/repos/REPO/browse/REF", "REF")
        .put("/mvc/projects/PRJ/repos/REPO/browse/REF2", "REF2")
        .put("/rest/api/1.0/projects/PRJ/repos/REPO/browse/REF3", "REF3")
        .put("/projects/PRJ/repos/REPO/history/REF4", "REF4")
        .put("/mvc/projects/PRJ/repos/REPO/history/REF5", "REF5")
        .put("/rest/api/1.0/projects/PRJ/repos/REPO/history/REF6", "REF6")
        .build();

    private final Map<String, String> commitHashRewrites = ImmutableMap.<String, String>builder()
        .put("/projects/PRJ/repos/REPO/commits/REF", "REF")
        .put("/mvc/projects/PRJ/repos/REPO/commits/REF2", "REF2")
        .put("/rest/api/1.0/projects/PRJ/repos/REPO/commits/REF3", "REF3")
        .build();

    // rewrite rules for private project repos
    private final Map<String, String> privateRepoRewrites = ImmutableMap.<String, String>builder()
        .put("/users/user/repos/REPO", "/mvc/projects/~user/repos/REPO")
        .put("/mvc/users/user/repos/REPO", "/mvc/projects/~user/repos/REPO")
        .put("/users/user/repos/REPO?create", "/mvc/projects/~user/repos/REPO?create")
        .put("/users/user/repos/REPO/browse/some/file.xml", "/mvc/projects/~user/repos/REPO/browse/some/file.xml")
        .put("/rest/api/1.0/users/user/repos/REPO", "/rest/api/1.0/projects/~user/repos/REPO")
        .build();

    private final String[] jsonHeaders = {"application/json", "application/x-json", "text/json", "text/x-json"};

    private UrlRewriter rewriter;

    @Before
    public void setUp() throws Exception {
        final InputStream rules = getClass().getResourceAsStream(REWRITE_RULES);
        try {
            assertNotNull("Rewrite rules are not in the classpath", rules);
            rewriter = new UrlRewriter(new Conf(rules, REWRITE_RULES));
        } finally {
            IOUtils.closeQuietly(rules);
        }
    }

    @Test
    public void testSimpleRewrites() throws Exception {
        for (Map.Entry<String, String> test : simpleRewrites.entrySet()) {
            final String url = test.getKey();
            final String targetUrl = test.getValue();
            assertRewrite(url, targetUrl);
        }
    }

    @Test
    public void testJsonRewrites() throws Exception {
        final String url = "/action";
        assertRewrite(url, "/mvc/action"); // no JSON accept header -> normal rewrite to MVC

        final String target = "/rest/api/1.0/action";
        for (String json : jsonHeaders) {
            assertRewrite(url, target, "Accept", json);
        }
    }

    @Test
    public void testCacheHeaders() throws Exception {
        final String url = "/s/test/_/download/test2";
        final HttpServletResponse response = assertRewrite(url, "/download/test2");
        assertTrue(response.containsHeader("Cache-Control"));
        assertTrue(response.containsHeader("Expires"));
    }


    @Test
    public void testPathRewrites() throws Exception {
        for (final Map.Entry<String, String> test : pathRewrites.entrySet()) {
            assertRewriteWithParam(test.getKey(), "path", test.getValue());
        }
    }

    @Test
    public void testCommitHashRewrites() throws Exception {
        for (final Map.Entry<String, String> test : commitHashRewrites.entrySet()) {
            assertRewriteWithParam(test.getKey(), "commitHash", test.getValue());
        }
    }

    @Test
    public void testPrivateRepoRewrites() throws Exception {
        for (Map.Entry<String, String> test : privateRepoRewrites.entrySet()) {
            final String url = test.getKey();
            final String targetUrl = test.getValue();
            assertRewrite(url, targetUrl);
        }
    }

    private void assertRewriteWithParam(final String url, final String paramName, final String paramValue) throws IOException, ServletException {
        final MockFilterChain chain = new MockFilterChain();
        final MutableBoolean forwarded = new MutableBoolean(false);
        final MockHttpServletRequest request = new MockHttpServletRequest("GET", url) {
            public RequestDispatcher getRequestDispatcher(String path) {
                return new MockRequestDispatcher(path) {
                    public void forward(ServletRequest request, ServletResponse response) {
                        forwarded.setValue(paramValue.equals(request.getParameter(paramName)));
                    }
                };
            }
        };
        final MockHttpServletResponse response = new MockHttpServletResponse();
        final UrlRewriteWrappedResponse wrappedResponse = new UrlRewriteWrappedResponse(response, request, rewriter); // see UrlRewriteFilter#doFilter, line 361

        assertTrue(rewriter.processRequest(request, wrappedResponse, chain));
        assertTrue(format("rule did not match %s; expected parameter '%s' equals to '%s'", url, paramName, paramValue),
                forwarded.isTrue() // the rewritter forwards the request if only the rule adding the parameter is matched
                || paramValue.equals(chain.getRequest().getParameter(paramName))); // the rewritter wraps the request if both the rule adding the parameter and a rewrite rule are matched
    }

    private HttpServletResponse assertRewrite(String from, String to, String... headers) throws Exception {
        final MockHttpServletRequest request = new MockHttpServletRequest("GET", from);
        final MockHttpServletResponse response = new MockHttpServletResponse();
        for (int i = 0; i < headers.length - 1; ++i) {
            request.addHeader(headers[i], headers[++i]);
        }
        return assertRewrite(from, to, request, response);
    }

    private HttpServletResponse assertRewrite(String from, String to, MockHttpServletRequest request, MockHttpServletResponse response) throws Exception {
        final RewrittenUrl rewrittenUrl = rewriter.processRequest(request, response);
        assertNotNull("No rewrite for: " + from, rewrittenUrl);
        final String rewrittenUrlString = rewrittenUrl.getTarget();
        assertEquals("Incorrect rewrite for: " + from, to, rewrittenUrlString);
        return response;
    }
}
