package com.workfusion.odf.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.inject.Inject;

import groovy.lang.Binding;
import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.Logger;

import com.workfusion.rpa.core.BindingUtils;
import com.workfusion.rpa.core.datastore.DataStoreInsert;
import com.workfusion.rpa.core.datastore.DataStoreQuery;

import static com.workfusion.odf.service.DatastoreTransactionService.TRANSACTION_DS_NAME;

/**
 * Intake transactions wait service implementation.
 *
 * @author Andrei Urbanovich
 */
public class TransactionWaitServiceImpl implements TransactionWaitService {
	public static final String TRANSACTION_STATUS_DS_NAME = "_odf_transactions_status";

	private DataStoreInsert dataStoreInsert;
    private DataStoreQuery dataStoreQuery;
    private Binding binding;
    private Logger logger;

	@Inject
    public TransactionWaitServiceImpl(final DataStoreInsert dataStoreInsert, final DataStoreQuery dataStoreQuery,
    		final Binding binding, final Logger logger) {
        this.dataStoreInsert = dataStoreInsert;
        this.dataStoreQuery = dataStoreQuery;
        this.binding = binding;
        this.logger = logger;
    }

    public void setStatus(final String transactionId, final String status) {
        String setTransactionStatusQuery = String.format("update @this set status = '%s' where transaction_id = '%s'",
        		status, StringEscapeUtils.escapeSql(transactionId));
        dataStoreQuery.executeQuery(TRANSACTION_STATUS_DS_NAME, setTransactionStatusQuery);
    }

    @Override
    public String registerTransactionCompletion(final String transactionId) {
	    String runId = BindingUtils.getBpRunId(binding);

    	// Select number from the Intake transaction DataStore
        // Select number from the Intake transaction status DataStore
        int transactionsCountPerRun = getTransactionCountPerRun(runId);
        int registeredTransactionsCountPerRun = getRegisteredTransactionsCountPerRun(runId);

        boolean isLastTransaction = (transactionsCountPerRun - registeredTransactionsCountPerRun) <= 1;

        logger.debug("Register transaction: id={}, runId={}, IsLast={}, TransactionsCountPerRun={}, RegisteredTransactionsCountPerRun={}",
        		transactionId, runId, isLastTransaction, transactionsCountPerRun, registeredTransactionsCountPerRun);

        // Register the transaction
    	Map<String, String> transactionStatusRow = new HashMap<>();
        transactionStatusRow.put("transaction_id", transactionId);
        transactionStatusRow.put("bp_run_id", runId);
        dataStoreInsert.insertRow(TRANSACTION_STATUS_DS_NAME, transactionStatusRow);

        if (isLastTransaction) {
            DataStoreQuery.QueryResult result = dataStoreQuery.executeQuery(TRANSACTION_DS_NAME, String.format("select transaction_id from @this where bp_run_id = '%s'", runId));
            List<Map<String, String>> transactionIds = result.getSelectResultAsMapRows().get().stream().collect(Collectors.toList());
            StringBuilder sb = new StringBuilder();
            transactionIds.forEach(row -> {
            	sb.append(row.get("transaction_id")).append(',');
            });
            if (logger.isDebugEnabled()) {
                logger.debug("Returned transaction IDs: {}", sb.toString());
            }

            return (sb.length() > 0) ? sb.substring(0, sb.length() - 1) : "";
        } else {
            return "";
        }
    }

    private int getTransactionCountPerRun(final String runId) {
        DataStoreQuery.QueryResult result = dataStoreQuery.executeQuery(TRANSACTION_DS_NAME, String.format("select count(*) as total from @this where bp_run_id = '%s'", runId));
        return result.getSelectResultAsMapRows().get().stream().findFirst().map((map) -> Integer.valueOf(map.get("total"))).get();
    }

    private int getRegisteredTransactionsCountPerRun(final String runId) {
        DataStoreQuery.QueryResult result = dataStoreQuery.executeQuery(TRANSACTION_STATUS_DS_NAME, String.format("select count(*) as total from @this where bp_run_id = '%s'", runId));
        return result.getSelectResultAsMapRows().get().stream().findFirst().map((map) -> Integer.valueOf(map.get("total"))).get();
    }
}
