/**
 * 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.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.openmrs.Order;

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;

public class CreateDiscontinueOrders implements CustomTaskChange {
	
	@Override
	public void execute(Database database) throws CustomChangeException {
		JdbcConnection connection = (JdbcConnection) database.getConnection();
		try {
			List<DiscontinuedOrder> discontinuedOrders = getDiscontinuedOrders(connection);
			createDiscontinueOrders(connection, discontinuedOrders);
		}
		catch (SQLException | DatabaseException e) {
			throw new CustomChangeException(e);
		}
	}
	
	private void createDiscontinueOrders(JdbcConnection connection, List<DiscontinuedOrder> discontinuedOrders)
	        throws CustomChangeException, SQLException, DatabaseException {
		final int batchSize = 1000;
		int index = 0;
		PreparedStatement insertStatement = null;
		Boolean autoCommit = null;
		try {
			autoCommit = connection.getAutoCommit();
			connection.setAutoCommit(false);
			insertStatement = connection
			        .prepareStatement("Insert into orders(previous_order_id, concept_id, patient_id, encounter_id, "
			                + "creator, date_created, discontinued_reason, discontinued_reason_non_coded, "
			                + "uuid, order_action, orderer, order_number, order_type_id, start_date, auto_expire_date) "
			                + "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
			for (DiscontinuedOrder discontinuedOrder : discontinuedOrders) {
				insertStatement.setInt(1, discontinuedOrder.previousOrderId);
				insertStatement.setInt(2, discontinuedOrder.conceptId);
				insertStatement.setInt(3, discontinuedOrder.patientId);
				setIntOrNull(insertStatement, 4, discontinuedOrder.encounterId);
				insertStatement.setInt(5, discontinuedOrder.discontinuedById);
				insertStatement.setDate(6, new Date(System.currentTimeMillis()));
				setIntOrNull(insertStatement, 7, discontinuedOrder.discontinuedReasonId);
				insertStatement.setString(8, discontinuedOrder.discontinuedReasonNonCoded);
				insertStatement.setString(9, UUID.randomUUID().toString());
				insertStatement.setString(10, Order.Action.DISCONTINUE.name());
				setIntOrNull(insertStatement, 11, discontinuedOrder.discontinuedById);
				insertStatement.setString(12, discontinuedOrder.orderNumber);
				insertStatement.setInt(13, discontinuedOrder.orderTypeId);
				insertStatement.setDate(14, discontinuedOrder.dateActivated);
				insertStatement.setDate(15, discontinuedOrder.dateActivated);
				insertStatement.addBatch();
				
				if (index % batchSize == 0) {
					insertStatement.executeBatch();
				}
				index++;
			}
			insertStatement.executeBatch();
			connection.commit();
		}
		catch (DatabaseException | SQLException e) {
			handleError(connection, e);
		}
		finally {
			if (autoCommit != null) {
				connection.setAutoCommit(autoCommit);
			}
			if (insertStatement != null) {
				insertStatement.close();
			}
		}
	}
	
	private void setIntOrNull(PreparedStatement statement, int index, Integer value) throws SQLException {
		if (value == null || value == 0) {
			statement.setNull(index, Types.INTEGER);
		} else {
			statement.setInt(index, value);
		}
	}
	
	private void handleError(JdbcConnection connection, Exception e) throws DatabaseException, CustomChangeException {
		connection.rollback();
		throw new CustomChangeException(e);
	}
	
	private List<DiscontinuedOrder> getDiscontinuedOrders(JdbcConnection connection) throws CustomChangeException,
	        SQLException {
		List<DiscontinuedOrder> dcOrders = new ArrayList<>();
		PreparedStatement statement = null;
		try {
			statement = connection.prepareStatement("select order_id, concept_id, patient_id, encounter_id, date_stopped, "
			        + "discontinued_by, discontinued_reason, discontinued_reason_non_coded, order_type_id "
			        + "from orders where discontinued = ?");
			statement.setBoolean(1, true);
			ResultSet rs = statement.executeQuery();
			while (rs.next()) {
				dcOrders.add(new DiscontinuedOrder(rs.getInt("order_id"), rs.getInt("concept_id"), rs.getInt("patient_id"),
				        rs.getInt("encounter_id"), rs.getInt("discontinued_by"), rs.getInt("discontinued_reason"), rs
				                .getString("discontinued_reason_non_coded"), rs.getDate("date_stopped"), rs
				                .getInt("order_type_id")));
			}
		}
		catch (SQLException | DatabaseException e) {
			throw new CustomChangeException(e);
		}
		finally {
			if (statement != null) {
				statement.close();
			}
		}
		return dcOrders;
	}
	
	@Override
	public String getConfirmationMessage() {
		return "Finished creating discontinue orders for discontinued orders";
	}
	
	@Override
	public void setUp() throws SetupException {
	}
	
	@Override
	public void setFileOpener(ResourceAccessor resourceAccessor) {
	}
	
	@Override
	public ValidationErrors validate(Database database) {
		return null;
	}
	
	private static class DiscontinuedOrder {
		
		public int orderId;
		
		public int conceptId;
		
		public int patientId;
		
		public int encounterId;
		
		public int discontinuedReasonId;
		
		public String discontinuedReasonNonCoded;
		
		public Date dateActivated;
		
		public int discontinuedById;
		
		public Date dateCreated;
		
		public int previousOrderId;
		
		public String orderNumber;
		
		public int orderTypeId;
		
		public Date date;
		
		private DiscontinuedOrder(int orderId, int conceptId, int patientId, int encounterId, int discontinuedById,
		    int discontinuedReasonId, String discontinuedReasonNonCoded, Date dateStopped, int orderTypeId) {
			this.orderId = orderId;
			this.previousOrderId = orderId;
			this.conceptId = conceptId;
			this.patientId = patientId;
			this.encounterId = encounterId;
			this.discontinuedReasonId = discontinuedReasonId;
			this.discontinuedReasonNonCoded = discontinuedReasonNonCoded;
			this.dateActivated = dateStopped;
			this.discontinuedById = discontinuedById;
			this.dateCreated = dateStopped;
			this.orderNumber = String.valueOf(orderId).concat("-DC");
			this.orderTypeId = orderTypeId;
		}
	}
}
