package ru.yandex.wmconsole.service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import org.springframework.transaction.TransactionStatus;

import ru.yandex.wmconsole.data.NotificationTypeEnum;
import ru.yandex.wmconsole.data.info.NotificationInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmconsole.data.wrappers.NotificationTypeInfoWrapper;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.service.AbstractDbService;
import ru.yandex.wmtools.common.util.ServiceTransactionCallbackWithoutResult;
import ru.yandex.wmtools.common.util.SqlUtil;

/**
 * @author Andrey Mima (amima@yandex-team.ru)
 */
public class NotificationSenderService extends AbstractDbService {
    private static final Logger log = LoggerFactory.getLogger(NotificationSenderService.class);

    private int outOfDateDays;
    private int concurrentNotifications;

    private static final String SELECT_NOTIFICATIONS_TO_SEND_QUERY =
            "SELECT " +
                        "tbl_notifications.notification_id, " +
                        "tbl_notifications.notification_type, " +
                        "tbl_notifications.user_id, " +
                        "tbl_notifications.issue_id, " +
                        "tbl_notifications.happened_time " +
                    "FROM " +
                        "tbl_notifications " +
                    "WHERE " +
                        "tbl_notifications.progress_started IS NULL " +
                    "AND " +
                        "DATEDIFF(NOW(), tbl_notifications.happened_time) < ? " +
                    "AND " +
                        "NOT notification_type = "+ NotificationTypeEnum.MAIN_MIRROR.getValue() + " " +
                    "AND " +
                        "NOT notification_type = " + NotificationTypeEnum.MIRROR_GROUPS.getValue() + " " +
                    "ORDER BY " +
                        "tbl_notifications.user_id, tbl_notifications.happened_time " +
                    "LIMIT 0, ? " +
                    "FOR UPDATE";

    private static final String UPDATE_START_PROGRESS_QUERY =
            "UPDATE " +
                        "tbl_notifications " +
                    "SET " +
                        "tbl_notifications.progress_started = NOW() " +
                    "WHERE " +
                        "tbl_notifications.notification_id IN (%s)";

    private static final String DELETE_OLD_NOTIFICATIONS_QUERY =
            "DELETE FROM " +
                        "tbl_notifications " +
                    "WHERE " +
                        "DATEDIFF(NOW(), tbl_notifications.happened_time) > ?";

    private static final ParameterizedRowMapper<NotificationInfo> notificationMapper =
        new ParameterizedRowMapper<NotificationInfo>() {
            @Override
            public NotificationInfo mapRow(ResultSet resultSet, int rowNum) throws SQLException {
                Long notificationId = resultSet.getLong("notification_id");
                long userId = resultSet.getLong("user_id");
                Integer notificationType = resultSet.getInt("notification_type");
                Long issueId = resultSet.getLong("issue_id");
                Date happened = resultSet.getTimestamp("happened_time");

                return new NotificationInfo(notificationId, happened, userId, notificationType, issueId);
            }
        };

    public Collection<NotificationInfo> getUnnotifiedNotifications() throws UserException, InternalException {
        final List<NotificationInfo> notifications = new ArrayList<NotificationInfo>(concurrentNotifications);

        getServiceTransactionTemplate(WMCPartition.nullPartition()).executeInService(new ServiceTransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) throws InternalException {
                log.debug("Success select logging: NOTIFICATIONS_TO_SEND_QUERY");
                List<NotificationInfo> localNotifications = getJdbcTemplate(WMCPartition.nullPartition()).query(
                        SELECT_NOTIFICATIONS_TO_SEND_QUERY,
                        notificationMapper,
                        outOfDateDays,
                        concurrentNotifications
                );

                startProgress(localNotifications);

                notifications.addAll(localNotifications);
            }
        });

        return notifications;
    }

    private void startProgress(List<NotificationInfo> notifications) throws InternalException {
        final int maxListSize = 50000;

        int index = 0;
        List<NotificationInfo> subList;

        while (index < notifications.size()) {
            if (index + maxListSize < notifications.size()) {
                subList = notifications.subList(index, index + maxListSize);
                index += maxListSize;
            } else {
                subList = notifications.subList(index, notifications.size());
                index += notifications.size();
            }

            if (!subList.isEmpty()) {
                String startString = SqlUtil.getCommaSeparatedList(notifications);
                String startProgressUpdateQuery = String.format(UPDATE_START_PROGRESS_QUERY, startString);
                log.debug("Success update logging: startProgressUpdateQuery");
                getJdbcTemplate(WMCPartition.nullPartition()).update(startProgressUpdateQuery);
            }
        }
    }

    public void cleanup() throws InternalException {
        log.debug("Success update logging: REMOVE_OLD_UPDATE");
        getJdbcTemplate(WMCPartition.nullPartition()).update(DELETE_OLD_NOTIFICATIONS_QUERY, outOfDateDays);
    }

    @Required
    public void setOutOfDateDays(int outOfDateDays) {
        this.outOfDateDays = outOfDateDays;
    }

    @Required
    public void setConcurrentNotifications(int concurrentNotifications) {
        this.concurrentNotifications = concurrentNotifications;
    }
}
