package de.avatic.lcc.repositories.premise;

import de.avatic.lcc.model.db.materials.Material;
import de.avatic.lcc.model.db.nodes.Location;
import de.avatic.lcc.model.db.nodes.Node;
import de.avatic.lcc.model.db.packaging.PackagingDimension;
import de.avatic.lcc.model.db.premises.Premise;
import de.avatic.lcc.model.db.premises.PremiseListEntry;
import de.avatic.lcc.model.db.premises.PremiseState;
import de.avatic.lcc.model.db.utils.DimensionUnit;
import de.avatic.lcc.model.db.utils.WeightUnit;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Repository
public class PremiseRepository {

    private final JdbcTemplate jdbcTemplate;
    private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    public PremiseRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
    }

    @Transactional
    public SearchQueryResult<PremiseListEntry> listPremises(String filter, SearchQueryPagination pagination,
                                                            Integer userId, Boolean draft, Boolean archived, Boolean done) {

        QueryBuilder queryBuilder = new QueryBuilder()
                .withFilter(filter)
                .withDraft(draft)
                .withArchived(archived)
                .withDone(done);

        String query = queryBuilder.buildSelectQuery();
        String countQuery = queryBuilder.buildCountQuery();

        List<PremiseListEntry> entities;
        Integer totalCount;

        if (isEmptyFilter(filter)) {
            entities = executeQueryWithoutFilter(query, userId, pagination);
            totalCount = executeCountQueryWithoutFilter(countQuery, userId);
        } else {
            entities = executeQueryWithFilter(query, userId, filter, pagination);
            totalCount = executeCountQueryWithFilter(countQuery, userId, filter);
        }

        return new SearchQueryResult<>(entities, pagination.getPage(), totalCount, pagination.getLimit());
    }


    private boolean isEmptyFilter(String filter) {
        return filter == null || filter.isBlank();
    }

    private List<PremiseListEntry> executeQueryWithoutFilter(String query, Integer userId,
                                                             SearchQueryPagination pagination) {
        return jdbcTemplate.query(
                query,
                new PremiseListEntryMapper(),
                userId,
                pagination.getLimit(),
                pagination.getOffset()
        );
    }

    private Integer executeCountQueryWithoutFilter(String countQuery, Integer userId) {
        return jdbcTemplate.queryForObject(countQuery, Integer.class, userId);
    }

    private List<PremiseListEntry> executeQueryWithFilter(String query, Integer userId,
                                                          String filter, SearchQueryPagination pagination) {
        String wildcardFilter = "%" + filter + "%";
        Object[] params = createFilterParams(userId, wildcardFilter, pagination);
        return jdbcTemplate.query(query, new PremiseListEntryMapper(), params);
    }

    private Integer executeCountQueryWithFilter(String countQuery, Integer userId, String filter) {
        String wildcardFilter = "%" + filter + "%";
        Object[] params = createFilterParamsForCount(userId, wildcardFilter);
        return jdbcTemplate.queryForObject(countQuery, Integer.class, params);
    }

    private Object[] createFilterParams(Integer userId, String wildcardFilter, SearchQueryPagination pagination) {
        return new Object[]{
                userId,
                wildcardFilter, wildcardFilter, wildcardFilter, wildcardFilter,
                wildcardFilter, wildcardFilter,
                pagination.getLimit(), pagination.getOffset()
        };
    }

    private Object[] createFilterParamsForCount(Integer userId, String wildcardFilter) {
        return new Object[]{
                userId,
                wildcardFilter, wildcardFilter, wildcardFilter,
                wildcardFilter, wildcardFilter, wildcardFilter
        };
    }


    @Transactional(readOnly = true)
    public List<Premise> getPremisesById(List<Integer> premiseIds) {

        if (premiseIds.isEmpty())
            return Collections.emptyList();

        String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
        String query = """
                SELECT * FROM premise
                WHERE id IN (""" + placeholders + ")";


        return jdbcTemplate.query(
                query,
                ps -> {
                    for (int parameterIndex = 0; parameterIndex < premiseIds.size(); parameterIndex++) {
                        ps.setInt(parameterIndex + 1, premiseIds.get(parameterIndex));
                    }

                },
                new PremiseMapper()
        );
    }

    @Transactional
    public Optional<Premise> getPremiseById(Integer id) {

        String query = """
                SELECT * FROM premise
                WHERE id = ?""";


        var premises = jdbcTemplate.query(query, new PremiseMapper(), id);

        if (premises.isEmpty()) {
            return Optional.empty();
        } else {
            return Optional.of(premises.getFirst());
        }
    }

    @Transactional
    public void resetPackaging(List<Integer> premiseIds, PackagingDimension hu, PackagingDimension shu, Boolean stackable, Boolean mixable) {

        if (premiseIds == null || premiseIds.isEmpty() || hu == null) {
            return;
        }

        boolean isStackable = stackable != null ? stackable : false;
        boolean isMixable = mixable != null ? mixable : false;

        MapSqlParameterSource params = new MapSqlParameterSource();
        params.addValue("length", hu.getLength());
        params.addValue("height", hu.getHeight());
        params.addValue("width", hu.getWidth());
        params.addValue("weight", hu.getWeight());
        params.addValue("dimensionUnit", hu.getDimensionUnit().name());
        params.addValue("weightUnit", hu.getWeightUnit().name());
        params.addValue("unitCount", (hu.getContentUnitCount() == null || shu.getContentUnitCount() == null) ? null : hu.getContentUnitCount() * shu.getContentUnitCount());
        params.addValue("stackable", isStackable);
        params.addValue("mixable", isMixable);
        params.addValue("premiseIds", premiseIds);

        String sql = """
                UPDATE premise
                SET individual_hu_length = :length,
                    individual_hu_height = :height,
                    individual_hu_width = :width,
                    individual_hu_weight = :weight,
                    hu_displayed_dimension_unit = :dimensionUnit,
                    hu_displayed_weight_unit = :weightUnit,
                    hu_unit_count = :unitCount,
                    hu_stackable = :stackable,
                    hu_mixable = :mixable
                WHERE id IN (:premiseIds)
                """;

        namedParameterJdbcTemplate.update(sql, params);

    }

    @Transactional
    public void resetPackaging(List<Integer> premiseIds, PackagingDimension hu, Boolean stackable, Boolean mixable) {


        if (premiseIds == null || premiseIds.isEmpty()) {
            return;
        }

        MapSqlParameterSource params = new MapSqlParameterSource();
        params.addValue("premiseIds", premiseIds);

        StringBuilder sqlBuilder = new StringBuilder("UPDATE premise SET ");
        List<String> setClauses = new ArrayList<>();

        // Handle PackagingDimension hu fields
        if (hu != null) {
            if (hu.getLength() != null) {
                setClauses.add("individual_hu_length = :length");
                params.addValue("length", hu.getLength());
            }

            if (hu.getHeight() != null) {
                setClauses.add("individual_hu_height = :height");
                params.addValue("height", hu.getHeight());
            }

            if (hu.getWidth() != null) {
                setClauses.add("individual_hu_width = :width");
                params.addValue("width", hu.getWidth());
            }

            if (hu.getWeight() != null) {
                setClauses.add("individual_hu_weight = :weight");
                params.addValue("weight", hu.getWeight());
            }

            if (hu.getDimensionUnit() != null) {
                setClauses.add("hu_displayed_dimension_unit = :dimensionUnit");
                params.addValue("dimensionUnit", hu.getDimensionUnit().name());
            }

            if (hu.getWeightUnit() != null) {
                setClauses.add("hu_displayed_weight_unit = :weightUnit");
                params.addValue("weightUnit", hu.getWeightUnit().name());
            }

            if (hu.getContentUnitCount() != null) {
                setClauses.add("hu_unit_count = :unitCount");
                params.addValue("unitCount", hu.getContentUnitCount());
            }
        }

        // Handle stackable
        if (stackable != null) {
            setClauses.add("hu_stackable = :stackable");
            params.addValue("stackable", stackable);
        }

        // Handle mixable
        if (mixable != null) {
            setClauses.add("hu_mixable = :mixable");
            params.addValue("mixable", mixable);
        }

        // If no fields to update, return early
        if (setClauses.isEmpty()) {
            return;
        }

        // Build the complete SQL
        sqlBuilder.append(String.join(", ", setClauses));
        sqlBuilder.append(" WHERE id IN (:premiseIds)");

        String sql = sqlBuilder.toString();
        var affectedRows = namedParameterJdbcTemplate.update(sql, params);

        if (affectedRows != premiseIds.size())
            throw new DatabaseException("Premise update failed for " + premiseIds.size() + " premises. Affected rows: " + affectedRows);

    }

    @Transactional
    public void updateMaterial(List<Integer> premiseIds, String hsCode, BigDecimal tariffRate) {
        updateMaterial(premiseIds, hsCode, tariffRate, null);
    }

    @Transactional
    public void updateMaterial(List<Integer> premiseIds, String hsCode, BigDecimal tariffRate, Boolean tariffUnlocked) {

        // Build the SET clause dynamically based on non-null parameters
        List<String> setClauses = new ArrayList<>();
        List<Object> parameters = new ArrayList<>();

        if (hsCode != null) {
            setClauses.add("hs_code = ?");
            parameters.add(hsCode);
        }

        if (tariffRate != null) {
            setClauses.add("tariff_rate = ?");
            parameters.add(tariffRate);
        }

        if (tariffUnlocked != null) {
            setClauses.add("tariff_unlocked = ?");
            parameters.add(tariffUnlocked);
        }

        // If no fields to update, return early
        if (setClauses.isEmpty()) {
            return;
        }

        // Add premiseIds to parameters
        parameters.addAll(premiseIds);

        String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
        String setClause = String.join(", ", setClauses);

        String query = "UPDATE premise SET " + setClause +
                " WHERE id IN (" + placeholders + ")";

        var affectedRows = jdbcTemplate.update(
                query,
                ps -> {
                    for (int i = 0; i < parameters.size(); i++) {
                        Object param = parameters.get(i);
                        if (param instanceof String) {
                            ps.setString(i + 1, (String) param);
                        } else if (param instanceof BigDecimal) {
                            ps.setBigDecimal(i + 1, (BigDecimal) param);
                        } else if (param instanceof Integer) {
                            ps.setInt(i + 1, (Integer) param);
                        } else if (param instanceof Boolean) {
                            ps.setBoolean(i + 1, (Boolean) param);
                        }
                    }
                }
        );

        if (affectedRows != premiseIds.size())
            throw new DatabaseException("Premise update failed for " + premiseIds.size() + " premises. Affected rows: " + affectedRows);
    }

    @Transactional
    public void resetPrice(List<Integer> premiseIds) {
        if (premiseIds == null || premiseIds.isEmpty()) {
            return;
        }

        String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
        String query = "UPDATE premise SET material_cost = null, is_fca_enabled = false, oversea_share = null WHERE id IN (" + placeholders + ")";
        jdbcTemplate.update(query, premiseIds.toArray());

    }

    @Transactional
    public void updatePrice(List<Integer> premiseIds, BigDecimal price, Boolean includeFcaFee, BigDecimal overseaShare) {
        // Build dynamic SET clause based on non-null parameters
        List<String> setClauses = new ArrayList<>();
        if (price != null) {
            setClauses.add("material_cost = ?");
        }
        if (includeFcaFee != null) {
            setClauses.add("is_fca_enabled = ?");
        }
        if (overseaShare != null) {
            setClauses.add("oversea_share = ?");
        }

        // If no parameters to update, return early
        if (setClauses.isEmpty()) {
            return;
        }

        String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
        String setClause = String.join(", ", setClauses);

        String query = "UPDATE premise SET " + setClause + " WHERE id IN (" + placeholders + ")";

        var affectedRows = jdbcTemplate.update(
                query,
                ps -> {
                    int parameterIndex = 1;

                    // Set the dynamic parameters in the same order as SET clauses
                    if (price != null) {
                        ps.setBigDecimal(parameterIndex++, price);
                    }
                    if (includeFcaFee != null) {
                        ps.setBoolean(parameterIndex++, includeFcaFee);
                    }
                    if (overseaShare != null) {
                        ps.setBigDecimal(parameterIndex++, overseaShare);
                    }

                    // Set premise ID parameters
                    for (Integer premiseId : premiseIds) {
                        ps.setInt(parameterIndex++, premiseId);
                    }
                }
        );

        if (affectedRows != premiseIds.size())
            throw new DatabaseException("Premise update failed for " + premiseIds.size() + " premises. Affected rows: " + affectedRows);
    }

    @Transactional
    public void updateTariffRate(Integer premiseId, Number tariffRate) {
        String sql = "UPDATE premise SET tariff_rate = ? WHERE id = ?";

        jdbcTemplate.update(sql, tariffRate, premiseId);
    }


    /**
     * Retrieves all draft premises that would conflict with updating the material or supplier for a given premise.
     * <p>
     * A conflict arises when multiple premises share the same combination of material ID and supplier ID while
     * being in the DRAFT state. This method enables the identification of such potential duplicates, excluding
     * the premise being evaluated to avoid self-collision.
     *
     * @param userId     The ID of the user who owns the premises.
     * @param premiseId  The ID of the premise being checked; this premise will be excluded from the results.
     * @param materialId The material ID to verify for potential conflicts.
     * @param supplierId The supplier node ID to verify for potential conflicts.
     * @return A list of premises in the DRAFT state with the same material and supplier combination (excluding the specified premise).
     * @throws IllegalArgumentException If any of the provided parameters are null.
     */
    @Transactional(readOnly = true)
    public List<Premise> getCollidingPremisesOnChange(Integer userId, Integer premiseId, Integer materialId, Integer supplierId) {

        if (premiseId == null || materialId == null || supplierId == null)
            return Collections.emptyList();

        String sql = "SELECT * FROM premise " +
                "WHERE material_id = ? " +
                "AND supplier_node_id = ? " +
                "AND id != ? " +
                "AND user_id = ? " +
                "AND state = 'DRAFT'";

        return jdbcTemplate.query(sql, new PremiseMapper(), materialId, supplierId, premiseId, userId);
    }

    @Transactional
    public void deletePremisesById(List<Integer> premiseIds) {
        if (premiseIds == null || premiseIds.isEmpty()) return;

        String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
        String query = "DELETE FROM premise WHERE id IN (" + placeholders + ") AND state = ?";

        Object[] params = new Object[premiseIds.size() + 1];
        for (int i = 0; i < premiseIds.size(); i++) {
            params[i] = premiseIds.get(i);
        }
        params[premiseIds.size()] = PremiseState.DRAFT.name();

        jdbcTemplate.update(query, params);
    }


    @Transactional
    public void setSupplierId(List<Integer> premiseId, Node supplier, boolean userSupplierNode) {
        String placeholders = String.join(",", Collections.nCopies(premiseId.size(), "?"));
        String sql = "UPDATE premise SET supplier_node_id = ?, user_supplier_node_id = ?, geo_lat = ?, geo_lng = ?, country_id = ? WHERE id IN (" + placeholders + ")";

        // Create array with all parameters
        Object[] params = new Object[5 + premiseId.size()];

        if (userSupplierNode) {
            params[0] = null;
            params[1] = supplier.getId();
        } else {
            params[0] = supplier.getId();
            params[1] = null;
        }
        params[2] = supplier.getGeoLat();
        params[3] = supplier.getGeoLng();
        params[4] = supplier.getCountryId();

        // Add premise IDs as individual parameters
        for (int i = 0; i < premiseId.size(); i++) {
            params[5 + i] = premiseId.get(i);
        }

        var affectedRows = jdbcTemplate.update(sql, params);

        if (affectedRows != premiseId.size())
            throw new DatabaseException("Premise update failed for " + premiseId.size() + " premises. Affected rows: " + affectedRows);

    }

    @Transactional
    public void setMaterialId(List<Integer> premiseId, Integer materialId) {
        String placeholders = String.join(",", Collections.nCopies(premiseId.size(), "?"));
        String sql = "UPDATE premise SET material_id = ? WHERE id IN (" + placeholders + ")";

        List<Object> params = new ArrayList<>();
        params.add(materialId);
        params.addAll(premiseId);
        jdbcTemplate.update(sql, params.toArray());
    }

    @Transactional
    public List<Premise> findByMaterialIdAndSupplierId(Integer materialId, Integer supplierId, Integer userSupplierId, Integer userId) {

        if ((supplierId == null && userSupplierId == null) || (supplierId != null && userSupplierId != null))
            throw new DatabaseException("Either supplierId or userSupplierId must be null");

        String query = """
                SELECT * FROM premise
                WHERE material_id = ? AND (user_id = ?
                """;

        if (userSupplierId != null)
            query += ") AND user_supplier_node_id = ?";
        else query += "OR state = 'COMPLETED') AND supplier_node_id = ?";


        query += " ORDER BY updated_at DESC";

        return jdbcTemplate.query(query, new PremiseMapper(), materialId, userId, userSupplierId != null ? userSupplierId : supplierId);
    }

    @Transactional
    public List<Premise> getPremisesByMaterialIdsAndSupplierIds(List<Integer> materialIds, List<Integer> supplierIds, List<Integer> userSupplierIds, Integer userId, boolean draftsOnly) {

        if ((materialIds == null || materialIds.isEmpty()) &&
                (supplierIds == null || supplierIds.isEmpty()) &&
                (userSupplierIds == null || userSupplierIds.isEmpty())) {
            return Collections.emptyList();
        }

        StringBuilder query = new StringBuilder("""
                SELECT * FROM premise
                WHERE 1=1
                """);

        if (draftsOnly)
            query.append(" AND user_id = ? AND state = ?");

        // Append conditions for IDs
        if (materialIds != null && !materialIds.isEmpty()) {
            String materialPlaceholders = String.join(",", Collections.nCopies(materialIds.size(), "?"));
            query.append(" AND material_id IN (").append(materialPlaceholders).append(")");
        }

        if (supplierIds != null && !supplierIds.isEmpty()) {
            String supplierPlaceholders = String.join(",", Collections.nCopies(supplierIds.size(), "?"));
            query.append(" AND supplier_node_id IN (").append(supplierPlaceholders).append(")");
        }

        if (userSupplierIds != null && !userSupplierIds.isEmpty()) {
            String userSupplierPlaceholders = String.join(",", Collections.nCopies(userSupplierIds.size(), "?"));
            query.append(" AND user_supplier_node_id IN (").append(userSupplierPlaceholders).append(")");
        }

        // Combine parameters for prepared statement
        var params = new ArrayList<>();

        if (draftsOnly) {
            params.add(userId);
            params.add(PremiseState.DRAFT.name());
        }

        if (materialIds != null && !materialIds.isEmpty()) params.addAll(materialIds);
        if (supplierIds != null && !supplierIds.isEmpty()) params.addAll(supplierIds);
        if (userSupplierIds != null && !userSupplierIds.isEmpty()) params.addAll(userSupplierIds);

        return jdbcTemplate.query(query.toString(), new PremiseMapper(), params.toArray());
    }

    @Transactional
    public Integer insert(Integer materialId, Integer supplierId, Integer userSupplierId, BigDecimal geoLat, BigDecimal geoLng, Integer countryId, Integer userId) {

        KeyHolder keyHolder = new GeneratedKeyHolder();

        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(
                    "INSERT INTO premise (material_id, supplier_node_id, user_supplier_node_id, user_id, state, created_at, updated_at, geo_lat, geo_lng, country_id)" +
                            "                VALUES (?, ?, ?, ?, 'DRAFT', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?, ?, ?)",
                    Statement.RETURN_GENERATED_KEYS);

            ps.setInt(1, materialId);
            ps.setObject(2, supplierId);
            ps.setObject(3, userSupplierId);
            ps.setInt(4, userId);
            ps.setBigDecimal(5, geoLat);
            ps.setBigDecimal(6, geoLng);
            ps.setInt(7, countryId);

            return ps;
        }, keyHolder);

        return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;

    }

    @Transactional
    public List<Integer> findAssociatedSuppliers(List<Integer> materialIds) {
        if (materialIds == null || materialIds.isEmpty()) {
            return Collections.emptyList();
        }

        String placeholders = String.join(",", Collections.nCopies(materialIds.size(), "?"));
        String query = """
                SELECT DISTINCT supplier_node_id
                FROM premise
                WHERE material_id IN (""" + placeholders + ")";

        return jdbcTemplate.query(
                query,
                (rs, rowNum) -> rs.getInt("supplier_node_id"),
                materialIds.toArray()
        );
    }

    @Transactional
    public List<Integer> findAssociatedUserSuppliers(List<Integer> materialIds, Integer userId) {

        if (materialIds == null || materialIds.isEmpty()) {
            return Collections.emptyList();
        }

        List<Object> params = new ArrayList<>(materialIds);
        params.add(userId);

        String placeholders = String.join(",", Collections.nCopies(materialIds.size(), "?"));
        String query = """
                SELECT DISTINCT p.user_supplier_node_id AS user_supplier_node_id
                FROM premise p
                JOIN sys_user_node sun ON p.user_supplier_node_id = sun.id
                WHERE p.material_id IN (""" + placeholders + ") AND sun.user_id = ? ";

        return jdbcTemplate.query(
                query,
                (rs, rowNum) -> rs.getInt("user_supplier_node_id"),
                params.toArray()
        );
    }

    @Transactional
    public void setPackagingId(Integer id, Integer packagingId) {
        String query = "UPDATE premise SET packaging_id = ? WHERE id = ?";

        var affectedRows = jdbcTemplate.update(query, packagingId, id);


        if (affectedRows == 0)
            throw new DatabaseException("No premise found with id " + id);

    }

    @Transactional
    public void checkOwner(List<Integer> premiseIds, int userId) {
        String query = """
                SELECT id FROM premise WHERE premise.id IN (:premiseIds) AND user_id <> :userId
                """;

        MapSqlParameterSource parameters = new MapSqlParameterSource();
        parameters.addValue("premiseIds", premiseIds);
        parameters.addValue("userId", userId);

        var otherIds = namedParameterJdbcTemplate.queryForList(query, parameters, Integer.class);

        if (!otherIds.isEmpty()) {
            throw new ForbiddenException("Access violation. Cannot open premise with ids = " + otherIds);
        }
    }

    @Transactional
    public void setStatus(List<Integer> premisesIds, PremiseState premiseState) {
        if (premisesIds == null || premisesIds.isEmpty() || premiseState == null) {
            return;
        }

        String placeholders = String.join(",", Collections.nCopies(premisesIds.size(), "?"));
        String query = "UPDATE premise SET state = ? WHERE id IN (" + placeholders + ")";

        List<Object> params = new ArrayList<>();
        params.add(premiseState.name());
        params.addAll(premisesIds);

        var affectedRows = jdbcTemplate.update(query, params.toArray());

        if (affectedRows != premisesIds.size())
            throw new DatabaseException("Premise update failed for " + premisesIds.size() + " premises. Affected rows: " + affectedRows);
    }


    @Transactional
    public List<Integer> getIdsWithUnlockedTariffs(List<Integer> premiseIds) {
        if (premiseIds == null || premiseIds.isEmpty()) {
            return premiseIds;
        }

        String sql = "SELECT id FROM premise WHERE id IN (:ids) AND tariff_unlocked = TRUE";

        List<Integer> unlockedIds = namedParameterJdbcTemplate.query(
                sql,
                new MapSqlParameterSource("ids", premiseIds),
                (rs, rowNum) -> rs.getInt("id")
        );

        return unlockedIds;
    }


    /**
     * Encapsulates SQL query building logic
     */
    private static class QueryBuilder {
        private static final String BASE_JOIN_QUERY = """
                FROM premise AS p
                LEFT JOIN material as m ON p.material_id = m.id
                LEFT JOIN node as n ON p.supplier_node_id = n.id
                LEFT JOIN sys_user_node as user_n ON p.user_supplier_node_id = user_n.id
                WHERE p.user_id = ?""";

        private static final String FILTER_CONDITION =
                " AND (n.name LIKE ? OR n.external_mapping_id LIKE ? OR " +
                        "user_n.name LIKE ? OR m.name LIKE ? OR m.name LIKE ? OR m.part_number LIKE ?)";

        private String filter;
        private Boolean draft;
        private Boolean archived;
        private Boolean done;

        public QueryBuilder withFilter(String filter) {
            this.filter = filter;
            return this;
        }

        public QueryBuilder withDraft(Boolean draft) {
            this.draft = draft;
            return this;
        }

        public QueryBuilder withArchived(Boolean archived) {
            this.archived = archived;
            return this;
        }

        public QueryBuilder withDone(Boolean done) {
            this.done = done;
            return this;
        }

        public String buildCountQuery() {
            StringBuilder queryBuilder = new StringBuilder();
            queryBuilder.append("SELECT COUNT(*) ").append(BASE_JOIN_QUERY);
            appendConditions(queryBuilder);
            return queryBuilder.toString();
        }

        public String buildSelectQuery() {
            StringBuilder queryBuilder = new StringBuilder();
            queryBuilder.append("""
                    SELECT p.id as 'p.id', p.state as 'p.state', p.user_id as 'p.user_id',
                           p.created_at as 'p.created_at', p.updated_at as 'p.updated_at',
                           p.supplier_node_id as 'p.supplier_node_id', p.user_supplier_node_id as 'p.user_supplier_node_id',
                           m.id as 'm.id', m.name as 'm.name', m.is_deprecated as 'm.is_deprecated',
                           m.hs_code as 'm.hs_code', m.part_number as 'm.part_number',
                           n.id as 'n.id', n.name as 'n.name', n.address as 'n.address',
                           n.country_id as 'n.country_id', n.geo_lat as 'n.geo_lat', n.geo_lng as 'n.geo_lng',
                           n.is_destination as 'n.is_destination', n.is_intermediate as 'n.is_intermediate', n.is_source as 'n.is_source',
                           n.is_deprecated as 'n.is_deprecated',
                           user_n.id as 'user_n.id', user_n.name as 'user_n.name', user_n.address as 'user_n.address',
                           user_n.country_id as 'user_n.country_id', user_n.geo_lat as 'user_n.geo_lat', user_n.geo_lng as 'user_n.geo_lng'
                    """).append(BASE_JOIN_QUERY);
            appendConditions(queryBuilder);
            queryBuilder.append(" ORDER BY p.updated_at DESC, p.id DESC");
            queryBuilder.append(" LIMIT ? OFFSET ?");
            return queryBuilder.toString();
        }

        private void appendConditions(StringBuilder queryBuilder) {
            if (filter != null && !filter.isBlank()) {
                queryBuilder.append(FILTER_CONDITION);
            }

            if (draft != null && draft || archived != null && archived || done != null && done) {
                boolean concat = false;

                queryBuilder.append(" AND (");

                if (draft != null && draft) {
                    queryBuilder.append(" p.state").append(" = 'DRAFT'");
                    concat = true;
                }

                if (archived != null && archived) {
                    if (concat)
                        queryBuilder.append(" OR ");
                    queryBuilder.append(" p.state").append(" = 'ARCHIVED'");
                    concat = true;
                }

                if (done != null && done) {
                    if (concat)
                        queryBuilder.append(" OR ");
                    queryBuilder.append(" p.state").append(" = 'COMPLETED'");
                    concat = true;
                }

                queryBuilder.append(")");

            }

        }

        private void appendBooleanCondition(StringBuilder queryBuilder, Boolean condition, String field) {
            if (condition != null && condition) {
                queryBuilder.append(" OR ").append(field).append(" = TRUE");
            }
        }
    }

    private static class PremiseListEntryMapper implements RowMapper<PremiseListEntry> {
        @Override
        public PremiseListEntry mapRow(ResultSet rs, int rowNum) throws SQLException {
            PremiseListEntry entity = new PremiseListEntry();

            // Map base premise properties
            mapBasePremiseProperties(entity, rs);

            // Map material
            entity.setMaterial(mapMaterial(rs));

            // Map supplier (either regular node or user node)
            mapSupplierProperties(entity, rs);

            return entity;
        }

        private void mapBasePremiseProperties(PremiseListEntry entity, ResultSet rs) throws SQLException {
            entity.setId(rs.getInt("p.id"));
            entity.setState(de.avatic.lcc.dto.calculation.PremiseState.valueOf(rs.getString("p.state")));
            entity.setOwnerId(rs.getInt("p.user_id"));
            entity.setCreatedAt(rs.getTimestamp("p.created_at").toLocalDateTime());
            entity.setUpdatedAt(rs.getTimestamp("p.created_at").toLocalDateTime()); // Note: This looks like a bug, should be "p.updated_at"
        }

        private Material mapMaterial(ResultSet rs) throws SQLException {
            Material data = new Material();

            data.setId(rs.getInt("m.id"));
            data.setName(rs.getString("m.name"));
            data.setDeprecated(rs.getBoolean("m.is_deprecated"));
            data.setHsCode(rs.getString("m.hs_code"));
            data.setPartNumber(rs.getString("m.part_number"));

            return data;
        }

        private void mapSupplierProperties(PremiseListEntry entity, ResultSet rs) throws SQLException {
            if (rs.getInt("p.supplier_node_id") != 0) {
                mapSupplier(false, entity, rs, "n");
            } else if (rs.getInt("p.user_supplier_node_id") != 0) {
                mapSupplier(true, entity, rs, "user_n");
                entity.setUserSupplier(true);
            }
        }

        private void mapSupplier(boolean isUserNode, PremiseListEntry entity, ResultSet rs, String tablePrefix) throws SQLException {
            entity.setSupplierId(rs.getInt(tablePrefix + ".id"));
            entity.setSupplierName(rs.getString(tablePrefix + ".name"));
            entity.setSupplierAddress(rs.getString(tablePrefix + ".address"));
            entity.setSupplierCountryId(rs.getInt(tablePrefix + ".country_id"));

            entity.setSupplierGeoLatitude(rs.getBigDecimal(tablePrefix + ".geo_lat"));
            entity.setSupplierGeoLongitude(rs.getBigDecimal(tablePrefix + ".geo_lng"));

            if (!isUserNode) {
                entity.setSupplierIsDestination(rs.getBoolean(tablePrefix + ".is_destination"));
                entity.setSupplierIsIntermediate(rs.getBoolean(tablePrefix + ".is_intermediate"));
                entity.setSupplierIsSource(rs.getBoolean(tablePrefix + ".is_source"));
                entity.setDeprecated(rs.getBoolean(tablePrefix + ".is_deprecated"));
            } else {
                entity.setSupplierIsDestination(false);
                entity.setSupplierIsIntermediate(false);
                entity.setSupplierIsSource(true);
                entity.setDeprecated(false);
            }

        }
    }

    private static class PremiseMapper implements RowMapper<Premise> {
        @Override
        public Premise mapRow(ResultSet rs, int rowNum) throws SQLException {
            var entity = new Premise();

            entity.setId(rs.getInt("id"));

            entity.setPackagingId(rs.getInt("packaging_id"));
            if (rs.wasNull())
                entity.setPackagingId(null);

            entity.setMaterialId(rs.getInt("material_id"));
            if (rs.wasNull())
                entity.setMaterialId(null);

            entity.setSupplierNodeId(rs.getInt("supplier_node_id"));
            if (rs.wasNull())
                entity.setSupplierNodeId(null);

            entity.setUserSupplierNodeId(rs.getInt("user_supplier_node_id"));
            if (rs.wasNull())
                entity.setUserSupplierNodeId(null);

            entity.setLocation(new Location(rs.getBigDecimal("geo_lng").doubleValue(), rs.getBigDecimal("geo_lat").doubleValue()));

            entity.setCountryId(rs.getInt("country_id"));
            if (rs.wasNull())
                entity.setCountryId(null);

            entity.setUserId(rs.getInt("user_id"));
            if (rs.wasNull())
                entity.setUserId(null);

            entity.setMaterialCost(rs.getBigDecimal("material_cost"));

            entity.setHsCode(rs.getString("hs_code"));

            entity.setTariffRate(rs.getBigDecimal("tariff_rate"));

            entity.setTariffUnlocked(rs.getBoolean("tariff_unlocked"));

            entity.setFcaEnabled(rs.getBoolean("is_fca_enabled"));
            if (rs.wasNull())
                entity.setFcaEnabled(null);

            entity.setOverseaShare(rs.getBigDecimal("oversea_share"));

            entity.setIndividualHuHeight(rs.getInt("individual_hu_height"));
            if (rs.wasNull())
                entity.setIndividualHuHeight(null);

            entity.setIndividualHuWidth(rs.getInt("individual_hu_width"));
            if (rs.wasNull())
                entity.setIndividualHuWidth(null);

            entity.setIndividualHuLength(rs.getInt("individual_hu_length"));
            if (rs.wasNull())
                entity.setIndividualHuLength(null);

            entity.setIndividualHuWeight(rs.getInt("individual_hu_weight"));
            if (rs.wasNull())
                entity.setIndividualHuWeight(null);

            entity.setHuDisplayedDimensionUnit(DimensionUnit.valueOf(rs.getString("hu_displayed_dimension_unit")));
            entity.setHuDisplayedWeightUnit(WeightUnit.valueOf(rs.getString("hu_displayed_weight_unit")));

            entity.setHuStackable(rs.getBoolean("hu_stackable"));
            if (rs.wasNull())
                entity.setHuStackable(null);

            entity.setHuMixable(rs.getBoolean("hu_mixable"));
            if (rs.wasNull())
                entity.setHuMixable(null);

            entity.setHuUnitCount(rs.getInt("hu_unit_count"));
            if (rs.wasNull())
                entity.setHuUnitCount(null);

            entity.setState(PremiseState.valueOf(rs.getString("state")));
            entity.setUpdatedAt(rs.getTimestamp("updated_at").toLocalDateTime());
            entity.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());

            return entity;
        }
    }
}
