package com.github.greengerong;

import com.google.common.collect.Maps;
import org.apache.http.Header;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.HashMap;

import static com.github.greengerong.PrerenderSeoService.ESCAPED_FRAGMENT_KEY;
import static org.apache.http.HttpStatus.SC_NOT_FOUND;
import static org.apache.http.HttpStatus.SC_OK;
import static org.apache.http.client.methods.HttpGet.METHOD_NAME;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class PreRenderSEOFilterTest {

    private PreRenderSEOFilter preRenderSEOFilter;

    @Mock
    private CloseableHttpClient httpClient;

    @Mock
    private FilterConfig filterConfig;

    @Mock
    private HttpServletRequest servletRequest;

    @Mock
    private HttpServletResponse servletResponse;

    @Mock
    private FilterChain filterChain;

    @Mock
    private HttpGet httpGet;
    @Mock
    private PrintWriter printWriter;

    @Before
    public void setUp() throws Exception {
        preRenderSEOFilter = new PreRenderSEOFilter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
                setPrerenderSeoService(new PrerenderSeoService(toMap(filterConfig)) {
                    @Override
                    protected CloseableHttpClient getHttpClient() {
                        return httpClient;
                    }

                    @Override
                    protected HttpGet getHttpGet(String apiUrl) {
                        return httpGet;
                    }
                });
            }
        };
    }

    @Test
    public void should_not_handle_when_non_get_request() throws Exception {
        //given
        preRenderSEOFilter.init(filterConfig);
        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer());
        when(servletRequest.getMethod()).thenReturn(HttpPost.METHOD_NAME);

        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient, never()).execute(httpGet);
        verify(filterChain).doFilter(servletRequest, servletResponse);
    }

    @Test
    public void should_handle_when_url_with_escaped_fragment_() throws Exception {
        //given
        preRenderSEOFilter.init(filterConfig);
        final CloseableHttpResponse httpResponse = mock(CloseableHttpResponse.class);
        final StatusLine statusLine = mock(StatusLine.class);

        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/test"));
        when(servletRequest.getMethod()).thenReturn(METHOD_NAME);
        when(servletRequest.getHeaderNames()).thenReturn(mock(Enumeration.class));
        when(httpClient.execute(httpGet)).thenReturn(httpResponse);
        when(httpResponse.getStatusLine()).thenReturn(statusLine);
        final HashMap<String, String> map = Maps.newHashMap();
        map.put(ESCAPED_FRAGMENT_KEY, "");
        when(servletRequest.getParameterMap()).thenReturn(map);
        when(statusLine.getStatusCode()).thenReturn(SC_OK);
        when(httpResponse.getAllHeaders()).thenReturn(new Header[0]);
        when(servletResponse.getWriter()).thenReturn(printWriter);

        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient).execute(httpGet);
        verify(filterChain, never()).doFilter(servletRequest, servletResponse);
    }

    @Test
    public void should_not_handle_when_user_agent_is_not_crawler() throws Exception {
        //given
        preRenderSEOFilter.init(filterConfig);

        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/test"));
        when(servletRequest.getMethod()).thenReturn(METHOD_NAME);
        when(servletRequest.getParameterMap()).thenReturn(Maps.<String, String>newHashMap());
        when(servletRequest.getHeader("User-Agent")).thenReturn("no");
        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient, never()).execute(httpGet);
        verify(filterChain).doFilter(servletRequest, servletResponse);
    }

    @Test
    public void should_not_handle_when_x_prerender_header_is_present() throws Exception {
        //given
        when(filterConfig.getInitParameter("crawlerUserAgents")).thenReturn("crawler1,crawler2");
        preRenderSEOFilter.init(filterConfig);

        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/test"));
        when(servletRequest.getMethod()).thenReturn(METHOD_NAME);
        when(servletRequest.getParameterMap()).thenReturn(Maps.<String, String>newHashMap());
        when(servletRequest.getHeader("User-Agent")).thenReturn("crawler1");
        when(servletRequest.getHeader("X-Prerender")).thenReturn("1");
        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient, never()).execute(httpGet);
        verify(filterChain).doFilter(servletRequest, servletResponse);
    }

    @Test
    public void should_not_handle_when_url_is_a_resource() throws Exception {
        //given
        when(filterConfig.getInitParameter("crawlerUserAgents")).thenReturn("crawler1,crawler2");
        preRenderSEOFilter.init(filterConfig);

        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/test.js"));
        when(servletRequest.getMethod()).thenReturn(METHOD_NAME);
        when(servletRequest.getParameterMap()).thenReturn(Maps.<String, String>newHashMap());
        when(servletRequest.getHeader("User-Agent")).thenReturn("crawler1");
        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient, never()).execute(httpGet);
        verify(filterChain).doFilter(servletRequest, servletResponse);
    }

    @Test
    public void should_not_handle_when_white_list_is_not_empty_and_url_is_not_in_white_list() throws Exception {
        //given
        when(filterConfig.getInitParameter("crawlerUserAgents")).thenReturn("crawler1,crawler2");
        when(filterConfig.getInitParameter("whitelist")).thenReturn("whitelist1,whitelist2");
        preRenderSEOFilter.init(filterConfig);

        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/test"));
        when(servletRequest.getMethod()).thenReturn(METHOD_NAME);
        when(servletRequest.getParameterMap()).thenReturn(Maps.<String, String>newHashMap());
        when(servletRequest.getHeader("User-Agent")).thenReturn("crawler1");
        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient, never()).execute(httpGet);
        verify(filterChain).doFilter(servletRequest, servletResponse);
    }

    @Test
    public void should_not_handle_when_black_list_is_not_empty_and_url_is_in_black_list() throws Exception {
        //given
        when(filterConfig.getInitParameter("crawlerUserAgents")).thenReturn("crawler1,crawler2");
        when(filterConfig.getInitParameter("blacklist")).thenReturn("blacklist1,http://localhost/test");
        preRenderSEOFilter.init(filterConfig);

        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/test"));
        when(servletRequest.getMethod()).thenReturn(METHOD_NAME);
        when(servletRequest.getParameterMap()).thenReturn(Maps.<String, String>newHashMap());
        when(servletRequest.getHeader("User-Agent")).thenReturn("crawler1");
        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient, never()).execute(httpGet);
        verify(filterChain).doFilter(servletRequest, servletResponse);
    }

    @Test
    public void should_handle_when_user_agent_is_crawler_and_url_is_not_resource_and_white_list_is_empty_and_black_list_is_empty() throws Exception {
        //given
        when(filterConfig.getInitParameter("crawlerUserAgents")).thenReturn("crawler1,crawler2");
        preRenderSEOFilter.init(filterConfig);

        final CloseableHttpResponse httpResponse = mock(CloseableHttpResponse.class);
        final StatusLine statusLine = mock(StatusLine.class);

        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/test"));
        when(servletRequest.getMethod()).thenReturn(METHOD_NAME);
        when(servletRequest.getHeader("User-Agent")).thenReturn("crawler1");

        when(servletRequest.getHeaderNames()).thenReturn(mock(Enumeration.class));
        when(httpClient.execute(httpGet)).thenReturn(httpResponse);
        when(httpResponse.getStatusLine()).thenReturn(statusLine);
        final HashMap<String, String> map = Maps.newHashMap();
        map.put(ESCAPED_FRAGMENT_KEY, "");
        when(servletRequest.getParameterMap()).thenReturn(map);
        when(statusLine.getStatusCode()).thenReturn(SC_OK);
        when(httpResponse.getAllHeaders()).thenReturn(new Header[0]);
        when(servletResponse.getWriter()).thenReturn(printWriter);
        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient).execute(httpGet);
        verify(filterChain, never()).doFilter(servletRequest, servletResponse);
    }

    @Test
    public void should_handle_when_every_thing_is_ok_but_prerender_server_response_is_not_200() throws Exception {
        //given
        when(filterConfig.getInitParameter("crawlerUserAgents")).thenReturn("crawler1,crawler2");
        preRenderSEOFilter.init(filterConfig);

        final CloseableHttpResponse httpResponse = mock(CloseableHttpResponse.class);
        final StatusLine statusLine = mock(StatusLine.class);

        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/test"));
        when(servletRequest.getMethod()).thenReturn(METHOD_NAME);
        when(servletRequest.getHeader("User-Agent")).thenReturn("crawler1");

        when(servletRequest.getHeaderNames()).thenReturn(mock(Enumeration.class));
        when(httpClient.execute(httpGet)).thenReturn(httpResponse);
        when(httpResponse.getStatusLine()).thenReturn(statusLine);
        final HashMap<String, String> map = Maps.newHashMap();
        map.put(ESCAPED_FRAGMENT_KEY, "");
        when(servletRequest.getParameterMap()).thenReturn(map);
        when(statusLine.getStatusCode()).thenReturn(SC_NOT_FOUND);
        when(httpResponse.getAllHeaders()).thenReturn(new Header[0]);
        when(servletResponse.getWriter()).thenReturn(printWriter);


        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient).execute(httpGet);
        verify(filterChain, never()).doFilter(servletRequest, servletResponse);
        verify(servletResponse).setStatus(SC_NOT_FOUND);
    }


    @Test
    public void should_handle_when_user_agent_is_crawler_and_url_is_not_resource_and_in_white_list_and_not_in_black_list() throws Exception {
        //given
        when(filterConfig.getInitParameter("crawlerUserAgents")).thenReturn("crawler1,crawler2");
        when(filterConfig.getInitParameter("whitelist")).thenReturn("whitelist1,http://localhost/test");
        when(filterConfig.getInitParameter("blacklist")).thenReturn("blacklist1,blacklist2");

        preRenderSEOFilter.init(filterConfig);

        final CloseableHttpResponse httpResponse = mock(CloseableHttpResponse.class);
        final StatusLine statusLine = mock(StatusLine.class);

        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/test"));
        when(servletRequest.getMethod()).thenReturn(METHOD_NAME);
        when(servletRequest.getHeader("User-Agent")).thenReturn("crawler1");

        when(servletRequest.getHeaderNames()).thenReturn(mock(Enumeration.class));
        when(httpClient.execute(httpGet)).thenReturn(httpResponse);
        when(httpResponse.getStatusLine()).thenReturn(statusLine);
        final HashMap<String, String> map = Maps.newHashMap();
        map.put(ESCAPED_FRAGMENT_KEY, "");
        when(servletRequest.getParameterMap()).thenReturn(map);
        when(statusLine.getStatusCode()).thenReturn(SC_OK);
        when(httpResponse.getAllHeaders()).thenReturn(new Header[0]);
        when(servletResponse.getWriter()).thenReturn(printWriter);

        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient).execute(httpGet);
        verify(filterChain, never()).doFilter(servletRequest, servletResponse);
    }

    @Test
    public void should_use_request_url_from_custom_header_if_available() throws Exception {
        //given
        when(filterConfig.getInitParameter("forwardedURLHeader")).thenReturn("X-Forwarded-URL");
        when(filterConfig.getInitParameter("whitelist")).thenReturn("http://my.public.domain.com/");
        when(filterConfig.getInitParameter("blacklist")).thenReturn("http://localhost/test");

        preRenderSEOFilter.init(filterConfig);

        final CloseableHttpResponse httpResponse = mock(CloseableHttpResponse.class);
        final StatusLine statusLine = mock(StatusLine.class);

        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/test"));
        when(servletRequest.getMethod()).thenReturn(METHOD_NAME);
        when(servletRequest.getHeader("X-Forwarded-URL")).thenReturn("http://my.public.domain.com/");

        when(servletRequest.getHeaderNames()).thenReturn(mock(Enumeration.class));
        when(httpClient.execute(httpGet)).thenReturn(httpResponse);
        when(httpResponse.getStatusLine()).thenReturn(statusLine);
        final HashMap<String, String> map = Maps.newHashMap();
        map.put(ESCAPED_FRAGMENT_KEY, "");
        when(servletRequest.getParameterMap()).thenReturn(map);
        when(statusLine.getStatusCode()).thenReturn(SC_OK);
        when(httpResponse.getAllHeaders()).thenReturn(new Header[0]);
        when(servletResponse.getWriter()).thenReturn(printWriter);

        //when
        preRenderSEOFilter.doFilter(servletRequest, servletResponse, filterChain);

        //then
        verify(httpClient).execute(httpGet);
        verify(filterChain, never()).doFilter(servletRequest, servletResponse);
    }
}