/**
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
 *
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
 * graphic logo is a trademark of OpenMRS Inc.
 */
package org.openmrs.util.databasechange;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import org.openmrs.util.DatabaseUtil;
import org.openmrs.util.OpenmrsConstants;

import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.CustomChangeException;
import liquibase.exception.DatabaseException;
import liquibase.exception.SetupException;
import liquibase.exception.ValidationErrors;
import liquibase.resource.ResourceAccessor;

/**
 * This changeset creates provider accounts for orderers that have no providers accounts, and then
 * converts the orderer from being users to providers
 */
public class ConvertOrderersToProviders implements CustomTaskChange {
	
	@Override
	public void execute(Database database) throws CustomChangeException {
		JdbcConnection connection = (JdbcConnection) database.getConnection();
		try {
			List<List<Object>> usersAndProviders = getUsersAndProviders(connection);
			convertOrdererToProvider(connection, usersAndProviders);
		}
		catch (Exception e) {
			throw new CustomChangeException(e);
		}
	}
	
	private List<List<Object>> getUsersAndProviders(JdbcConnection connection) throws CustomChangeException, SQLException {
		//Should only match on current users that are orderers
		final String query = "SELECT u.user_id AS userId, p.provider_id AS providerId FROM users u, provider p"
		        + " WHERE u.person_id = p.person_id AND u.user_id IN (select orderer from orders)";
		
		return DatabaseUtil.executeSQL(connection.getUnderlyingConnection(), query, true);
	}
	
	private void convertOrdererToProvider(JdbcConnection connection, List<List<Object>> usersAndProviders)
	        throws CustomChangeException, SQLException, DatabaseException {
		final int batchSize = 1000;
		int index = 0;
		PreparedStatement updateStatement = null;
		Statement statement = connection.createStatement();
		Boolean autoCommit = null;
		try {
			autoCommit = connection.getAutoCommit();
			connection.setAutoCommit(false);
			
			updateStatement = connection.prepareStatement("UPDATE orders SET orderer = ? WHERE orderer = ?");
			boolean supportsBatchUpdate = connection.getMetaData().supportsBatchUpdates();
			for (List<Object> row : usersAndProviders) {
				updateStatement.setInt(1, (Integer) row.get(1));
				updateStatement.setInt(2, (Integer) row.get(0));
				if (supportsBatchUpdate) {
					updateStatement.addBatch();
					index++;
					if (index % batchSize == 0) {
						updateStatement.executeBatch();
					}
				} else {
					updateStatement.executeUpdate();
				}
			}
			
			if (supportsBatchUpdate) {
				updateStatement.executeBatch();
			}
			
			//Set the orderer for orders with null orderer to Unknown Provider
			statement.execute("UPDATE orders SET orderer = " + "(SELECT provider_id FROM provider WHERE uuid ="
			        + "(SELECT property_value FROM global_property WHERE property = '" + ""
			        + OpenmrsConstants.GP_UNKNOWN_PROVIDER_UUID + "')) " + "WHERE orderer IS NULL");
			
			connection.commit();
		}
		catch (DatabaseException | SQLException e) {
			handleError(connection, e);
		}
		finally {
			if (autoCommit != null) {
				connection.setAutoCommit(autoCommit);
			}
			if (updateStatement != null) {
				updateStatement.close();
			}
			if (statement != null) {
				statement.close();
			}
		}
	}
	
	@Override
	public String getConfirmationMessage() {
		return "Finished converting orders.orderer from user_id to provider_id";
	}
	
	@Override
	public void setUp() throws SetupException {
	}
	
	@Override
	public void setFileOpener(ResourceAccessor resourceAccessor) {
	}
	
	@Override
	public ValidationErrors validate(Database database) {
		return null;
	}
	
	private void handleError(JdbcConnection connection, Exception e) throws DatabaseException, CustomChangeException {
		connection.rollback();
		throw new CustomChangeException(e);
	}
}
