/*
 * Copyright 2011-2018 the original author or authors.
 *
 * 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 org.springframework.data.mongodb.core.mapping;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.util.ClassTypeInformation;

/**
 * Unit tests for {@link BasicMongoPersistentEntity}.
 *
 * @author Oliver Gierke
 * @author Christoph Strobl
 */
@RunWith(MockitoJUnitRunner.class)
public class BasicMongoPersistentEntityUnitTests {

	@Mock ApplicationContext context;
	@Mock MongoPersistentProperty propertyMock;

	@Test
	public void subclassInheritsAtDocumentAnnotation() {

		BasicMongoPersistentEntity<Person> entity = new BasicMongoPersistentEntity<Person>(
				ClassTypeInformation.from(Person.class));
		assertThat(entity.getCollection(), is("contacts"));
	}

	@Test
	public void evaluatesSpELExpression() {

		MongoPersistentEntity<Company> entity = new BasicMongoPersistentEntity<Company>(
				ClassTypeInformation.from(Company.class));
		assertThat(entity.getCollection(), is("35"));
	}

	@Test // DATAMONGO-65, DATAMONGO-1108
	public void collectionAllowsReferencingSpringBean() {

		CollectionProvider provider = new CollectionProvider();
		provider.collectionName = "reference";

		when(context.getBean("myBean")).thenReturn(provider);
		when(context.containsBean("myBean")).thenReturn(true);

		BasicMongoPersistentEntity<DynamicallyMapped> entity = new BasicMongoPersistentEntity<DynamicallyMapped>(
				ClassTypeInformation.from(DynamicallyMapped.class));
		entity.setApplicationContext(context);

		assertThat(entity.getCollection(), is("reference"));

		provider.collectionName = "otherReference";
		assertThat(entity.getCollection(), is("otherReference"));
	}

	@Test // DATAMONGO-937
	public void shouldDetectLanguageCorrectly() {

		BasicMongoPersistentEntity<DocumentWithLanguage> entity = new BasicMongoPersistentEntity<DocumentWithLanguage>(
				ClassTypeInformation.from(DocumentWithLanguage.class));
		assertThat(entity.getLanguage(), is("spanish"));
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Test(expected = MappingException.class) // DATAMONGO-1053
	public void verifyShouldThrowExceptionForInvalidTypeOfExplicitLanguageProperty() {

		BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
				ClassTypeInformation.from(AnyDocument.class));

		when(propertyMock.isExplicitLanguageProperty()).thenReturn(true);
		when(propertyMock.getActualType()).thenReturn((Class) Number.class);

		entity.addPersistentProperty(propertyMock);
		entity.verify();
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Test // DATAMONGO-1053
	public void verifyShouldPassForStringAsExplicitLanguageProperty() {

		BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
				ClassTypeInformation.from(AnyDocument.class));
		when(propertyMock.isExplicitLanguageProperty()).thenReturn(true);
		when(propertyMock.getActualType()).thenReturn((Class) String.class);
		entity.addPersistentProperty(propertyMock);

		entity.verify();

		verify(propertyMock, times(1)).isExplicitLanguageProperty();
		verify(propertyMock, times(1)).getActualType();
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Test // DATAMONGO-1053
	public void verifyShouldIgnoreNonExplicitLanguageProperty() {

		BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
				ClassTypeInformation.from(AnyDocument.class));
		when(propertyMock.isExplicitLanguageProperty()).thenReturn(false);
		entity.addPersistentProperty(propertyMock);

		entity.verify();

		verify(propertyMock, times(1)).isExplicitLanguageProperty();
		verify(propertyMock, never()).getActualType();
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Test(expected = MappingException.class) // DATAMONGO-1157
	public void verifyShouldThrowErrorForLazyDBRefOnFinalClass() {

		BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
				ClassTypeInformation.from(AnyDocument.class));
		org.springframework.data.mongodb.core.mapping.DBRef dbRefMock = mock(
				org.springframework.data.mongodb.core.mapping.DBRef.class);
		when(propertyMock.isDbReference()).thenReturn(true);
		when(propertyMock.getDBRef()).thenReturn(dbRefMock);
		when(dbRefMock.lazy()).thenReturn(true);
		when(propertyMock.getActualType()).thenReturn((Class) Class.class);
		entity.addPersistentProperty(propertyMock);

		entity.verify();
	}

	@Test(expected = MappingException.class) // DATAMONGO-1157
	public void verifyShouldThrowErrorForLazyDBRefArray() {

		BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
				ClassTypeInformation.from(AnyDocument.class));
		org.springframework.data.mongodb.core.mapping.DBRef dbRefMock = mock(
				org.springframework.data.mongodb.core.mapping.DBRef.class);
		when(propertyMock.isDbReference()).thenReturn(true);
		when(propertyMock.getDBRef()).thenReturn(dbRefMock);
		when(dbRefMock.lazy()).thenReturn(true);
		when(propertyMock.isArray()).thenReturn(true);
		entity.addPersistentProperty(propertyMock);

		entity.verify();
	}

	@Test // DATAMONGO-1157
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void verifyShouldPassForLazyDBRefOnNonArrayNonFinalClass() {

		BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
				ClassTypeInformation.from(AnyDocument.class));
		org.springframework.data.mongodb.core.mapping.DBRef dbRefMock = mock(
				org.springframework.data.mongodb.core.mapping.DBRef.class);
		when(propertyMock.isDbReference()).thenReturn(true);
		when(propertyMock.getDBRef()).thenReturn(dbRefMock);
		when(dbRefMock.lazy()).thenReturn(true);
		when(propertyMock.getActualType()).thenReturn((Class) Object.class);
		entity.addPersistentProperty(propertyMock);

		entity.verify();

		verify(propertyMock, times(1)).isDbReference();
	}

	@Test // DATAMONGO-1157
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void verifyShouldPassForNonLazyDBRefOnFinalClass() {

		BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
				ClassTypeInformation.from(AnyDocument.class));
		org.springframework.data.mongodb.core.mapping.DBRef dbRefMock = mock(
				org.springframework.data.mongodb.core.mapping.DBRef.class);
		when(propertyMock.isDbReference()).thenReturn(true);
		when(propertyMock.getDBRef()).thenReturn(dbRefMock);
		when(dbRefMock.lazy()).thenReturn(false);
		entity.addPersistentProperty(propertyMock);

		entity.verify();

		verify(dbRefMock, times(1)).lazy();
	}

	@Test // DATAMONGO-1291
	public void metaInformationShouldBeReadCorrectlyFromInheritedDocumentAnnotation() {

		BasicMongoPersistentEntity<DocumentWithCustomAnnotation> entity = new BasicMongoPersistentEntity<DocumentWithCustomAnnotation>(
				ClassTypeInformation.from(DocumentWithCustomAnnotation.class));

		assertThat(entity.getCollection(), is("collection-1"));
	}

	@Test // DATAMONGO-1373
	public void metaInformationShouldBeReadCorrectlyFromComposedDocumentAnnotation() {

		BasicMongoPersistentEntity<DocumentWithComposedAnnotation> entity = new BasicMongoPersistentEntity<DocumentWithComposedAnnotation>(
				ClassTypeInformation.from(DocumentWithComposedAnnotation.class));

		assertThat(entity.getCollection(), is("custom-collection"));
	}

	@Document("contacts")
	class Contact {}

	class Person extends Contact {}

	@Document("#{35}")
	class Company {}

	@Document("#{myBean.collectionName}")
	class DynamicallyMapped {}

	class CollectionProvider {
		String collectionName;

		public String getCollectionName() {
			return collectionName;
		}
	}

	@Document(language = "spanish")
	static class DocumentWithLanguage {}

	static class AnyDocument {}

	@CustomDocumentAnnotation
	static class DocumentWithCustomAnnotation {}

	@ComposedDocumentAnnotation
	static class DocumentWithComposedAnnotation {}

	@Retention(RetentionPolicy.RUNTIME)
	@Target({ ElementType.TYPE })
	@Document("collection-1")
	static @interface CustomDocumentAnnotation {
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target({ ElementType.TYPE })
	@Document
	static @interface ComposedDocumentAnnotation {

		@AliasFor(annotation = Document.class, attribute = "collection")
		String name() default "custom-collection";
	}
}
