package ru.yandex.qe.dispenser.domain.dao.campaign;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.collect.ImmutableMap;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.CustomSql;
import com.healthmarketscience.sqlbuilder.UpdateQuery;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSchema;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSpec;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
import org.jetbrains.annotations.NotNull;
import org.postgresql.util.PGobject;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import ru.yandex.qe.dispenser.domain.Campaign;
import ru.yandex.qe.dispenser.domain.CampaignForBot;
import ru.yandex.qe.dispenser.domain.CampaignUpdate;
import ru.yandex.qe.dispenser.domain.bot.BigOrder;
import ru.yandex.qe.dispenser.domain.dao.SqlDaoBase;
import ru.yandex.qe.dispenser.domain.dao.SqlUtils;
import ru.yandex.qe.dispenser.domain.dao.bot.bigorder.SqlBigOrderDao;

public class SqlCampaignDao extends SqlDaoBase implements CampaignDao {

    private static final String GET_ALL_QUERY = "SELECT c.id, c.key, c.name, c.status, c.start_date, c.request_creation_disabled, "
            + "c.request_modification_disabled_for_non_managers, "
            + "c.allowed_request_modification_when_closed, "
            + "c.allowed_modification_on_missing_additional_fields, "
            + "c.forced_allowing_request_status_change, "
            + "c.single_provider_request_mode, "
            + "c.allowed_request_creation_for_provider_admin, "
            + "c.allowed_request_creation_for_capacity_planner, "
            + "c.campaign_type, "
            + "c.request_modification_disabled, "
            + "cb.id as campaign_big_order_id, cb.big_order_id, bo.date FROM campaign c LEFT JOIN "
            + "campaign_big_order cb ON c.id = cb.campaign_id AND cb.deleted != TRUE LEFT JOIN "
            + "bot_big_order bo ON cb.big_order_id = bo.id";
    private static final String GET_ALL_NON_DELETED_QUERY = GET_ALL_QUERY + " WHERE c.deleted != TRUE";
    private static final String GET_ALL_NON_DELETED_SORTED_QUERY = GET_ALL_NON_DELETED_QUERY
            + " ORDER BY c.start_date DESC, c.id DESC, cb.big_order_id ASC";
    private static final String GET_BY_ID_QUERY = GET_ALL_NON_DELETED_QUERY + " AND c.id = :id ORDER BY cb.big_order_id ASC";
    private static final String GET_BY_KEY_QUERY = GET_ALL_NON_DELETED_QUERY + " AND c.key = :key ORDER BY cb.big_order_id ASC";
    private static final String GET_BY_IDS_QUERY = GET_ALL_NON_DELETED_QUERY + " AND c.id IN (:ids) ORDER BY c.id ASC, cb.big_order_id ASC";
    private static final String GET_BY_STATUS_QUERY = GET_ALL_NON_DELETED_QUERY + " AND c.status IN (%s) "
            + "ORDER BY c.start_date DESC, c.id DESC, cb.big_order_id ASC";
    private static final String GET_BY_ID_FOR_UPDATE_QUERY = GET_BY_ID_QUERY + " FOR UPDATE OF c";
    private static final String INSERT_CAMPAIGN_QUERY = "INSERT INTO campaign(key, name, status, start_date, request_creation_disabled, "
            + "request_modification_disabled_for_non_managers, "
            + "allowed_request_modification_when_closed, allowed_modification_on_missing_additional_fields, forced_allowing_request_status_change, single_provider_request_mode, allowed_request_creation_for_provider_admin, allowed_request_creation_for_capacity_planner, campaign_type, request_modification_disabled) "
            + "VALUES (:key, :name, cast(:status AS campaign_status), :start_date, "
            + ":request_creation_disabled, :request_modification_disabled_for_non_managers, "
            + ":allowed_request_modification_when_closed, "
            + ":allowed_modification_on_missing_additional_fields, :forced_allowing_request_status_change, :single_provider_request_mode, :allowed_request_creation_for_provider_admin, :allowed_request_creation_for_capacity_planner, cast(:campaign_type as campaign_type), :request_modification_disabled)";
    private static final String INSERT_CAMPAIGN_TEST_QUERY = "INSERT INTO campaign(id, key, name, status, start_date, request_creation_disabled, "
            + "request_modification_disabled_for_non_managers, "
            + "allowed_request_modification_when_closed, allowed_modification_on_missing_additional_fields, forced_allowing_request_status_change, single_provider_request_mode, allowed_request_creation_for_provider_admin, allowed_request_creation_for_capacity_planner, campaign_type, request_modification_disabled) "
            + "VALUES (:id, :key, :name, cast(:status AS campaign_status), :start_date, "
            + ":request_creation_disabled, :request_modification_disabled_for_non_managers, "
            + ":allowed_request_modification_when_closed, "
            + ":allowed_modification_on_missing_additional_fields, :forced_allowing_request_status_change, :single_provider_request_mode, :allowed_request_creation_for_provider_admin, :allowed_request_creation_for_capacity_planner, cast(:campaign_type as campaign_type), :request_modification_disabled)";
    private static final String UPDATE_CAMPAIGN_QUERY = "UPDATE campaign SET name = :name, status = cast(:status AS campaign_status), "
            + "start_date = :start_date, request_creation_disabled = :request_creation_disabled,"
            + " request_modification_disabled_for_non_managers = "
            + ":request_modification_disabled_for_non_managers, allowed_request_modification_when_closed = "
            + ":allowed_request_modification_when_closed, allowed_modification_on_missing_additional_fields = "
            + ":allowed_modification_on_missing_additional_fields, forced_allowing_request_status_change = :forced_allowing_request_status_change, "
            + "single_provider_request_mode = :single_provider_request_mode, "
            + "allowed_request_creation_for_provider_admin = :allowed_request_creation_for_provider_admin, "
            + "allowed_request_creation_for_capacity_planner = :allowed_request_creation_for_capacity_planner, "
            + "request_modification_disabled = :request_modification_disabled WHERE id = :id";
    private static final String DELETE_CAMPAIGN_QUERY = "UPDATE campaign SET deleted = TRUE WHERE id = :id";
    private static final String INSERT_BIG_ORDERS_FOR_CAMPAIGN_QUERY = "INSERT INTO campaign_big_order(campaign_id, big_order_id) "
            + "VALUES (:campaign_id, :big_order_id) ON CONFLICT DO NOTHING";
    private static final String CLEAR_BIG_ORDERS_FROM_CAMPAIGN_QUERY = "UPDATE campaign_big_order SET deleted = "
            + "big_order_id NOT IN (:big_orders) WHERE campaign_id = :id";
    private static final String CLEAR_QUERY = "TRUNCATE campaign CASCADE";
    private static final String GET_CAMPAIGN_ORDERS = "SELECT id, campaign_id, big_order_id FROM campaign_big_order WHERE id IN (:ids) "
            + "AND deleted != TRUE ORDER BY id ASC";
    private static final String GET_CAMPAIGN_ORDERS_BY_ORDER = "SELECT id, campaign_id, big_order_id FROM campaign_big_order WHERE big_order_id IN (:big_order_ids) "
            + "AND deleted != TRUE ORDER BY id ASC";
    private static final String GET_ACTIVE_FOR_BOT_QUERY = "SELECT c.id, c.name, c.key, c.start_date, c.status, c.campaign_type, "
            + "bo.id as big_order_id, bo.date, bo.configs FROM campaign c LEFT JOIN "
            + "campaign_big_order cb ON c.id = cb.campaign_id AND cb.deleted != TRUE LEFT JOIN "
            + "bot_big_order bo ON cb.big_order_id = bo.id WHERE c.deleted != TRUE "
            + "AND c.status = cast(:active_status AS campaign_status) ORDER BY c.start_date DESC, "
            + "c.id DESC, cb.big_order_id ASC";
    private static final String GET_FOR_BOT_QUERY = "SELECT c.id, c.name, c.key, c.start_date, c.status, c.campaign_type, "
            + "bo.id as big_order_id, bo.date, bo.configs FROM campaign c LEFT JOIN "
            + "campaign_big_order cb ON c.id = cb.campaign_id AND cb.deleted != TRUE LEFT JOIN "
            + "bot_big_order bo ON cb.big_order_id = bo.id WHERE c.deleted != TRUE AND c.id IN (:ids) "
            + "ORDER BY c.start_date DESC, c.id DESC, cb.big_order_id ASC";
    private static final String GET_BY_ID_WITH_DELETED_QUERY = GET_ALL_QUERY + " WHERE c.id = :id ORDER BY cb.big_order_id ASC";

    @NotNull
    @Override
    public Set<Campaign> getAll() {
        return new HashSet<>(getAllSorted(new HashSet<>()));
    }

    @NotNull
    @Override
    public List<Campaign> getAllSorted(@NotNull final Set<Campaign.Status> statuses) {
        if (statuses.isEmpty()) {
            return jdbcTemplate.query(GET_ALL_NON_DELETED_SORTED_QUERY, SqlCampaignDao::getCampaigns);
        } else {
            final StringBuilder inBuilder = new StringBuilder();
            final Map<String, Object> inParams = new HashMap<>();
            statuses.forEach(s -> {
                if (inBuilder.length() != 0) {
                    inBuilder.append(", ");
                }
                inBuilder.append("cast(:");
                inBuilder.append(s.name().toLowerCase());
                inBuilder.append("_status");
                inBuilder.append(" AS campaign_status)");
                inParams.put(s.name().toLowerCase() + "_status", s.name());
            });
            final String query = String.format(GET_BY_STATUS_QUERY, inBuilder.toString());
            return jdbcTemplate.query(query, new MapSqlParameterSource(inParams), SqlCampaignDao::getCampaigns);
        }
    }

    @NotNull
    @Override
    public Campaign create(@NotNull final Campaign newInstance) {
        try {
            final long id = jdbcTemplate.insert(INSERT_CAMPAIGN_QUERY, toCreateParams(newInstance));
            addBigOrdersToCampaign(id, newInstance.getBigOrders());
            return read(id);
        } catch (DuplicateKeyException ex) {
            throw new IllegalArgumentException("Duplicate field value violates uniqueness constraint", ex);
        }
    }

    @NotNull
    @Override
    public Campaign read(@NotNull final Long id) throws EmptyResultDataAccessException {
        return jdbcTemplate.query(GET_BY_ID_QUERY, new MapSqlParameterSource("id", id), SqlCampaignDao::getCampaigns)
                .stream().findFirst().orElseThrow(() -> new EmptyResultDataAccessException("No campaign with id " + id, 1));
    }

    @Override
    public boolean update(@NotNull final Campaign transientObject) {
        try {
            return jdbcTemplate.update(UPDATE_CAMPAIGN_QUERY, toUpdateParams(transientObject)) > 0;
        } catch (DuplicateKeyException ex) {
            throw new IllegalArgumentException("Duplicate field value violates uniqueness constraint", ex);
        }
    }

    @NotNull
    @Override
    public Campaign readForUpdate(@NotNull final Long id) throws EmptyResultDataAccessException {
        return jdbcTemplate.query(GET_BY_ID_FOR_UPDATE_QUERY, new MapSqlParameterSource("id", id), SqlCampaignDao::getCampaigns)
                .stream().findFirst().orElseThrow(() -> new EmptyResultDataAccessException("No campaign with id " + id, 1));
    }

    @Override
    public void partialUpdate(@NotNull final Campaign current, @NotNull final CampaignUpdate update) {
        if (update.hasSimpleFieldUpdates()) {
            final ImmutableMap<String, Object> params = preparePartialUpdateParams(current, update);
            final String partialUpdateQuery = preparePartialUpdateQuery(update);
            try {
                jdbcTemplate.update(partialUpdateQuery, params);
            } catch (DuplicateKeyException ex) {
                throw new IllegalArgumentException("Duplicate field value violates uniqueness constraint", ex);
            }
        }
        if (update.getBigOrders() != null) {
            final Set<Long> bigOrderIds = update.getBigOrders().stream().map(Campaign.BigOrder::getBigOrderId).collect(Collectors.toSet());
            addBigOrdersToCampaign(current.getId(), update.getBigOrders());
            jdbcTemplate.update(CLEAR_BIG_ORDERS_FROM_CAMPAIGN_QUERY, ImmutableMap.of("id", current.getId(), "big_orders", bigOrderIds));
        }
    }

    @Override
    public List<Campaign.CampaignOrder> getCampaignOrders(final @NotNull Set<Long> ids) {
        if (ids.isEmpty()) {
            return List.of();
        }
        return jdbcTemplate.query(GET_CAMPAIGN_ORDERS, new MapSqlParameterSource("ids", ids), (rs, rn) -> {
            final long id = rs.getLong("id");
            final long campaignId = rs.getLong("campaign_id");
            final long bigOrderId = rs.getLong("big_order_id");
            return new Campaign.CampaignOrder(id, campaignId, bigOrderId);
        });
    }

    @Override
    public List<Campaign.CampaignOrder> getCampaignOrdersByOrderIds(final @NotNull Set<Long> bigOrderIds) {
        if (bigOrderIds.isEmpty()) {
            return List.of();
        }
        return jdbcTemplate.query(GET_CAMPAIGN_ORDERS_BY_ORDER, new MapSqlParameterSource("big_order_ids", bigOrderIds), (rs, rn) -> {
            final long id = rs.getLong("id");
            final long campaignId = rs.getLong("campaign_id");
            final long bigOrderId = rs.getLong("big_order_id");
            return new Campaign.CampaignOrder(id, campaignId, bigOrderId);
        });
    }

    @Override
    public @NotNull Optional<CampaignForBot> getActiveForBotIntegration() {
        return jdbcTemplate.query(GET_ACTIVE_FOR_BOT_QUERY, new MapSqlParameterSource("active_status", Campaign.Status.ACTIVE.name()),
                SqlCampaignDao::getCampaignsForBot).stream().findFirst();
    }

    @Override
    public Map<Long, CampaignForBot> readForBot(final @NotNull Set<Long> ids) {
        if (ids.isEmpty()) {
            return Collections.emptyMap();
        }
        return jdbcTemplate.query(GET_FOR_BOT_QUERY, new MapSqlParameterSource("ids", ids), SqlCampaignDao::getCampaignsForBot).stream()
                .collect(Collectors.toMap(CampaignForBot::getId, Function.identity()));
    }

    @Override
    public @NotNull Campaign readWithDeleted(final @NotNull Long id) throws EmptyResultDataAccessException {
        return jdbcTemplate.query(GET_BY_ID_WITH_DELETED_QUERY, new MapSqlParameterSource("id", id), SqlCampaignDao::getCampaigns)
                .stream().findFirst().orElseThrow(() -> new EmptyResultDataAccessException("No campaign with id " + id, 1));
    }

    @NotNull
    @Override
    public Optional<Campaign> readOptional(final long id) {
        return jdbcTemplate.query(GET_BY_ID_QUERY, new MapSqlParameterSource("id", id), SqlCampaignDao::getCampaigns)
                .stream().findFirst();
    }

    @Override
    public Set<Campaign> readByIds(Set<Long> ids) {
        if (ids.isEmpty()) {
            return Set.of();
        }
        return new HashSet<>(jdbcTemplate.query(GET_BY_IDS_QUERY,
                new MapSqlParameterSource("ids", ids), SqlCampaignDao::getCampaigns));
    }

    @Override
    public Optional<Campaign> getByKey(String key) {
        return jdbcTemplate.query(GET_BY_KEY_QUERY, new MapSqlParameterSource("key", key),
                SqlCampaignDao::getCampaigns).stream().findFirst();
    }

    @Override
    public Campaign insertForTest(Campaign campaign) {
        try {
            jdbcTemplate.insert(INSERT_CAMPAIGN_TEST_QUERY, toTestCreateParams(campaign));
            addBigOrdersToCampaign(campaign.getId(), campaign.getBigOrders());
            return read(campaign.getId());
        } catch (DuplicateKeyException ex) {
            throw new IllegalArgumentException("Duplicate field value violates uniqueness constraint", ex);
        }
    }

    @Override
    public boolean delete(@NotNull final Campaign persistentObject) {
        return jdbcTemplate.update(DELETE_CAMPAIGN_QUERY, ImmutableMap.of("id", persistentObject.getId())) > 0;
    }

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

    private void addBigOrdersToCampaign(final long campaignId, final Collection<Campaign.BigOrder> bigOrders) {
        final List<Map<String, ?>> params = bigOrders.stream()
                .map(bigOrder -> ImmutableMap.<String, Object>of("big_order_id", bigOrder.getBigOrderId(), "campaign_id", campaignId))
                .collect(Collectors.toList());
        jdbcTemplate.batchUpdate(INSERT_BIG_ORDERS_FOR_CAMPAIGN_QUERY, params);
    }

    private static List<Campaign> getCampaigns(final ResultSet rs) throws SQLException {
        final List<Campaign> result = new ArrayList<>();
        final Map<Long, Campaign.BigOrder> bigOrdersCache = new HashMap<>();
        Campaign.Builder currentCampaignBuilder = Campaign.builder();
        Long currentId = null;
        List<Campaign.BigOrder> currentBigOrders = new ArrayList<>();
        while (rs.next()) {
            final long id = rs.getLong("id");
            final long bigOrderId = rs.getLong("big_order_id");
            final boolean missingBigOrder = rs.wasNull();
            if (currentId == null || currentId != id) {
                if (currentId != null) {
                    result.add(currentCampaignBuilder.setBigOrders(currentBigOrders).build());
                }
                currentCampaignBuilder = Campaign.builder()
                        .setName(rs.getString("name"))
                        .setStatus(Campaign.Status.valueOf(rs.getString("status")))
                        .setStartDate(rs.getDate("start_date").toLocalDate())
                        .setKey(rs.getString("key"))
                        .setRequestCreationDisabled(rs.getBoolean("request_creation_disabled"))
                        .setRequestModificationDisabledForNonManagers(rs.getBoolean("request_modification_disabled_for_non_managers"))
                        .setAllowedRequestModificationWhenClosed(rs.getBoolean("allowed_request_modification_when_closed"))
                        .setAllowedModificationOnMissingAdditionalFields(rs.getBoolean("allowed_modification_on_missing_additional_fields"))
                        .setForcedAllowingRequestStatusChange(rs.getBoolean("forced_allowing_request_status_change"))
                        .setSingleProviderRequestModeEnabled(rs.getBoolean("single_provider_request_mode"))
                        .setAllowedRequestCreationForProviderAdmin(rs.getBoolean("allowed_request_creation_for_provider_admin"))
                        .setAllowedRequestCreationForCapacityPlanner(rs.getBoolean("allowed_request_creation_for_capacity_planner"))
                        .setType(Campaign.Type.valueOf(rs.getString("campaign_type")))
                        .setRequestModificationDisabled(rs.getBoolean("request_modification_disabled"))
                        .setId(id);
                currentId = id;
                currentBigOrders = new ArrayList<>();
            }
            if (!missingBigOrder) {
                final long campaignBigOrderId = rs.getLong("campaign_big_order_id");
                final Campaign.BigOrder bigOrder;
                if (bigOrdersCache.containsKey(campaignBigOrderId)) {
                    bigOrder = bigOrdersCache.get(campaignBigOrderId);
                } else {
                    bigOrder = new Campaign.BigOrder(bigOrderId, LocalDate.parse(rs.getString("date"),
                            DateTimeFormatter.ISO_LOCAL_DATE));
                    bigOrder.setId(campaignBigOrderId);
                    bigOrdersCache.put(campaignBigOrderId, bigOrder);
                }
                currentBigOrders.add(bigOrder);
            }
        }
        if (currentId != null) {
            result.add(currentCampaignBuilder.setBigOrders(currentBigOrders).build());
        }
        return result;
    }

    private static List<CampaignForBot> getCampaignsForBot(final ResultSet rs) throws SQLException {
        final List<CampaignForBot> result = new ArrayList<>();
        final Map<Long, BigOrder> bigOrdersCache = new HashMap<>();
        CampaignForBot.Builder currentCampaignBuilder = CampaignForBot.builder();
        Long currentId = null;
        List<BigOrder> currentBigOrders = new ArrayList<>();
        while (rs.next()) {
            final long id = rs.getLong("id");
            final long bigOrderId = rs.getLong("big_order_id");
            final boolean missingBigOrder = rs.wasNull();
            if (currentId == null || currentId != id) {
                if (currentId != null) {
                    result.add(currentCampaignBuilder.setBigOrders(currentBigOrders).build());
                }
                currentCampaignBuilder = CampaignForBot.builder()
                        .setKey(rs.getString("key"))
                        .setName(rs.getString("name"))
                        .setStartDate(rs.getDate("start_date").toLocalDate())
                        .setStatus(Campaign.Status.valueOf(rs.getString("status")))
                        .setType(Campaign.Type.valueOf(rs.getString("campaign_type")))
                        .setId(id);
                currentId = id;
                currentBigOrders = new ArrayList<>();
            }
            if (!missingBigOrder) {
                final BigOrder bigOrder;
                if (bigOrdersCache.containsKey(bigOrderId)) {
                    bigOrder = bigOrdersCache.get(bigOrderId);
                } else {
                    bigOrder = BigOrder.builder(LocalDate.parse(rs.getString("date"), DateTimeFormatter.ISO_LOCAL_DATE))
                            .configs(SqlUtils.fromJsonb((PGobject) rs.getObject("configs"),
                                    SqlBigOrderDao.CONFIG_TYPE))
                            .build(bigOrderId);
                    bigOrdersCache.put(bigOrderId, bigOrder);
                }
                currentBigOrders.add(bigOrder);
            }
        }
        if (currentId != null) {
            result.add(currentCampaignBuilder.setBigOrders(currentBigOrders).build());
        }
        return result;
    }

    private static Map<String, Object> toCreateParams(final Campaign campaign) {
        return ImmutableMap.<String, Object>builder()
                .put("key", campaign.getKey())
                .put("status", campaign.getStatus().name())
                .put("name", campaign.getName())
                .put("start_date", java.sql.Date.valueOf(campaign.getStartDate()))
                .put("request_creation_disabled", campaign.isRequestCreationDisabled())
                .put("request_modification_disabled_for_non_managers", campaign.isRequestModificationDisabledForNonManagers())
                .put("allowed_request_modification_when_closed", campaign.isAllowedRequestModificationWhenClosed())
                .put("allowed_modification_on_missing_additional_fields", campaign.isAllowedModificationOnMissingAdditionalFields())
                .put("forced_allowing_request_status_change", campaign.isForcedAllowingRequestStatusChange())
                .put("single_provider_request_mode", campaign.isSingleProviderRequestModeEnabled())
                .put("allowed_request_creation_for_provider_admin", campaign.isAllowedRequestCreationForProviderAdmin())
                .put("allowed_request_creation_for_capacity_planner", campaign.isAllowedRequestCreationForCapacityPlanner())
                .put("campaign_type", campaign.getType().name())
                .put("request_modification_disabled", campaign.isRequestModificationDisabled())
                .build();

    }

    private static Map<String, Object> toTestCreateParams(final Campaign campaign) {
        return ImmutableMap.<String, Object>builder()
                .put("id", campaign.getId())
                .put("key", campaign.getKey())
                .put("status", campaign.getStatus().name())
                .put("name", campaign.getName())
                .put("start_date", java.sql.Date.valueOf(campaign.getStartDate()))
                .put("request_creation_disabled", campaign.isRequestCreationDisabled())
                .put("request_modification_disabled_for_non_managers", campaign.isRequestModificationDisabledForNonManagers())
                .put("allowed_request_modification_when_closed", campaign.isAllowedRequestModificationWhenClosed())
                .put("allowed_modification_on_missing_additional_fields", campaign.isAllowedModificationOnMissingAdditionalFields())
                .put("forced_allowing_request_status_change", campaign.isForcedAllowingRequestStatusChange())
                .put("single_provider_request_mode", campaign.isSingleProviderRequestModeEnabled())
                .put("allowed_request_creation_for_provider_admin", campaign.isAllowedRequestCreationForProviderAdmin())
                .put("allowed_request_creation_for_capacity_planner", campaign.isAllowedRequestCreationForCapacityPlanner())
                .put("campaign_type", campaign.getType().name())
                .put("request_modification_disabled", campaign.isRequestModificationDisabled())
                .build();

    }

    private static Map<String, Object> toUpdateParams(final Campaign campaign) {
        return ImmutableMap.<String, Object>builder()
                .put("id", campaign.getId())
                .put("status", campaign.getStatus().name())
                .put("name", campaign.getName())
                .put("start_date", java.sql.Date.valueOf(campaign.getStartDate()))
                .put("request_creation_disabled", campaign.isRequestCreationDisabled())
                .put("request_modification_disabled_for_non_managers", campaign.isRequestModificationDisabledForNonManagers())
                .put("allowed_request_modification_when_closed", campaign.isAllowedRequestModificationWhenClosed())
                .put("allowed_modification_on_missing_additional_fields", campaign.isAllowedModificationOnMissingAdditionalFields())
                .put("forced_allowing_request_status_change", campaign.isForcedAllowingRequestStatusChange())
                .put("single_provider_request_mode", campaign.isSingleProviderRequestModeEnabled())
                .put("allowed_request_creation_for_provider_admin", campaign.isAllowedRequestCreationForProviderAdmin())
                .put("allowed_request_creation_for_capacity_planner", campaign.isAllowedRequestCreationForCapacityPlanner())
                .put("request_modification_disabled", campaign.isRequestModificationDisabled())
                .build();
    }

    private ImmutableMap<String, Object> preparePartialUpdateParams(final Campaign current, final CampaignUpdate update) {
        final ImmutableMap.Builder<String, Object> paramsBuilder = ImmutableMap.<String, Object>builder().put("id", current.getId());
        if (update.getStatus() != null) {
            paramsBuilder.put("status", update.getStatus().name());
        }
        if (update.getName() != null) {
            paramsBuilder.put("name", update.getName());
        }
        if (update.getStartDate() != null) {
            paramsBuilder.put("start_date", java.sql.Date.valueOf(update.getStartDate()));
        }
        if (update.getRequestCreationDisabled() != null) {
            paramsBuilder.put("request_creation_disabled", update.getRequestCreationDisabled());
        }
        if (update.getRequestModificationDisabledForNonManagers() != null) {
            paramsBuilder.put("request_modification_disabled_for_non_managers", update.getRequestModificationDisabledForNonManagers());
        }
        if (update.getAllowedRequestModificationWhenClosed() != null) {
            paramsBuilder.put("allowed_request_modification_when_closed", update.getAllowedRequestModificationWhenClosed());
        }
        if (update.getAllowedModificationOnMissingAdditionalFields() != null) {
            paramsBuilder.put("allowed_modification_on_missing_additional_fields", update.getAllowedModificationOnMissingAdditionalFields());
        }
        if (update.getForcedAllowingRequestStatusChange() != null) {
            paramsBuilder.put("forced_allowing_request_status_change", update.getForcedAllowingRequestStatusChange());
        }
        if (update.getSingleProviderRequestModeEnabled() != null) {
            paramsBuilder.put("single_provider_request_mode", update.getSingleProviderRequestModeEnabled());
        }
        if (update.getAllowedRequestCreationForProviderAdmin() != null) {
            paramsBuilder.put("allowed_request_creation_for_provider_admin", update.getAllowedRequestCreationForProviderAdmin());
        }
        if (update.getAllowedRequestCreationForCapacityPlanner() != null) {
            paramsBuilder.put("allowed_request_creation_for_capacity_planner", update.getAllowedRequestCreationForCapacityPlanner());
        }
        if (update.getRequestModificationDisabled() != null) {
            paramsBuilder.put("request_modification_disabled", update.getRequestModificationDisabled());
        }
        return paramsBuilder.build();
    }

    private String preparePartialUpdateQuery(final CampaignUpdate update) {
        final DbSchema schema = new DbSchema(new DbSpec(), "public");
        final DbTable table = new DbTable(schema, "campaign", "c");
        final DbColumn idColumn = table.addColumn("id");
        final DbColumn nameColumn = table.addColumn("name");
        final DbColumn statusColumn = table.addColumn("status");
        final DbColumn startDateColumn = table.addColumn("start_date");
        final DbColumn requestCreationDisabledColumn = table.addColumn("request_creation_disabled");
        final DbColumn requestModificationDisabledForNonManagersColumn = table.addColumn("request_modification_disabled_for_non_managers");
        final DbColumn allowedRequestModificationWhenClosedColumn = table.addColumn("allowed_request_modification_when_closed");
        final DbColumn allowedModificationOnMissingAdditionalFieldsColumn = table.addColumn("allowed_modification_on_missing_additional_fields");
        final DbColumn forcedAllowingRequestStatusChangeColumn = table.addColumn("forced_allowing_request_status_change");
        final DbColumn singleProviderRequestModeColumn = table.addColumn("single_provider_request_mode");
        final DbColumn allowedRequestCreationForProviderAdminColumn = table.addColumn("allowed_request_creation_for_provider_admin");
        final DbColumn allowedRequestCreationForCapacityPlannerColumn = table.addColumn("allowed_request_creation_for_capacity_planner");
        final DbColumn requestModificationDisabledColumn = table.addColumn("request_modification_disabled");
        final UpdateQuery updateQuery = new UpdateQuery(table);
        updateQuery.addCondition(BinaryCondition.equalTo(idColumn, new CustomSql(":id")));
        if (update.getName() != null) {
            updateQuery.addCustomSetClause(nameColumn, new CustomSql(":name"));
        }
        if (update.getStatus() != null) {
            updateQuery.addCustomSetClause(statusColumn, new CustomSql("cast(:status AS campaign_status)"));
        }
        if (update.getStartDate() != null) {
            updateQuery.addCustomSetClause(startDateColumn, new CustomSql(":start_date"));
        }
        if (update.getRequestCreationDisabled() != null) {
            updateQuery.addCustomSetClause(requestCreationDisabledColumn, new CustomSql(":request_creation_disabled"));
        }
        if (update.getRequestModificationDisabledForNonManagers() != null) {
            updateQuery.addCustomSetClause(requestModificationDisabledForNonManagersColumn,
                    new CustomSql(":request_modification_disabled_for_non_managers"));
        }
        if (update.getAllowedRequestModificationWhenClosed() != null) {
            updateQuery.addCustomSetClause(allowedRequestModificationWhenClosedColumn,
                    new CustomSql(":allowed_request_modification_when_closed"));
        }
        if (update.getAllowedModificationOnMissingAdditionalFields() != null) {
            updateQuery.addCustomSetClause(allowedModificationOnMissingAdditionalFieldsColumn,
                    new CustomSql(":allowed_modification_on_missing_additional_fields"));
        }
        if (update.getForcedAllowingRequestStatusChange() != null) {
            updateQuery.addCustomSetClause(forcedAllowingRequestStatusChangeColumn,
                    new CustomSql(":forced_allowing_request_status_change"));
        }
        if (update.getSingleProviderRequestModeEnabled() != null) {
            updateQuery.addCustomSetClause(singleProviderRequestModeColumn,
                    new CustomSql(":single_provider_request_mode"));
        }
        if (update.getAllowedRequestCreationForProviderAdmin() != null) {
            updateQuery.addCustomSetClause(allowedRequestCreationForProviderAdminColumn,
                    new CustomSql(":allowed_request_creation_for_provider_admin"));
        }
        if (update.getAllowedRequestCreationForCapacityPlanner() != null) {
            updateQuery.addCustomSetClause(allowedRequestCreationForCapacityPlannerColumn,
                    new CustomSql(":allowed_request_creation_for_capacity_planner"));
        }
        if (update.getRequestModificationDisabled() != null) {
            updateQuery.addCustomSetClause(requestModificationDisabledColumn,
                    new CustomSql(":request_modification_disabled"));
        }
        return updateQuery.toString();
    }

}
