package ru.yandex.wmconsole.service;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import ru.yandex.common.util.collections.Pair;
import ru.yandex.wmconsole.data.UserDailyCounterEnum;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.service.AbstractDbService;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

/**
 * User: azakharov
 * Date: 13.08.13
 * Time: 13:51
 */
public class UserDailyCounterService extends AbstractDbService {

    private static final Integer DEFAULT_MAX_COUNTER_VALUE = 10;

    private Map<UserDailyCounterEnum, Integer> countersMaxValues = new EnumMap<UserDailyCounterEnum, Integer>(UserDailyCounterEnum.class);

    private @Nullable Pair<Integer, DateTime> getCounter(long userId, String tableName) throws InternalException {
        final String query = "SELECT counter, last_modified FROM " + tableName + " WHERE user_id = ?";
        List<Pair<Integer, DateTime>> res =
                getJdbcTemplate(WMCPartition.nullPartition()).query(query, countMapper, userId);
        return res.isEmpty() ? null : res.iterator().next();
    }

    private void insertOrIncreaseCounter(long userId, long counter, String tableName) throws InternalException {
        final String query = "INSERT INTO " + tableName + " (user_id, counter, last_modified) VALUES (?, ?, NOW()) " +
                "ON DUPLICATE KEY UPDATE counter = counter + 1, last_modified = NOW()";
        getJdbcTemplate(WMCPartition.nullPartition()).update(query, userId, counter);
    }

    private void insertOrUpdateCounter(long userId, long counter, String tableName) throws InternalException {
        final String query = "INSERT INTO " + tableName + " (user_id, counter, last_modified) VALUES (?, ?, NOW()) " +
                "ON DUPLICATE KEY UPDATE counter = ?, last_modified = NOW()";
        getJdbcTemplate(WMCPartition.nullPartition()).update(query, userId, counter, counter);
    }

    private void updateCounter(long userId, String tableName) throws InternalException {
        final String query = "UPDATE " + tableName + " SET counter = counter + 1, last_modified = NOW() WHERE user_id = ?";
        getJdbcTemplate(WMCPartition.nullPartition()).update(query, userId);
    }

    private @NotNull Integer getMaxCounterValue(@NotNull final UserDailyCounterEnum counter) {
        Integer res = countersMaxValues.get(counter);
        if (res == null) {
            return DEFAULT_MAX_COUNTER_VALUE;
        } else {
            return res;
        }
    }

    /**
     * Increments counter for given user
     *
     * @param userId    user identifier
     * @param counter   counter type
     * @return          counter is less than counter max value
     * @throws InternalException
     */
    public boolean increment(final long userId, @NotNull final UserDailyCounterEnum counter) throws InternalException {
        final Pair<Integer, DateTime> record = getCounter(userId, counter.getTableName());
        final Integer maxCounterValue = getMaxCounterValue(counter);

        if (record == null) {
            insertOrIncreaseCounter(userId, 1l, counter.getTableName());
        } else if (record.getSecond() == null || record.getSecond().toDateMidnight().isBefore(new DateMidnight())) {
            insertOrUpdateCounter(userId, 1l, counter.getTableName());
        } else {
            if (record.getFirst() >= maxCounterValue) {
                // daily counter overcomes day limit
                return false;
            }

            updateCounter(userId, counter.getTableName());
        }

        return true;
    }

    @Required
    public void setCountersMaxValues(Map<UserDailyCounterEnum, Integer> countersMaxValues) {
        this.countersMaxValues = countersMaxValues;
    }

    private static final ParameterizedRowMapper<Pair<Integer, DateTime>> countMapper = new ParameterizedRowMapper<Pair<Integer, DateTime>>() {
        @Override
        public Pair<Integer, DateTime> mapRow(ResultSet rs, int rowNum) throws SQLException {
            DateTime lastModified = new DateTime(rs.getTimestamp("last_modified"));
            if (rs.wasNull()) {
                lastModified = null;
            }
            return new Pair<Integer, DateTime>(rs.getInt("counter"), lastModified);
        }
    };
}
