/**
 * Copyright (C) 2015 Orange
 * 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 com.francetelecom.clara.cloud.db.liquibase;

import static org.junit.Assert.*;

import java.sql.SQLException;
import javax.sql.DataSource;
import liquibase.exception.LiquibaseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * This test class verifies that some 'complex' liquibase refactoring commands work as expected
 * This test class shall be enhanced when more complex refactorings need to be tested
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration()
public class CheckVariousRefactoringCommandsIT {

	@Autowired
	public DataSource datasource;

	// liquibase wrapper used to call liquibase functions  	
	private LiquibaseTestWrapper liquibaseWrapper = new LiquibaseTestWrapper();

	// test db credentials used by liquibaseTestWrapper
	private String paasTestDbUrl;
	private String paasTestDbUser;
	private String paasTestDbPassword;

	// jdbcTemplate used to execute some sql requests
	private JdbcTemplate jdbcTemplate;
	
	// Default changelog used to set-up test data
	public String basicInitialChangeLofFile = "classpath:./test-changesets/initial-changeset.xml";

	
	/**
	 * Setup of test data: set attributes, purge and initialize test datbase 
	 */
	@Before
	public void setup() throws SQLException, LiquibaseException {
		// Init jdbcTemplate
		this.jdbcTemplate = new JdbcTemplate(datasource);
		
		// Set test db properties
		DriverManagerDataSource ds = (DriverManagerDataSource)datasource;
		this.paasTestDbUrl = ds.getUrl();
		this.paasTestDbUser = ds.getUsername();
		this.paasTestDbPassword = ds.getPassword();

		// purge test database
		liquibaseWrapper.purgeDatabase(paasTestDbUrl, paasTestDbUser, paasTestDbPassword);
		
		// init database state
		liquibaseWrapper.applyChangeLog(paasTestDbUrl, paasTestDbUser, paasTestDbPassword, basicInitialChangeLofFile);
	}
	
	/**
	 * Test clean-up: purge test database 
	 */
	@After 
	public void teardown() throws SQLException, LiquibaseException {
		// purge test database
		liquibaseWrapper.purgeDatabase(paasTestDbUrl, paasTestDbUser, paasTestDbPassword);
	}
	
	
	/** 
	 * Given a database with a table TABLE_TEST_1 with some lines
	 * When liquibase executes following changeset: 
	 * 		<renameTable oldTableName="TABLE_TEST_1" newTableName="table_test_2"/>
	 * Then table 'TABLE_TEST_2' can be requested
	 * And  table 'TABLE_TEST_1' does not exist anymore
	 */
	@Test
	public void testRenameTable() throws SQLException, LiquibaseException {
		// Prepare test data
		jdbcTemplate.execute("insert into table_test_1 values (1717,'test-1717')");

		// Run test
		applyChangeLog("classpath:./test-changesets/rename-table-changeset.xml");

		// Assert table has been renamed
		int n = jdbcTemplate.queryForObject("select count(*) from table_test_2 where id=1717 and text='test-1717'", Integer.class);
		assertEquals(1,n);
		// And old name does not exist
		try {
			jdbcTemplate.queryForObject("select count(*) from table_test_1", Integer.class);
			fail("table_test_1 still exists");
		} catch(DataAccessException e) {
			// ignore as we expect an exception
		}
	}

	/** 
	 * Given a database with a table TABLE_TEST_1 with some lines
	 * When liquibase executes following changeset: 
	 * 		<addColumn tableName="table_test_1">
     *			<column name="new_data" type="varchar(255)" defaultValue="default_value_for_new_column"/>
	 *		</addColumn>
	 * Then 'new_data' column on existing lines is 'default_value_for_new_column'
	 */
	@Test
	public void testNewColumnWithDefaultValue() throws SQLException, LiquibaseException {
		// Prepare test data
		jdbcTemplate.execute("insert into table_test_1 values (1717,'test-1717')");

		// Run test
		applyChangeLog("classpath:./test-changesets/new-column-changeset.xml");

		// Assert new column can be requested and has a default value
		int n = jdbcTemplate.queryForObject("select count(*) from table_test_1 where id=1717 and NEW_DATA='default_value_for_new_column'", Integer.class);
		assertEquals(1,n);
	}

	/** 
	 * Given a database with no sequence
	 * When liquibase executes following changeset: <createSequence sequenceName="my_sequence" incrementBy="2" startValue="1"/>
	 * Then the first value of "my_sequence" is 1
	 * And  the next  value of "my_sequence" is 3
	 */
	@Test
	public void testCreateSequence() throws SQLException, LiquibaseException {
		// Run test
		applyChangeLog("classpath:./test-changesets/create-sequence-changeset.xml");

		// Assert new sequence is created by requesting it twice
		int v1 = jdbcTemplate.queryForObject("select nextval('my_sequence')", Integer.class);
		assertEquals(1,v1);	
		int v2 = jdbcTemplate.queryForObject("select nextval('my_sequence')", Integer.class);
		assertEquals(3,v2);	
	}
	
	/**
	 * This test verifies a pattern to fix art #95049
	 * Given a database in which a changeset adding a not nullable column has been commited without defining a default value
	 * And they are some data in the updated table
	 * When liquibase applies a similar changeset that defines a default value
	 * Then the fixed changeset should be executed without error  
	 * @throws LiquibaseException 
	 * @throws SQLException 
	 */
	@Test
	public void testFixInvalidChangeSetAddNotNullColumnWithNoDefaultValue() throws SQLException, LiquibaseException {
		// Prepare test data: purge test table, apply incorrect change set and insert data in table
		jdbcTemplate.execute("delete from table_test_1");
		applyChangeLog("classpath:./test-changesets/new-column-no-default-value-invalid-changeset.xml");
		jdbcTemplate.execute("insert into table_test_1 values (1717,'test-1717','new-data-1717')");
		
		// Run test
		applyChangeLog("classpath:./test-changesets/new-column-no-default-value-fixed-changeset.xml");		
		
		// Assertion: nothing;  changeset should simply be applied without error	
	}
	
	/**
	 * This test verifies a pattern to implement database denormalization
	 * Given a database with 3 tables table_1, table_1 and table_3 modeling an inheritance entity_1 > entity_2 > entity_3
	 * When schema is denormalized
	 * Then table_1 and table_2 data are moved into table_3
	 */
	@Test
	public void testDenormalization() throws SQLException, LiquibaseException {
		// Prepare test data: complete initialization with additional change set
		applyChangeLog("classpath:./test-changesets/denormalization-init-changeset.xml");
		
		// Run test
		applyChangeLog("classpath:./test-changesets/denormalization-migration-changeset.xml");	

		// Assertions
		SqlRowSet results = jdbcTemplate.queryForRowSet("select id, c1, c2, c3 from table_3");
		assertTrue("no data in SqlRowSet results", results.first());
		do {
			int id = results.getInt("id");
			String c1 = results.getString("c1");
			String c2 = results.getString("c2");
			String c3 = results.getString("c3");
			assertEquals("t1v"+id, c1);
			assertEquals("t2v"+id, c2);
			assertEquals("t3v"+id, c3);
		} while(results.next());
		
	}
	
	// === Utility methods
	
	/**
	 * apply change log by calling liquibase test wrapper
	 * @param changeLog
	 * @throws SQLException
	 * @throws LiquibaseException
	 */
	private void applyChangeLog(String changeLog) throws SQLException, LiquibaseException {
		liquibaseWrapper.applyChangeLog(paasTestDbUrl, paasTestDbUser, paasTestDbPassword,changeLog);		
	}
	
}
