/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.apache.wicket.core.request.mapper;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.apache.wicket.MockPage;
import org.apache.wicket.core.request.handler.BookmarkableListenerRequestHandler;
import org.apache.wicket.core.request.handler.BookmarkablePageRequestHandler;
import org.apache.wicket.core.request.handler.IPageProvider;
import org.apache.wicket.core.request.handler.IPageRequestHandler;
import org.apache.wicket.core.request.handler.ListenerRequestHandler;
import org.apache.wicket.core.request.handler.PageAndComponentProvider;
import org.apache.wicket.core.request.handler.PageProvider;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
import org.apache.wicket.core.request.mapper.PackageMapperTest.OuterPage.InnerPage;
import org.apache.wicket.mock.MockWebRequest;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.component.IRequestableComponent;
import org.apache.wicket.request.component.IRequestablePage;
import org.apache.wicket.request.mapper.parameter.INamedParameters;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.lang.PackageName;
import org.junit.jupiter.api.Test;

/**
 * Tests for {@link PackageMapper}
 */
class PackageMapperTest extends AbstractMapperTest
{
	private static final String MOUNT_PATH = "mount/path";

	private static final String ALIAS = "alias";
	private static final String PAGE_CLASS_NAME = MockPage.class.getSimpleName();
	private final PackageMapper encoder = new PackageMapper(MOUNT_PATH,
		PackageName.forClass(MockPage.class))
	{
		@Override
		protected IMapperContext getContext()
		{
			return context;
		}

		@Override
		boolean getRecreateMountedPagesAfterExpiry()
		{
			return true;
		}
	};
	private final PackageMapper aliasEncoder = new PackageMapper(MOUNT_PATH,
		PackageName.forClass(MockPage.class))
	{
		@Override
		protected IMapperContext getContext()
		{
			return context;
		}

		@Override
		protected String transformFromUrl(String classNameAlias)
		{
			final String realClassName;
			if (ALIAS.equals(classNameAlias))
			{
				realClassName = PAGE_CLASS_NAME;
			}
			else
			{
				realClassName = super.transformFromUrl(classNameAlias);
			}
			return realClassName;
		}

		@Override
		protected String transformForUrl(String className)
		{
			final String alias;
			if (PAGE_CLASS_NAME.equals(className))
			{
				alias = ALIAS;
			}
			else
			{
				alias = super.transformForUrl(className);
			}
			return alias;
		}
	};

	private final PackageMapper namedParametersEncoder = new PackageMapper(
		MOUNT_PATH + "/${foo}/${bar}", PackageName.forClass(MockPage.class))
	{
		@Override
		protected IMapperContext getContext()
		{
			return context;
		}
	};
	private final PackageMapper innerClassEncoder = new PackageMapper(MOUNT_PATH,
		PackageName.forClass(OuterPage.class))
	{
		@Override
		protected IMapperContext getContext()
		{
			return context;
		}
	};

	/**
	 *
	 */
	@Test
	void decode1()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + PAGE_CLASS_NAME);
		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(RenderPageRequestHandler.class);
		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
		assertEquals(PAGE_CLASS_NAME, page.getClass().getSimpleName());
		assertEquals(0, page.getPageParameters().getIndexedCount());
		assertTrue(page.getPageParameters().getNamedKeys().isEmpty());
	}

	/**
	 * https://issues.apache.org/jira/browse/WICKET-4994
	 */
	@Test
	void decode1CaseInsensitively()
	{
		Url url = Url.parse(MOUNT_PATH.replace('o', 'O').replace('p', 'P') + '/' + PAGE_CLASS_NAME);
		IRequestHandler handler = encoder.setCaseSensitiveMatch(false).mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(RenderPageRequestHandler.class);
		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
		assertEquals(PAGE_CLASS_NAME, page.getClass().getSimpleName());
		assertEquals(0, page.getPageParameters().getIndexedCount());
		assertTrue(page.getPageParameters().getNamedKeys().isEmpty());
	}

	/**
	 * https://issues.apache.org/jira/browse/WICKET-5500
	 */
	@Test
	void decodePageClassWithPathParameters()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + PAGE_CLASS_NAME + ";something=else");
		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertTrue(handler instanceof RenderPageRequestHandler);
		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
		assertEquals(PAGE_CLASS_NAME, page.getClass().getSimpleName());
		assertEquals(0, page.getPageParameters().getIndexedCount());
		assertTrue(page.getPageParameters().getNamedKeys().isEmpty());
	}

	/**
	 *
	 */
	@Test
	void decode2()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/indexed1?a=b&b=c");
		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(RenderPageRequestHandler.class);
		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
		assertEquals(PAGE_CLASS_NAME, page.getClass().getSimpleName());

		PageParameters p = page.getPageParameters();
		assertEquals(1, p.getIndexedCount());
		assertEquals("indexed1", p.get(0).toString());

		assertEquals(2, p.getNamedKeys().size());
		assertEquals("b", p.get("a").toString());
		assertEquals("c", p.get("b").toString());
	}

	/**
	 * The tests resolves pages by id as if they were existed in the page store. These pages have no
	 * page parameters (i.e. page.getPageParameters() == null).
	 *
	 * The request that the encoder does also has no parameters (neither in the path nor in the
	 * query string) so the resolved page is assumed to be valid.
	 */
	@Test
	void decode3()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "?15");
		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(RenderPageRequestHandler.class);
		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
		checkPage(page, 15);
	}

	/**
	 * The tests resolves pages by id as if they were existed in the page store. These pages have no
	 * page parameters (i.e. page.getPageParameters() == null).
	 *
	 * Since Wicket 7.0.0 (WICKET-4441) if a new request to hybrid url (a url with both mount path
	 * and pageId) has different page parameters than the resolved page then a new page instance
	 * with the new parameters is created. This way if a user manipulates manually the product id in
	 * url like: /mount/path/Products/23?3 to /mount/path/Products/24?3 then Wicket will create a
	 * new page that will show product=24
	 */
	@Test
	void decode4()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/i1/i2?15&a=b&b=c");
		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(RenderPageRequestHandler.class);
		RenderPageRequestHandler h = (RenderPageRequestHandler)handler;
		((PageProvider)h.getPageProvider()).setPageSource(context);
		IRequestablePage page = h.getPage();
		checkPage(page, 1);

		PageParameters p = page.getPageParameters();
		assertEquals(2, p.getIndexedCount());

		assertEquals(2, p.getNamedKeys().size());
	}

	/**
	 *
	 */
	@Test
	void decode5()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "?15--foo-bar");
		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(ListenerRequestHandler.class);

		ListenerRequestHandler h = (ListenerRequestHandler)handler;

		IRequestablePage page = h.getPage();
		checkPage(page, 15);

		assertEquals("foo:bar", h.getComponent().getPageRelativePath());
		assertNull(h.getBehaviorIndex());
	}

	/**
	 *
	 */
	@Test
	void decode6()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/i1/i2?15--foo-bar&a=b&b=c");
		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(ListenerRequestHandler.class);
		ListenerRequestHandler h = (ListenerRequestHandler)handler;

		IRequestablePage page = h.getPage();
		checkPage(page, 15);

		assertEquals("foo:bar", h.getComponent().getPageRelativePath());

		PageParameters p = h.getPageParameters();
		assertEquals(2, p.getIndexedCount());

		assertEquals(2, p.getNamedKeys().size());
	}

	/**
	 *
	 */
	@Test
	void decode7()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "?15-.4-foo-bar");
		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(ListenerRequestHandler.class);

		ListenerRequestHandler h = (ListenerRequestHandler)handler;

		IRequestablePage page = h.getPage();
		checkPage(page, 15);

		assertEquals("foo:bar", h.getComponent().getPageRelativePath());
		assertEquals((Object)4, h.getBehaviorIndex());
	}

	/**
	 *
	 */
	@Test
	void decode8()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/i1/i2?15-5.-foo-bar&a=b&b=c");

		context.setNextPageRenderCount(5);

		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(ListenerRequestHandler.class);
		ListenerRequestHandler h = (ListenerRequestHandler)handler;

		IRequestablePage page = h.getPage();
		assertEquals(page.getRenderCount(), 5);
	}

	/**
	 *
	 */
	@Test
	void decode9()
	{
		assertThrows(StalePageException.class, () -> {
			Url url = Url.parse(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/i1/i2?15-5.-foo-bar&a=b&b=c");

			context.setNextPageRenderCount(6);

			IRequestHandler handler = encoder.mapRequest(getRequest(url));
			((IPageRequestHandler)handler).getPage();
		});
	}

	/**
	 *
	 */
	@Test
	void decodeNamedParameters()
	{
		Url url = Url
			.parse(MOUNT_PATH + "/fooValue/barValue/" + PAGE_CLASS_NAME + "/i1/i2?a=b&b=c");
		IRequestHandler handler = namedParametersEncoder.mapRequest(getRequest(url));

		assertTrue(handler instanceof RenderPageRequestHandler);
		RenderPageRequestHandler h = (RenderPageRequestHandler)handler;

		IRequestablePage page = h.getPage();
		checkPage(page, 1);

		PageParameters p = h.getPageParameters();
		assertEquals(2, p.getIndexedCount());

		assertEquals(4, p.getNamedKeys().size());
		assertEquals("fooValue", p.get("foo").toString());
		assertEquals("barValue", p.get("bar").toString());

	}

	/**
	 * WICKET-2993
	 */
	@Test
	void decode10()
	{
		// use String.class but any other non-Page will do the job as well
		Url url = Url.parse(String.class.getSimpleName());

		IRequestHandler handler = encoder.mapRequest(getRequest(url));
		assertNull(handler, "A non-page class should not create a request handler!");
	}

	/**
	 * https://issues.apache.org/jira/browse/WICKET-5996
	 */
	@Test
	void decode11()
	{
		Url url = Url.parse(MOUNT_PATH);
		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertNull(handler);
	}

	/**
	 * https://issues.apache.org/jira/browse/WICKET-5996
	 */
	@Test
	void decode12()
	{
		Url url = Url.parse(MOUNT_PATH + '/');
		IRequestHandler handler = encoder.mapRequest(getRequest(url));

		assertNull(handler);
	}

	/**
	 *
	 */
	@Test
	void encode1()
	{
		PageProvider provider = new PageProvider(MockPage.class, new PageParameters());
		provider.setPageSource(context);
		IRequestHandler handler = new BookmarkablePageRequestHandler(provider);
		Url url = encoder.mapHandler(handler);
		assertEquals(MOUNT_PATH + '/' + PAGE_CLASS_NAME, url.toString());
	}

	/**
	 *
	 */
	@Test
	void encode2()
	{
		PageParameters parameters = new PageParameters();
		parameters.set(0, "i1");
		parameters.set(1, "i2");
		parameters.set("a", "b", INamedParameters.Type.QUERY_STRING);
		parameters.set("b", "c", INamedParameters.Type.QUERY_STRING);
		PageProvider provider = new PageProvider(MockPage.class, parameters);
		provider.setPageSource(context);
		IRequestHandler handler = new BookmarkablePageRequestHandler(provider);
		Url url = encoder.mapHandler(handler);
		assertEquals(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/i1/i2?a=b&b=c", url.toString());
	}

	/**
	 *
	 */
	@Test
	void encode3()
	{
		PageParameters parameters = new PageParameters();
		parameters.set(0, "i1");
		parameters.set(1, "i2");
		parameters.set("a", "b", INamedParameters.Type.QUERY_STRING);
		parameters.set("b", "c", INamedParameters.Type.QUERY_STRING);

		PageProvider provider = new PageProvider(MockPage.class, parameters);
		provider.setPageSource(context);
		IRequestHandler handler = new BookmarkablePageRequestHandler(provider);
		Url url = encoder.mapHandler(handler);

		assertEquals(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/i1/i2?a=b&b=c", url.toString());
	}

	/**
	 *
	 */
	@Test
	void encode4()
	{
		MockPage page = new MockPage(15);
		page.getPageParameters().set(0, "i1");
		page.getPageParameters().set(1, "i2");
		page.getPageParameters().set("a", "b", INamedParameters.Type.QUERY_STRING);
		page.getPageParameters().set("b", "c", INamedParameters.Type.QUERY_STRING);
		page.setCreatedBookmarkable(true);

		IPageProvider provider = new PageProvider(page);
		IRequestHandler handler = new RenderPageRequestHandler(provider);
		Url url = encoder.mapHandler(handler);

		assertEquals(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/i1/i2?15&a=b&b=c", url.toString());
	}

	/**
	 *
	 */
	@Test
	void encode5()
	{
		MockPage page = new MockPage(15);
		page.getPageParameters().set(0, "i1");
		page.getPageParameters().set(1, "i2");
		page.getPageParameters().set("a", "b", INamedParameters.Type.QUERY_STRING);
		page.getPageParameters().set("b", "c", INamedParameters.Type.QUERY_STRING);

		page.setCreatedBookmarkable(false);

		IPageProvider provider = new PageProvider(page);
		IRequestHandler handler = new RenderPageRequestHandler(provider);
		Url url = encoder.mapHandler(handler);

		assertEquals(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/i1/i2?15&a=b&b=c", url.toString());
	}

	/**
	 *
	 */
	@Test
	void encode6()
	{
		MockPage page = new MockPage(15);
		page.getPageParameters().set(0, "i1");
		page.getPageParameters().set(1, "i2");
		page.getPageParameters().set("a", "b", INamedParameters.Type.QUERY_STRING);
		page.getPageParameters().set("b", "c", INamedParameters.Type.QUERY_STRING);

		// shouldn't make any difference for BookmarkableListenerRequestHandler,
		// as this explicitly says the url must be bookmarkable
		page.setCreatedBookmarkable(false);

		IRequestableComponent c = page.get("foo:bar");

		PageAndComponentProvider provider = new PageAndComponentProvider(page, c);
		IRequestHandler handler = new BookmarkableListenerRequestHandler(provider);

		Url url = encoder.mapHandler(handler);

		assertEquals(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/i1/i2?15-0.-foo-bar&a=b&b=c",
			url.toString());
	}

	/**
	 *
	 */
	@Test
	void encode7()
	{
		MockPage page = new MockPage(15);
		page.getPageParameters().set(0, "i1");
		page.getPageParameters().set(1, "i2");
		page.getPageParameters().set("a", "b", INamedParameters.Type.QUERY_STRING);
		page.getPageParameters().set("b", "c", INamedParameters.Type.QUERY_STRING);

		// shouldn't make any difference for BookmarkableListenerRequestHandler,
		// as this explicitly says the url must be bookmarkable
		page.setCreatedBookmarkable(false);

		IRequestableComponent c = page.get("foo:bar");

		PageAndComponentProvider provider = new PageAndComponentProvider(page, c);
		IRequestHandler handler = new BookmarkableListenerRequestHandler(provider, 4);

		Url url = encoder.mapHandler(handler);

		assertEquals(MOUNT_PATH + '/' + PAGE_CLASS_NAME + "/i1/i2?15-0.4-foo-bar&a=b&b=c",
			url.toString());
	}

	/**
	 *
	 */
	@Test
	void encode8()
	{
		MockPage page = new MockPage(15);
		page.setBookmarkable(true);
		page.setCreatedBookmarkable(true);
		page.setPageStateless(true);

		IPageProvider provider = new PageProvider(page);
		IRequestHandler handler = new RenderPageRequestHandler(provider);

		Url url = encoder.mapHandler(handler);

		assertEquals(MOUNT_PATH + '/' + PAGE_CLASS_NAME, url.toString());
	}

	/**
	 *
	 */
	@Test
	void encodeNamedPageParameters()
	{
		MockPage page = new MockPage(15);
		page.setBookmarkable(true);
		page.setCreatedBookmarkable(true);
		page.setPageStateless(true);

		IPageProvider provider = new PageProvider(page);
		page.getPageParameters().set("foo", "fooValue", INamedParameters.Type.PATH);
		page.getPageParameters().set("bar", "barValue", INamedParameters.Type.PATH);
		IRequestHandler handler = new RenderPageRequestHandler(provider);

		Url url = namedParametersEncoder.mapHandler(handler);

		assertEquals(MOUNT_PATH + "/fooValue/barValue/" + PAGE_CLASS_NAME, url.toString());
	}

	/**
	 * https://issues.apache.org/jira/browse/WICKET-3838
	 */
	@Test
	void encodeInnerClass()
	{
		InnerPage page = new OuterPage.InnerPage();
		IPageProvider provider = new PageProvider(page);
		IRequestHandler handler = new BookmarkablePageRequestHandler(provider);

		Url url = innerClassEncoder.mapHandler(handler);

		assertEquals(MOUNT_PATH + '/' + "PackageMapperTest$OuterPage$InnerPage", url.toString());
	}

	/**
	 * https://issues.apache.org/jira/browse/WICKET-3838
	 */
	@Test
	void decodeInnerClass()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + "PackageMapperTest$OuterPage$InnerPage");
		IRequestHandler handler = innerClassEncoder.mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(RenderPageRequestHandler.class);
		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
		assertEquals("InnerPage", page.getClass().getSimpleName());
		assertEquals(0, page.getPageParameters().getIndexedCount());
		assertTrue(page.getPageParameters().getNamedKeys().isEmpty());
	}

	/**
	 * https://issues.apache.org/jira/browse/WICKET-3941
	 */
	@Test
	void encodeAlias()
	{
		MockPage page = new MockPage(15);
		page.setBookmarkable(true);
		page.setCreatedBookmarkable(true);
		page.setPageStateless(true);

		IPageProvider provider = new PageProvider(page);
		IRequestHandler handler = new RenderPageRequestHandler(provider);

		Url url = aliasEncoder.mapHandler(handler);

		assertEquals(MOUNT_PATH + '/' + ALIAS, url.toString());
	}

	/**
	 * https://issues.apache.org/jira/browse/WICKET-3941
	 */
	@Test
	void decodeAlias()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + ALIAS + "?15");
		IRequestHandler handler = aliasEncoder.mapRequest(getRequest(url));

		assertThat(handler).isInstanceOf(RenderPageRequestHandler.class);
		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
		checkPage(page, 15);
		assertEquals(PAGE_CLASS_NAME, page.getClass().getSimpleName());
	}

	/**
	 * https://issues.apache.org/jira/browse/WICKET-5565
	 */
	@Test
	void testGetCompatibilityScore()
	{
		Url url = Url.parse(MOUNT_PATH + '/' + "MyPage");
		MockWebRequest request = new MockWebRequest(url);
		int score = encoder.getCompatibilityScore(request);

		assertEquals(4, score);

		url = Url.parse(MOUNT_PATH + "/foo/bar/" + "MyPage");
		request = new MockWebRequest(url);
		score = namedParametersEncoder.getCompatibilityScore(request);

		assertEquals(6, score);
	}

	public static class OuterPage extends MockPage
	{
		private static final long serialVersionUID = 1L;

		public static class InnerPage extends MockPage
		{
			private static final long serialVersionUID = 1L;
		}
	}
}
