package ru.yandex.qe.dispenser.domain.dao.bot.service.reserve.configurations;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;

import ru.yandex.qe.dispenser.domain.Campaign;
import ru.yandex.qe.dispenser.domain.dao.SqlDaoBase;
import ru.yandex.qe.dispenser.domain.dao.bot.service.reserve.BotServiceReserveFilter;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;
import ru.yandex.qe.dispenser.domain.hierarchy.HierarchySupplier;
import ru.yandex.qe.dispenser.domain.util.CollectionUtils;

public class SqlBotConfigurationReserve extends SqlDaoBase implements BotConfigurationReserveDao {

    private static final String READ_BY_ID_QUERY = "SELECT * FROM bot_configuration_reserve WHERE id = :id";
    private static final String INSERT_QUERY = "INSERT INTO bot_configuration_reserve " +
            "(service_id, campaign_id, big_order_id, location_segment_id, configuration_id, quantity, storage_id, storage_quantity) VALUES " +
            "(:serviceId, :campaignId, :bigOrderId, :locationSegmentId, :configurationId, :quantity, :storageId, :storageQuantity)";
    private static final String UPDATE_QUERY = "UPDATE bot_configuration_reserve SET " +
            "service_id = :serviceId, campaign_id = :campaignId, big_order_id = :bigOrderId, location_segment_id = :locationSegmentId, " +
            "configuration_id = :configurationId, quantity = :quantity, storage_id = :storageId, storage_quantity = :storageQuantity " +
            "WHERE id = :id";
    private static final String REMOVE_BY_ID_QUERY = "DELETE FROM bot_configuration_reserve WHERE id = :id";
    private static final String TRUNCATE_QUERY = "TRUNCATE bot_configuration_reserve";

    private final HierarchySupplier hierarchySupplier;

    @Inject
    public SqlBotConfigurationReserve(final HierarchySupplier hierarchySupplier) {
        this.hierarchySupplier = hierarchySupplier;
    }

    @Override
    public BotConfigurationReserve read(final Long id) {
        final Hierarchy hierarchy = hierarchySupplier.get();
        return jdbcTemplate.queryForObject(READ_BY_ID_QUERY, Collections.singletonMap("id", id),
                (rs, i) -> toReserve(rs, hierarchy));
    }

    @Override
    public BotConfigurationReserve create(final BotConfigurationReserve reserve) {
        final long id = jdbcTemplate.insert(INSERT_QUERY, toParams(reserve));
        reserve.setId(id);
        return reserve;
    }

    @Override
    public boolean update(final BotConfigurationReserve reserve) {
        return jdbcTemplate.update(UPDATE_QUERY, toParams(reserve)) > 0;
    }

    @Override
    public boolean delete(final BotConfigurationReserve reserve) {
        return jdbcTemplate.update(REMOVE_BY_ID_QUERY, toParams(reserve)) > 0;
    }

    @Override
    public Collection<BotConfigurationReserve> getReserves(final BotServiceReserveFilter filter) {
        final Map<String, Object> params = new HashMap<>();
        final String query = query(filter, params);
        final Hierarchy hierarchy = hierarchySupplier.get();
        return jdbcTemplate.queryForSet(query, params, (rs, i) -> toReserve(rs, hierarchy));
    }

    @Override
    public boolean clear() {
        return jdbcTemplate.update(TRUNCATE_QUERY) > 0;
    }

    private String query(final BotServiceReserveFilter filter, final Map<String, Object> params) {
        return "SELECT * FROM bot_configuration_reserve" + where(filter, params);
    }

    private String where(final BotServiceReserveFilter filter, final Map<String, Object> params) {
        final List<String> conditions = new ArrayList<>(3);
        if (!filter.getServices().isEmpty()) {
            conditions.add("service_id IN (:service_ids)");
            params.put("service_ids", CollectionUtils.ids(filter.getServices()));
        }
        if (!filter.getBigOrderIds().isEmpty()) {
            conditions.add("big_order_id IN (:big_order_ids)");
            params.put("big_order_ids", filter.getBigOrderIds());
        }
        if (!filter.getCampaignIds().isEmpty()) {
            conditions.add("campaign_id IN (:campaign_ids)");
            params.put("campaign_ids", filter.getCampaignIds());
        }
        if (!filter.getCampaignBigOrders().isEmpty()) {
            final List<Campaign.CampaignOrder> campaignOrders = new ArrayList<>(filter.getCampaignBigOrders());
            final StringBuilder conditionBuilder = new StringBuilder();
            conditionBuilder.append("(");
            for (int i = 0; i < campaignOrders.size(); i++) {
                params.put("big_order_id_" + i, campaignOrders.get(i).getBigOrderId());
                params.put("campaign_id_" + i, campaignOrders.get(i).getCampaignId());
                if (i > 0) {
                    conditionBuilder.append(" OR ");
                }
                conditionBuilder.append("(r.big_order_id = :big_order_id_");
                conditionBuilder.append(i);
                conditionBuilder.append(" AND r.campaign_id = :campaign_id_");
                conditionBuilder.append(i);
                conditionBuilder.append(")");
            }
            conditionBuilder.append(")");
            conditions.add(conditionBuilder.toString());
        }
        if (conditions.isEmpty()) {
            return "";
        } else {
            return " WHERE " + StringUtils.join(conditions, " AND ");
        }
    }

    private BotConfigurationReserve toReserve(final ResultSet rs, final Hierarchy hierarchy) throws SQLException {
        return BotConfigurationReserve.builder()
                .id(rs.getLong("id"))
                .service(hierarchy.getServiceReader().read(rs.getLong("service_id")))
                .campaignId(rs.getLong("campaign_id"))
                .bigOrderId(rs.getLong("big_order_id"))
                .locationSegmentId(rs.getLong("location_segment_id"))
                .configurationId(rs.getLong("configuration_id"))
                .quantity(rs.getLong("quantity"))
                .storageId(getLong(rs, "storage_id"))
                .storageQuantity(getLong(rs, "storage_quantity"))
                .build();
    }

    private Map<String, ?> toParams(final BotConfigurationReserve reserve) {
        final Map<String, Object> params = new HashMap<>();
        params.put("id", reserve.getId());
        params.put("serviceId", reserve.getKey().getService().getId());
        params.put("campaignId", reserve.getKey().getCampaignId());
        params.put("bigOrderId", reserve.getKey().getBigOrderId());
        params.put("locationSegmentId", reserve.getKey().getLocationSegmentId());
        params.put("configurationId", reserve.getKey().getConfigurationId());
        params.put("quantity", reserve.getQuantity());
        params.put("storageId", reserve.getKey().getStorageId());
        params.put("storageQuantity", reserve.getStorageQuantity());
        return params;
    }

    @Override
    public Stream<BotConfigurationReserve> getReserveStreamFiltered(final BotServiceReserveFilter filter) {
        return getReserves(filter).stream();
    }
}
