package ru.yandex.wmconsole.service;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;

import ru.yandex.wmconsole.data.HostInfoStatusEnum;
import ru.yandex.wmconsole.data.info.HostUnavailableInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.service.AbstractDbService;
import ru.yandex.wmtools.common.util.IServiceJdbcTemplate;

/**
 * Сервис для временного хранения проблемных хостов
 *
 * @author Alexey Zakharov <azakharov@yandex-team.ru>
 */
public class HostStatusService extends AbstractDbService {
    private static final Logger log = LoggerFactory.getLogger(HostStatusService.class);

    // Запрос для случая, когда список hostsToHold пустой
    private static final String DELETE_BAD_HOSTS_QUERY =
            "DELETE FROM tbl_host_penalty_status_history ";

    // Запрос для случая, когда список hostsToHold не пустой
    private static final String DELETE_BAD_HOSTS_LONG_QUERY =
            DELETE_BAD_HOSTS_QUERY + " WHERE host_id NOT IN ";

    private static final String REPLACE_BAD_HOSTS_QUERY =
            "REPLACE INTO tbl_host_penalty_status_history (host_id, status) VALUES " +
            "(?, ?)";

    private static final String SELECT_NOT_CHANGED_HOSTS_QUERY =
            "SELECT count(*) FROM tbl_host_penalty_status_history " +
            "WHERE (host_id = ?) AND (status = ?)";

    /**
     * Получить список хостов с изменившимся статусом
     *
     * @param badHostStatus список хостов с ошибочным статусом
     * @param dbIndex индекс БД, в которой анализируются хосты
     * @param hostsToHold выходной параметр - список идентификаторов хостов, которые нужно оставить в БД
     * @return список хостов с изменившимся статусом
     * @throws InternalException
     */
    public List<HostUnavailableInfo> getChangedHosts(
            final List<HostUnavailableInfo> badHostStatus,
            final int dbIndex,
            final List<Long> hostsToHold) throws InternalException {
         // NOTE! Получение изменившихся хостов не сделать одним запросом, потому что статус вычисляется в коде
         // в методе getCalculatedHostInfoStatus. Это возможное направление для оптимизации.

         final List<HostUnavailableInfo> result = new LinkedList<HostUnavailableInfo>();

         for (HostUnavailableInfo info : badHostStatus) {
             final HostInfoStatusEnum status = info.getCalculatedHostInfoStatus();
             if (status == null) {
                 continue;
             }

             final Integer rowsFound = getJdbcTemplate(new WMCPartition(dbIndex)).safeQueryForInt(
                     SELECT_NOT_CHANGED_HOSTS_QUERY, info.getHostId(), status.getValue());
             if (rowsFound != null && rowsFound > 0) {
                 // Ничего не изменилось (Ошибка была и есть) -- запоминаем идентификатор хоста, чтобы оставить запись в БД
                 // Это нужно, иначе в следующий раз мы снова отправим сообщение об ошибке
                 hostsToHold.add(info.getHostId());
             } else {
                 // Если не найдена строка для данного хоста для даной базы с таким же статусом, то она входит в результат
                 result.add(info);
             }
         }

         return result;
    }

    /**
     * Сохранить хосты в таблице истории сообщений об ошибке
     *
     * @param badHostStatus
     * @param dbIndex
     * @param hostsToHold
     * @throws InternalException
     */
     public void saveBadHosts(
             final List<HostUnavailableInfo> badHostStatus,
             final int dbIndex,
             final List<Long> hostsToHold) throws InternalException {
         final IServiceJdbcTemplate jdbcTemplate = getJdbcTemplate(new WMCPartition(dbIndex));
         // Удаление старых статусов хостов для данной БД
         if (hostsToHold.isEmpty()) {
             jdbcTemplate.update(DELETE_BAD_HOSTS_QUERY);
         } else {
             jdbcTemplate.update(DELETE_BAD_HOSTS_LONG_QUERY + getHostIdCommaSeparatedString(hostsToHold));
         }

         // Сохранение новых статусов хостов
         try {
             getJdbcTemplate(new WMCPartition(dbIndex)).getJdbcOperations().batchUpdate(
                     REPLACE_BAD_HOSTS_QUERY,
                     new BatchPreparedStatementSetter() {
                         private final Iterator<HostUnavailableInfo> iterator = badHostStatus.iterator();

                         @Override
                         public void setValues(final PreparedStatement ps, final int i) throws SQLException {
                             final HostUnavailableInfo info = iterator.next();
                             ps.setLong(1, info.getHostId());
                             final HostInfoStatusEnum status = info.getCalculatedHostInfoStatus();
                             if (status != null) {
                                 ps.setInt(2, status.getValue());
                             } else {
                                 ps.setNull(2, Types.INTEGER);
                             }
                         }

                         @Override
                         public int getBatchSize() {
                             return badHostStatus.size();
                         }
                     });
         } catch (Exception e) {
             log.error("Exception in while saving bad host");
         }
     }

     public void saveBadHost(int dbIndex, long hostId, int status) {
         try {
             getJdbcTemplate(new WMCPartition(dbIndex)).update(
                     REPLACE_BAD_HOSTS_QUERY, hostId, status);
         } catch (InternalException e) {
             log.error("Exception in while saving bad host");
         }
     }

     private String getHostIdCommaSeparatedString(final List<Long> ids) {
         final StringBuilder sb = new StringBuilder();
         sb.append('(');
         boolean first = true;
         for (Long id: ids) {
             if (!first) {
                 sb.append(',');
             } else {
                 first = false;
             }
             sb.append(id);
         }
         sb.append(')');
         return sb.toString();
     }
}
