/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.userguide.sql;

import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;

import org.hibernate.Session;
import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ResultCheckStyle;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLDeleteAll;
import org.hibernate.annotations.SQLInsert;
import org.hibernate.annotations.SQLUpdate;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;

import org.hibernate.testing.TestForIssue;
import org.junit.Before;
import org.junit.Test;

import org.jboss.logging.Logger;

import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;

/**
 * This test is for replicating the HHH-10557 issue.
 *
 * @author Vlad Mihalcea
 */
public class CollectionLoaderTest extends BaseEntityManagerFunctionalTestCase {

	private static final Logger log = Logger.getLogger( CollectionLoaderTest.class );

	@Override
	protected Class<?>[] getAnnotatedClasses() {
		return new Class<?>[] {
			Person.class
		};
	}

	@Before
	public void init() {
		doInJPA( this::entityManagerFactory, entityManager -> {
			Session session = entityManager.unwrap( Session.class );
			session.doWork( connection -> {
				try(Statement statement = connection.createStatement(); ) {
					statement.executeUpdate( "ALTER TABLE person ADD COLUMN valid boolean" );
					statement.executeUpdate( "ALTER TABLE Person_phones ADD COLUMN valid boolean" );
				}
			} );
		});
	}

	@Test @TestForIssue( jiraKey = "HHH-10557")
	public void test_HHH10557() {

		Person _person = doInJPA( this::entityManagerFactory, entityManager -> {
			Person person = new Person();
			person.setName( "John Doe" );
			entityManager.persist( person );
			person.getPhones().add( "123-456-7890" );
			person.getPhones().add( "123-456-0987" );
			return person;
		} );

		try {

			doInJPA( this::entityManagerFactory, entityManager -> {
				Long postId = _person.getId();
				Person person = entityManager.find( Person.class, postId );
				assertEquals( 2, person.getPhones().size() );
				person.getPhones().remove( 0 );
				person.setName( "Mr. John Doe" );
			} );


			doInJPA( this::entityManagerFactory, entityManager -> {
				Long postId = _person.getId();
				Person person = entityManager.find( Person.class, postId );
				assertEquals( 1, person.getPhones().size() );
			} );
		}
		catch (Exception e) {
			log.error( "Throws NullPointerException because the bag is not initialized by the @Loader" );
		}
	}


	//tag::sql-custom-crud-example[]
	@Entity(name = "Person")
	@SQLInsert(
		sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) ",
		check = ResultCheckStyle.COUNT
	)
	@SQLUpdate(
		sql = "UPDATE person SET name = ? where id = ? ")
	@SQLDelete(
		sql = "UPDATE person SET valid = false WHERE id = ? ")
	@Loader(namedQuery = "find_valid_person")
	@NamedNativeQueries({
		@NamedNativeQuery(
			name = "find_valid_person",
			query = "SELECT id, name " +
					"FROM person " +
					"WHERE id = ? and valid = true",
			resultClass = Person.class
		),
		@NamedNativeQuery(
			name = "find_valid_phones",
			query = "SELECT person_id, phones " +
					"FROM Person_phones " +
					"WHERE person_id = ? and valid = true "
		)
	})
	public static class Person {

		@Id
		@GeneratedValue
		private Long id;

		private String name;

		@ElementCollection
		@SQLInsert(
			sql = "INSERT INTO person_phones (person_id, phones, valid) VALUES (?, ?, true) ")
		@SQLDeleteAll(
			sql = "UPDATE person_phones SET valid = false WHERE person_id = ?")
		@Loader(namedQuery = "find_valid_phones")
		private List<String> phones = new ArrayList<>();

		public Long getId() {
			return id;
		}

		public void setId(Long id) {
			this.id = id;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public List<String> getPhones() {
			return phones;
		}
	}
	//end::sql-custom-crud-example[]

}
