package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.transaction.Transactional;

import org.springframework.stereotype.Component;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;

@RequiredArgsConstructor
@Component
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
class QueryExecutor {

    private static final String FETCH_ENTRY_VALUES_QUERY = """
            select value from ::tableName::
                where type_id = :typeId and value in (:entries)""";

    private static final String UPDATE_ENTRIES_QUERY = """
            update ::tableName::
                set deleted = ::deleted::, version = :version
                where type_id = :typeId and value in (:entries)""";

    // Currently (2023-04-13) there is a limitation in the Postgres JDBC driver, that limits the number of elements in a "IN" clause
    // to the max value of a 'short'.  We subtract a small value to be on the safe side, since it is unclear what contributes
    // to the number of elements, only the elements or parentheses etc.
    private static final int ELEMENT_CHUNK_SIZE = Short.MAX_VALUE - 10;

    EntityManager entityManager;


    @Transactional
    public LinkedList<String> runUndeleteQueryInBatches(String typeId, Set<String> entries, long version, String tableName) {

        return runUpdateQueryInBatches(typeId, entries, version, tableName, false, true);
    }


    private LinkedList<String> runUpdateQueryInBatches(String typeId, Set<String> entries, long version, String tableName, boolean deleted, boolean collectChangedValues) {

        var results = new LinkedList<String>();

        var entryList = new ArrayList<>(entries);

        for (int fromIndex = 0, toIndex = ELEMENT_CHUNK_SIZE; ; ) {
            toIndex = Math.min(toIndex, entryList.size());

            if (fromIndex >= entryList.size()) {
                break;
            }

            var values = entryList.subList(fromIndex, toIndex);

            if (collectChangedValues) {
                var entryValues = executeFetchValuesQuery(typeId, tableName, values);
                results.addAll(entryValues);
            }

            executeUpdateQuery(typeId, version, tableName, values, deleted);

            fromIndex += ELEMENT_CHUNK_SIZE;
            toIndex += ELEMENT_CHUNK_SIZE;
        }

        return results;
    }


    private void executeUpdateQuery(String typeId, long version, String tableName, List<String> values, boolean deleted) {

        String updateSql = getUpdateEntriesQuery(tableName, deleted);

        Query updateEntriesQuery = entityManager.createNativeQuery(updateSql);
        updateEntriesQuery.setParameter("typeId", typeId);
        updateEntriesQuery.setParameter("version", version);

        updateEntriesQuery.setParameter("entries", values);
        updateEntriesQuery.executeUpdate();
    }


    // The call to query.getResultList returns an untyped list, there is no way around that.
    // So we suppress the warning.
    // CAUTION: Make sure that the query actually returns a list of Strings.
    @SuppressWarnings("unchecked")
    private List<String> executeFetchValuesQuery(String typeId, String tableName, List<String> values) {

        String fetchSql = getFetchEntryValuesQuery(tableName);

        Query fetchEntryValuesQuery = entityManager.createNativeQuery(fetchSql);
        fetchEntryValuesQuery.setParameter("typeId", typeId);

        fetchEntryValuesQuery.setParameter("entries", values);
        return fetchEntryValuesQuery.getResultList();
    }


    private String getFetchEntryValuesQuery(String tableName) {

        return FETCH_ENTRY_VALUES_QUERY.replace("::tableName::", tableName);
    }


    private String getUpdateEntriesQuery(String tableName, boolean deleted) {

        return UPDATE_ENTRIES_QUERY.replace("::tableName::", tableName).replace("::deleted::", Boolean.toString(deleted));
    }


    @Transactional
    public void runDeleteQueryInBatches(String typeId, Set<String> entries, long version, String tableName) {

        runUpdateQueryInBatches(typeId, entries, version, tableName, true, false);
    }

}
