package com.shin1ogawa.entity;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Transaction;

import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.api.datastore.dev.LocalDatastoreService;
import com.google.appengine.tools.development.ApiProxyLocalImpl;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.ApiProxyException;
import com.google.apphosting.api.ApiProxy.Environment;
import com.google.apphosting.api.ApiProxy.LogRecord;
import com.shin1ogawa.AbstractRelasionShipTest;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;

import static org.junit.Assert.assertThat;

public class LLParentTest {

	@Test
	public void JDOで保存してJDOで読む() {
		Key parentKey = saveByJDO();
		assertByJDO(parentKey);
	}

	@Test
	public void JDOで保存して低レベルAPIで読む() throws EntityNotFoundException {
		Key parentKey = saveByJDO();
		assertByLowLevelApi(parentKey);
	}

	@Test
	public void 低レベルAPIで保存して低レベルAPIで読む() throws EntityNotFoundException {
		Key parentKey = saveByLowLevelApi();
		assertByLowLevelApi(parentKey);
	}

	@Test
	public void 低レベルAPIで保存してJDOで読む() throws EntityNotFoundException {
		Key parentKey = saveByLowLevelApi();
		assertByJDO(parentKey);
	}

	private void assertByJDO(Key parentKey) {
		PersistenceManager pm = factory.getPersistenceManager();
		LLParent entity = pm.getObjectById(LLParent.class, parentKey);
		assertThat(entity.getKey(), is(equalTo(parentKey)));
		assertThat((String) entity.getProperty1(), is(equalTo("parent:property1value")));
		assertThat(entity.getProperty2().getValue(), is(equalTo("parent:property2value")));
		assertThat(entity.getChildA(), is(not(nullValue())));
		assertThat(entity.getChildA().getProperty1(), is(equalTo("childA:property1value")));
		assertThat(entity.getChildBList(), is(not(nullValue())));
		assertThat(entity.getChildBList().get(0).getProperty1(), is(equalTo("childB1:property1value")));
		assertThat(entity.getChildBList().get(1).getProperty1(), is(equalTo("childB2:property1value")));
		pm.close();
	}

	private void assertByLowLevelApi(Key parentKey) throws EntityNotFoundException {
		DatastoreService service = DatastoreServiceFactory.getDatastoreService();
		Entity entity = service.get(parentKey);
		assertThat(entity.getKey(), is(equalTo(parentKey)));
		assertThat((String) entity.getProperty("property1"), is(equalTo("parent:property1value")));
		assertThat(((Text) entity.getProperty("property2")).getValue(), is(equalTo("parent:property2value")));
		// 以下のふたつは自動的に取得されない…といぅか、属性すら存在しない。
		assertThat(entity.hasProperty("childA"), is(equalTo(false)));
		assertThat(entity.hasProperty("childBList"), is(equalTo(false)));
		// OneToOneのChildAを取得する。
		Query queryA = new Query(LLChildA.class.getSimpleName(), entity.getKey());
		Entity childA = service.prepare(queryA).asSingleEntity();
		assertThat((String) childA.getProperty("property1"), is(equalTo("childA:property1value")));
		// OneToManyのChildBを取得する。
		Query queryB = new Query(LLChildB.class.getSimpleName(), entity.getKey()).addSort("property1");
		List<Entity> childBList = service.prepare(queryB).asList(FetchOptions.Builder.withOffset(0));
		assertThat((String) childBList.get(0).getProperty("property1"), is(equalTo("childB1:property1value")));
		assertThat((String) childBList.get(1).getProperty("property1"), is(equalTo("childB2:property1value")));
	}

	private Key saveByJDO() {
		LLParent parent = new LLParent();
		parent.setProperty1("parent:property1value");
		parent.setProperty2(new Text("parent:property2value"));

		LLChildA childA = new LLChildA();
		childA.setProperty1("childA:property1value");
		parent.setChildA(childA);

		List<LLChildB> childBList = new ArrayList<LLChildB>();
		LLChildB childB1 = new LLChildB();
		childB1.setProperty1("childB1:property1value");
		childBList.add(childB1);
		LLChildB childB2 = new LLChildB();
		childB2.setProperty1("childB2:property1value");
		childBList.add(childB2);
		parent.setChildBList(childBList);

		PersistenceManager pm = factory.getPersistenceManager();
		Transaction transaction = pm.currentTransaction();
		transaction.begin();
		parent = pm.makePersistent(parent);
		transaction.commit();
		pm.close();
		return parent.getKey();
	}

	private Key saveByLowLevelApi() {
		Entity parent = new Entity(LLParent.class.getSimpleName());
		parent.setProperty("property1", "parent:property1value");
		parent.setProperty("property2", new Text("parent:property2value"));

		DatastoreService service = DatastoreServiceFactory.getDatastoreService();
		com.google.appengine.api.datastore.Transaction transaction = service.beginTransaction();

		Key parentKey = service.put(transaction, parent);

		List<Entity> entities = new ArrayList<Entity>();
		Entity childA = new Entity(LLChildA.class.getSimpleName(), parentKey);
		childA.setProperty("property1", "childA:property1value");
		entities.add(childA);

		Entity childB1 = new Entity(LLChildB.class.getSimpleName(), parentKey);
		childB1.setProperty("property1", "childB1:property1value");
		entities.add(childB1);
		Entity childB2 = new Entity(LLChildB.class.getSimpleName(), parentKey);
		childB2.setProperty("property1", "childB2:property1value");
		entities.add(childB2);

		service.put(transaction, entities);
		transaction.commit();

		return parentKey;
	}

	static PersistenceManagerFactory factory;

	@BeforeClass
	public static void setUpBeforeClass() {
		if (AbstractRelasionShipTest.factory == null) {
			factory = JDOHelper.getPersistenceManagerFactory("transactions-optional");
		} else {
			factory = AbstractRelasionShipTest.factory;
		}
	}

	@Before
	public void setUp() {
		ApiProxy.setEnvironmentForCurrentThread(new ApiProxy.Environment() {
			public String getAppId() {
				return "unit test";
			}

			public Map<String, Object> getAttributes() {
				return new HashMap<String, Object>();
			}

			public String getAuthDomain() {
				return "gmail.com";
			}

			public String getEmail() {
				return "hoge@gmail.com";
			}

			public String getRequestNamespace() {
				return "";
			}

			public String getVersionId() {
				return "1";
			}

			public boolean isAdmin() {
				return false;
			}

			public boolean isLoggedIn() {
				return true;
			}
		});
		ApiProxy.setDelegate(new MyDelegate(new File("target/lowLevelApiTest")) {
		});
		ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
		proxy.setProperty(LocalDatastoreService.NO_STORAGE_PROPERTY, Boolean.TRUE.toString());
	}

	@After
	public void tearDown() {
		ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
		LocalDatastoreService datastoreService = (LocalDatastoreService) proxy.getService("datastore_v3");
		datastoreService.clearProfiles();
		datastoreService.stop();
		ApiProxy.setDelegate(null);
		ApiProxy.setEnvironmentForCurrentThread(null);
	}

	static class MyDelegate extends ApiProxyLocalImpl {

		MyDelegate(File folder) {
			super(folder);
		}

		public byte[] makeSyncCall(Environment environment, String packageName, String methodName, byte[] request)
				throws ApiProxyException {
			if (packageName.equals("datastore_v3")) {
				System.out.println("makeSyncCall:" + packageName + ":" + methodName);
			}
			return super.makeSyncCall(environment, packageName, methodName, request);
		}

		public void log(Environment environment, LogRecord record) {
			super.log(environment, record);
		}
	}
}
