package ru.yandex.wmconsole.service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;

import org.springframework.jdbc.core.simple.ParameterizedRowMapper;

import ru.yandex.common.util.collections.Pair;
import ru.yandex.wmconsole.data.info.DateCountInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmtools.common.data.info.WMUserInfo;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.error.UserProblem;
import ru.yandex.wmtools.common.service.UserInfoService;
import ru.yandex.wmtools.common.util.ParameterizedMapRowMapper;
import ru.yandex.wmtools.common.util.SqlUtil;
import ru.yandex.wmtools.common.util.TimeFilter;

/**
 * Created by IntelliJ IDEA.
 * User: senin
 * Date: 20.03.2007
 * Time: 10:09:24
 */
public class WMCUserInfoService extends UserInfoService<WMUserInfo> implements IWMCUserInfoService {
    private static final String FIELD_OFFLINE_LINKS_ENABLED = "offline_links_enabled";
    private static final String FIELD_NEW_USERS_COUNT = "new_users_count";
    private static final String FIELD_DATE = "date";
    private static final String FIELD_USER_ID = "user_id";
    private static final String FIELD_REG_DATE = "reg_date";
    private static final String FIELD_IS_SUPPORT = "is_support";
    private static final String FIELD_IS_BLOCKED = "is_blocked";
    private static final String FIELD_DAY = "day";
    private static final String FIELD_TRUE_USERS_COUNT = "true_users_count";
    private static final String FIELD_NEED_FEEDBACK_CONFIRMATION = "need_feedback_confirmation";

    private static final String SELECT_LAST_VISIT_UPDATE_QUERY = "" +
            "UPDATE " +
            "    tbl_users " +
            "SET " +
            "    last_visit = ? " +
            "WHERE " +
            "    user_id = ? " +
            "AND (" +
            "        last_visit < ? " +
            "    OR " +
            "        last_visit IS NULL " +
            ")";

    private static final String INSERT_NEW_USER_INSERT_QUERY =
            "INSERT INTO " +
                    "    tbl_users (user_id, reg_date) " +
                    "VALUES (?, NOW()) ";

    private static final String SELECT_USER_INFO_QUERY =
            "SELECT " +
                    "    u.user_id AS  " + FIELD_USER_ID + ", " +
                    "    unix_timestamp(u.reg_date) AS " + FIELD_REG_DATE + ", " +
                    "    (SELECT " +
                    "         count(*) " +
                    "     FROM " +
                    "         tbl_support_users au " +
                    "     WHERE " +
                    "         au.user_id = u.user_id " +
                    "    ) AS " + FIELD_IS_SUPPORT + ", " +
                    "    (SELECT " +
                    "         count(*) " +
                    "     FROM " +
                    "         tbl_blocked_users bu " +
                    "     WHERE " +
                    "         bu.user_id = u.user_id " +
                    "    ) AS " + FIELD_IS_BLOCKED + " " +
                    "FROM " +
                    "    tbl_users u " +
                    "WHERE " +
                    "    u.user_id = ? ";


    private static final String SELECT_NEW_USERS_COUNT_QUERY =
            "SELECT " +
                    " date(reg_date) AS " + FIELD_DAY + "," +
                    " count(user_id) AS " + FIELD_NEW_USERS_COUNT + " " +
                    "FROM " +
                    " tbl_users " +
                    "WHERE " +
                    "  reg_date < current_date() " +
                    "%1$s " +  // SqlUtil.getTimeFilterPart() will be here
                    "GROUP BY " + FIELD_DAY + " " +
                    "ORDER BY " + FIELD_DAY;

    private static final String CALL_TRUE_USERS_COUNT_QUERY = "" +
            "CALL wmc_dump_true_users_graph_data(?, ?, ?, ?)";

    private static final String SELECT_TRUE_USERS_COUNT_2_QUERY = "" +
            "SELECT " +
            " day AS " + FIELD_DAY + "," +
            " true_users_count AS " + FIELD_TRUE_USERS_COUNT + " " +
            "FROM " +
            " tbl_true_users_history " +
            "WHERE " +
            "  day < current_date() " +
            "%1$s " +  // SqlUtil.getTimeFilterPart() will be here
            "GROUP BY " + FIELD_DAY + " " +
            "ORDER BY " + FIELD_DAY;

    private static final String SELECT_USERS_WITH_HOSTS_COUNT_QUERY =
            "SELECT " +
                    " date(u.reg_date) AS " + FIELD_DAY + "," +
                    " count(distinct u.user_id) AS " + FIELD_NEW_USERS_COUNT + " " +
                    "FROM " +
                    " tbl_users u " +
                    "LEFT JOIN tbl_users_hosts uh ON u.user_id = uh.user_id " +
                    "WHERE " +
                    "  NOT uh.state is NULL AND " +
                    "  u.reg_date < current_date() " +
                    "%1$s " +  // SqlUtil.getTimeFilterPart() will be here
                    "GROUP BY " + FIELD_DAY + " " +
                    "ORDER BY " + FIELD_DAY;

    private static final String SELECT_USERS_WITH_VERIFIED_HOSTS_COUNT_QUERY =
            "SELECT " +
                    " date(u.reg_date) AS " + FIELD_DAY + "," +
                    " count(distinct u.user_id) AS " + FIELD_NEW_USERS_COUNT + " " +
                    "FROM " +
                    " tbl_users u " +
                    "LEFT JOIN tbl_users_hosts uh ON u.user_id = uh.user_id " +
                    "WHERE " +
                    "  uh.state = 1 AND" +
                    "  u.reg_date < current_date() " +
                    "  u.reg_date < current_date() " +
                    "%1$s " +  // SqlUtil.getTimeFilterPart() will be here
                    "GROUP BY " + FIELD_DAY + " " +
                    "ORDER BY " + FIELD_DAY;

    private static final String SELECT_OFFLINE_LINKS_ENABLED_QUERY =
            "SELECT " +
                    "    offline_links_enabled AS " + FIELD_OFFLINE_LINKS_ENABLED + ", " +
                    "    need_feedback_confirmation AS " + FIELD_NEED_FEEDBACK_CONFIRMATION + " " +
                    "FROM " +
                    "    tbl_users u " +
                    "WHERE " +
                    "    u.user_id = ? ";

    private static final String UPDATE_USER_OPTIONS_QUERY =
            "UPDATE " +
                    "    tbl_users " +
                    "SET " +
                    "    offline_links_enabled = ? " +
                    "WHERE " +
                    "    user_id = ? ";

    private static final String UPDATE_FEEDBACK_CONFIRMATION_QUERY =
            "UPDATE " +
                    "    tbl_users " +
                    "SET " +
                    "    need_feedback_confirmation = ? " +
                    "WHERE " +
                    "    user_id = ? ";

    private final ParameterizedRowMapper<WMUserInfo> userInfoMapper = new ParameterizedRowMapper<WMUserInfo>() {
        @Override
        public WMUserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
            long userId = rs.getLong(FIELD_USER_ID);
            int isSupportInt = rs.getInt(FIELD_IS_SUPPORT);
            int isBlockedInt = rs.getInt(FIELD_IS_BLOCKED);
            return new WMUserInfo(userId, getPassportUserInfo(userId), isSupportInt > 0, isBlockedInt > 0);
        }
    };

    @Override
    public Pair<Boolean, Boolean> getUserOptions(long userId) throws InternalException, UserException {
        List<Pair<Boolean, Boolean>> options = getJdbcTemplate(WMCPartition.nullPartition()).query(
                SELECT_OFFLINE_LINKS_ENABLED_QUERY,
                new ParameterizedRowMapper<Pair<Boolean, Boolean>>() {
                    @Override
                    public Pair<Boolean, Boolean> mapRow(ResultSet resultSet, int i) throws SQLException {
                        Boolean offlineLinksEnabled = resultSet.getBoolean(FIELD_OFFLINE_LINKS_ENABLED);
                        if (resultSet.wasNull()) {
                            offlineLinksEnabled = null;
                        }
                        Boolean needFeeedbackConfirmation = resultSet.getBoolean(FIELD_NEED_FEEDBACK_CONFIRMATION);
                        if (resultSet.wasNull()) {
                            needFeeedbackConfirmation = null;
                        }
                        return new Pair<Boolean, Boolean>(offlineLinksEnabled, needFeeedbackConfirmation);
                    }
                }, userId);

        if (options.isEmpty()) {
            throw new UserException(UserProblem.NO_SUCH_USER_IN_SERVICE, UserProblem.NO_SUCH_USER_IN_SERVICE.toString() + ". UserId: " + userId);
        }

        return options.get(0);
    }

    @Override
    public List<DateCountInfo> getNewUsersCount(final TimeFilter timeFilter) throws InternalException {
        String query = String.format(SELECT_NEW_USERS_COUNT_QUERY,
                SqlUtil.getTimeFilterPart(timeFilter, FIELD_REG_DATE));
        return getJdbcTemplate(WMCPartition.nullPartition()).query(query, new ParameterizedRowMapper<DateCountInfo>() {
            @Override
            public DateCountInfo mapRow(ResultSet resultSet, int i) throws SQLException {
                return new DateCountInfo(resultSet.getDate(FIELD_DAY), resultSet.getInt(FIELD_NEW_USERS_COUNT));
            }
        });
    }

    @Override
    public List<DateCountInfo> getTrueUsersCount(final Date fromDate, final Date toDate, final int numOfDays, final int numOfVisits) throws InternalException {
        final List<DateCountInfo> result = new ArrayList<DateCountInfo>();

        ParameterizedRowMapper<DateCountInfo> rowMapper = new ParameterizedRowMapper<DateCountInfo>() {
            @Override
            public DateCountInfo mapRow(ResultSet resultSet, int i) throws SQLException {
                return new DateCountInfo(resultSet.getDate(FIELD_DATE), resultSet.getInt(FIELD_TRUE_USERS_COUNT));
            }
        };

        result.addAll(getJdbcTemplate(WMCPartition.nullPartition()).query(CALL_TRUE_USERS_COUNT_QUERY,
                rowMapper, fromDate, toDate, numOfDays, numOfVisits));

        return result;
    }

    @Override
    public List<DateCountInfo> getTrueUsersCount2(final TimeFilter timeFilter) throws InternalException {
        String query = String.format(SELECT_TRUE_USERS_COUNT_2_QUERY,
                SqlUtil.getTimeFilterPart(timeFilter, FIELD_DAY));
        return getJdbcTemplate(WMCPartition.nullPartition()).query(query, new ParameterizedRowMapper<DateCountInfo>() {
            @Override
            public DateCountInfo mapRow(ResultSet resultSet, int i) throws SQLException {
                return new DateCountInfo(resultSet.getDate(FIELD_DAY), resultSet.getInt(FIELD_TRUE_USERS_COUNT));
            }
        });
    }

    @Override
    public List<DateCountInfo> getUsersWithHostsCount(final TimeFilter timeFilter) throws InternalException {
        String query = String.format(SELECT_USERS_WITH_HOSTS_COUNT_QUERY,
                SqlUtil.getTimeFilterPart(timeFilter, FIELD_REG_DATE));
        return getJdbcTemplate(WMCPartition.nullPartition()).query(query, new ParameterizedRowMapper<DateCountInfo>() {
            @Override
            public DateCountInfo mapRow(ResultSet resultSet, int i) throws SQLException {
                return new DateCountInfo(resultSet.getDate(FIELD_DAY), resultSet.getInt(FIELD_NEW_USERS_COUNT));
            }
        });
    }

    @Override
    public List<DateCountInfo> getUsersWithVerifiedHostsCount(final TimeFilter timeFilter) throws InternalException {
        String query = String.format(SELECT_USERS_WITH_VERIFIED_HOSTS_COUNT_QUERY, SqlUtil.getTimeFilterPart(timeFilter, FIELD_REG_DATE));
        return getJdbcTemplate(WMCPartition.nullPartition()).query(query, new ParameterizedRowMapper<DateCountInfo>() {
            @Override
            public DateCountInfo mapRow(ResultSet resultSet, int i) throws SQLException {
                return new DateCountInfo(resultSet.getDate(FIELD_DAY), resultSet.getInt(FIELD_NEW_USERS_COUNT));
            }
        });
    }

    @Override
    public NavigableMap<Date, Integer> getTrueUsersInfo(final TimeFilter timeFilter) throws InternalException {
        String query = String.format(SELECT_TRUE_USERS_COUNT_2_QUERY,
                SqlUtil.getTimeFilterPart(timeFilter, FIELD_DAY));
        return getJdbcTemplate(WMCPartition.nullPartition()).queryForNavigableMap(query, new ParameterizedMapRowMapper<Date, Integer>() {
            @Override
            public Pair<Date, Integer> mapRow(ResultSet resultSet, int i) throws SQLException {
                return new Pair<Date, Integer>(resultSet.getDate(FIELD_DAY), resultSet.getInt(FIELD_TRUE_USERS_COUNT));
            }
        });
    }

    @Override
    public NavigableMap<Date, Integer> getNewUsersInfo(final TimeFilter timeFilter) throws InternalException {
        String query = String.format(SELECT_NEW_USERS_COUNT_QUERY, SqlUtil.getTimeFilterPart(timeFilter, FIELD_DAY));
        return getJdbcTemplate(WMCPartition.nullPartition()).queryForNavigableMap(query, new ParameterizedMapRowMapper<Date, Integer>() {
            Integer prevValue = null;

            @Override
            public Pair<Date, Integer> mapRow(ResultSet resultSet, int i) throws SQLException {
                Date date = resultSet.getDate(FIELD_DAY);
                Integer value = resultSet.getInt(FIELD_NEW_USERS_COUNT);
                if (prevValue != null) {
                    value += prevValue;
                }
                prevValue = value;
                return new Pair<Date, Integer>(date, value);
            }
        });
    }

    @Override
    public NavigableMap<Date, Integer> getUsersWithVerifiedHostsInfo(final TimeFilter timeFilter) throws InternalException {
        String query = String.format(SELECT_USERS_WITH_VERIFIED_HOSTS_COUNT_QUERY, SqlUtil.getTimeFilterPart(timeFilter, FIELD_DAY));
        return getJdbcTemplate(WMCPartition.nullPartition()).queryForNavigableMap(query, new ParameterizedMapRowMapper<Date, Integer>() {
            Integer prevValue = null;

            @Override
            public Pair<Date, Integer> mapRow(ResultSet resultSet, int i) throws SQLException {
                Date date = resultSet.getDate(FIELD_DAY);
                Integer value = resultSet.getInt(FIELD_NEW_USERS_COUNT);
                if (prevValue != null) {
                    value += prevValue;
                }
                prevValue = value;
                return new Pair<Date, Integer>(date, value);
            }
        });
    }

    @Override
    public NavigableMap<Date, Integer> getUsersWithHostsInfo(final TimeFilter timeFilter) throws InternalException {
        String query = String.format(SELECT_USERS_WITH_HOSTS_COUNT_QUERY, SqlUtil.getTimeFilterPart(timeFilter, FIELD_DAY));
        return getJdbcTemplate(WMCPartition.nullPartition()).queryForNavigableMap(query, new ParameterizedMapRowMapper<Date, Integer>() {
            Integer prevValue = null;

            @Override
            public Pair<Date, Integer> mapRow(ResultSet resultSet, int i) throws SQLException {
                Date date = resultSet.getDate(FIELD_DAY);
                Integer value = resultSet.getInt(FIELD_NEW_USERS_COUNT);
                if (prevValue != null) {
                    value += prevValue;
                }
                prevValue = value;
                return new Pair<Date, Integer>(date, value);
            }
        });
    }

    @Override
    @Deprecated // 2013-03-05. Удалить после внедрения настройки для каждого хоста
    public void updateOfflineLinksOption(long userId, boolean offlineLinksEnabled) throws InternalException {
        getJdbcTemplate(WMCPartition.nullPartition()).update(UPDATE_USER_OPTIONS_QUERY, offlineLinksEnabled, userId);
    }

    @Override
    public void updateFeedbackConfirmationOption(long userId, boolean needFeedbackConfirmation) throws InternalException {
        getJdbcTemplate(WMCPartition.nullPartition()).update(UPDATE_FEEDBACK_CONFIRMATION_QUERY, needFeedbackConfirmation, userId);
    }

    @Override
    public void createNewUser(long userId) throws InternalException {
        getJdbcTemplate(WMCPartition.nullPartition()).update(INSERT_NEW_USER_INSERT_QUERY, userId);
    }

    @Override
    public void updateLastVisit(Map.Entry<Long, Date> entry) throws InternalException {
        getJdbcTemplate(WMCPartition.nullPartition()).update(SELECT_LAST_VISIT_UPDATE_QUERY, entry.getValue(), entry.getKey(), entry.getValue());
    }

    @Override
    protected List<WMUserInfo> doUserInfoQuery(long userId) throws InternalException {
        return getJdbcTemplate(WMCPartition.nullPartition()).query(SELECT_USER_INFO_QUERY, userInfoMapper, userId);
    }
}
