/**
 * Copyright (C) 2016 Instacount Inc. ([email protected])
 *
 * 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 io.instacount.appengine.counter.service;

import static org.junit.Assert.*;

import java.math.BigInteger;

import io.instacount.appengine.counter.data.CounterShardOperationData;
import org.junit.After;
import org.junit.Before;

import com.google.appengine.api.capabilities.CapabilitiesService;
import com.google.appengine.api.capabilities.CapabilitiesServiceFactory;
import com.google.appengine.api.capabilities.Capability;
import com.google.appengine.api.capabilities.CapabilityStatus;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.urlfetch.URLFetchServicePb.URLFetchRequest;
import com.google.appengine.tools.development.testing.LocalCapabilitiesServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalMemcacheServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.impl.translate.opt.joda.JodaTimeTranslators;
import com.googlecode.objectify.util.Closeable;
import io.instacount.appengine.counter.Counter;
import io.instacount.appengine.counter.data.CounterData;
import io.instacount.appengine.counter.data.CounterShardData;

/**
 * An abstract base class for testing {@link ShardedCounterServiceImpl}
 *
 * @author David Fuelling
 */
public abstract class AbstractShardedCounterServiceTest
{
	protected static final String DELETE_COUNTER_SHARD_QUEUE_NAME = "deleteCounterShardQueue";

	protected static final String TEST_COUNTER1 = "test-counter1";

	protected static final String TEST_COUNTER2 = "test-counter2";

	protected ShardedCounterService shardedCounterService;
	protected ShardedCounterServiceImpl shardedCounterServiceImpl;

	protected LocalTaskQueueTestConfig.TaskCountDownLatch countdownLatch;

	protected LocalServiceTestHelper helper = new LocalServiceTestHelper(
		// No Eventual Consistency, by default
		new LocalDatastoreServiceTestConfig().setDefaultHighRepJobPolicyUnappliedJobPercentage(0f),
		new LocalMemcacheServiceTestConfig(), new LocalTaskQueueTestConfig());

	protected MemcacheService memcache;

	protected CapabilitiesService capabilitiesService;

	public static class DeleteShardedCounterDeferredCallback extends LocalTaskQueueTestConfig.DeferredTaskCallback
	{
		private static final long serialVersionUID = -2113612286521272160L;

		@Override
		protected int executeNonDeferredRequest(URLFetchRequest req)
		{
			// Do Nothing in this callback. This callback is only here to
			// simulate a task-queue run.

			// See here:
			// http://stackoverflow.com/questions/6632809/gae-unit-testing-taskqueue-with-testbed
			// The dev app server is single-threaded, so it can't run tests in
			// the background properly. Thus, we test that the task was added to
			// the queue properly. Then, we manually run the shard-deletion code
			// and assert that it's working properly.
			return 200;
		}
	}

	@Before
	public void setUp() throws Exception
	{
		// Don't call super.setUp because we initialize slightly differently
		// here...

		countdownLatch = new LocalTaskQueueTestConfig.TaskCountDownLatch(1);

		// See
		// http://www.ensor.cc/2010/11/unit-testing-named-queues-spring.html
		// NOTE: THE QUEUE XML PATH RELATIVE TO WEB APP ROOT, More info
		// below
		// http://stackoverflow.com/questions/11197058/testing-non-default-app-engine-task-queues
		final LocalTaskQueueTestConfig localTaskQueueConfig = new LocalTaskQueueTestConfig()
			.setDisableAutoTaskExecution(false).setQueueXmlPath("src/test/resources/queue.xml")
			.setTaskExecutionLatch(countdownLatch).setCallbackClass(DeleteShardedCounterDeferredCallback.class);

		// Use a different queue.xml for testing purposes
		helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy(),
			new LocalMemcacheServiceTestConfig(), new LocalCapabilitiesServiceTestConfig(), localTaskQueueConfig);
		helper.setUp();

		memcache = MemcacheServiceFactory.getMemcacheService();
		capabilitiesService = CapabilitiesServiceFactory.getCapabilitiesService();

		// New Objectify 5.1 Way. See https://groups.google.com/forum/#!topic/objectify-appengine/O4FHC_i7EGk
		this.session = ObjectifyService.begin();

		// Enable Joda Translators
		JodaTimeTranslators.add(ObjectifyService.factory());

		ObjectifyService.factory().register(CounterData.class);
		ObjectifyService.factory().register(CounterShardData.class);
		ObjectifyService.factory().register(CounterShardOperationData.class);

		shardedCounterServiceImpl = new ShardedCounterServiceImpl();
		this.shardedCounterService = shardedCounterServiceImpl;
	}

	// New Objectify 5.1 Way. See https://groups.google.com/forum/#!topic/objectify-appengine/O4FHC_i7EGk
	protected Closeable session;

	@After
	public void tearDown()
	{
		// New Objectify 5.1 Way. See https://groups.google.com/forum/#!topic/objectify-appengine/O4FHC_i7EGk
		this.session.close();

		this.helper.tearDown();
	}

	// ///////////////////////
	// Helper Methods
	// ///////////////////////

	/**
	 * Helper method to perform assertions on a specified counter.
	 *
	 * @param counter
	 */
	protected void assertCounter(Counter counter, String expectedCounterName, BigInteger expectedCounterCount)
	{
		assertTrue(counter != null);
		assertEquals(expectedCounterCount, counter.getCount());
		assertEquals(expectedCounterName, counter.getName());
	}

	/**
	 * Create a new {@link CounterService} with the specified "number of
	 * initial shards" as found in {@code numInitialShards} .
	 *
	 * @param numInitialShards
	 *
	 * @return
	 */
	protected ShardedCounterService initialShardedCounterService(int numInitialShards)
	{
		ShardedCounterServiceConfiguration config = new ShardedCounterServiceConfiguration.Builder()
			.withNumInitialShards(numInitialShards).build();

		ShardedCounterService service = new ShardedCounterServiceImpl(MemcacheServiceFactory.getMemcacheService(),
			config);
		return service;
	}

	/**
	 * @return {@code true} if Memcache is usable; {@code false} otherwise.
	 */
	protected boolean isMemcacheAvailable()
	{
		CapabilityStatus capabilityStatus = this.capabilitiesService.getStatus(Capability.MEMCACHE).getStatus();
		return capabilityStatus == CapabilityStatus.ENABLED;
	}

	protected void disableMemcache()
	{
		// See
		// http://www.ensor.cc/2010/11/unit-testing-named-queues-spring.html
		// NOTE: THE QUEUE XML PATH RELATIVE TO WEB APP ROOT, More info
		// below
		// http://stackoverflow.com/questions/11197058/testing-non-default-app-engine-task-queues
		final LocalTaskQueueTestConfig localTaskQueueConfig = new LocalTaskQueueTestConfig()
			.setDisableAutoTaskExecution(false).setQueueXmlPath("src/test/resources/queue.xml")
			.setTaskExecutionLatch(countdownLatch).setCallbackClass(DeleteShardedCounterDeferredCallback.class);

		Capability testOne = new Capability("memcache");
		CapabilityStatus testStatus = CapabilityStatus.DISABLED;
		// Initialize
		LocalCapabilitiesServiceTestConfig capabilityStatusConfig = new LocalCapabilitiesServiceTestConfig()
			.setCapabilityStatus(testOne, testStatus);

		// Use a different queue.xml for testing purposes
		helper = new LocalServiceTestHelper(
			new LocalDatastoreServiceTestConfig().setDefaultHighRepJobPolicyUnappliedJobPercentage(0.01f),
			new LocalMemcacheServiceTestConfig(), localTaskQueueConfig, capabilityStatusConfig);
		helper.setUp();
	}

}