package ru.yandex.wmconsole.service;

import java.net.IDN;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

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

import ru.yandex.common.framework.pager.Pager;
import ru.yandex.common.util.db.LongRowMapper;
import ru.yandex.webmaster.common.host.dao.TblHostsMainDao;
import ru.yandex.wmconsole.data.NotificationTypeEnum;
import ru.yandex.wmconsole.data.WMCHistoryObjectTypeEnum;
import ru.yandex.wmconsole.data.info.BriefHostInfo;
import ru.yandex.wmconsole.data.info.DraftMessageInfo;
import ru.yandex.wmconsole.data.info.HostOwnersDraftMessageInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmconsole.service.error.WMCUserProblem;
import ru.yandex.wmtools.common.data.HistoryActionEnum;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.util.SqlUtil;

/**
 * @author avhaliullin
 */
public class HostOwnersDraftMessagesService extends AbstractDraftMessagesService<HostOwnersDraftMessageInfo> {
    private static final Logger log = LoggerFactory.getLogger(HostOwnersDraftMessagesService.class);

    private static final String FIELD_HOSTS_COUNT = "hosts_count";
    private static final String FIELD_FIRST_HOST = "host_first";
    private static final String FIELD_HOST_ID = "host_id";
    private static final String PARAM_HOST_LIST = "host_id";

    private static final String PATTERN_HOST_NAMES_LIST = "%%host-names-list%%";

    private static final String SELECT_MESSAGE_HOSTS_LIST_QUERY =
            "SELECT " +
                    "    host_id AS " + FIELD_HOST_ID + " " +
                    "FROM " +
                    "    tbl_message_hosts " +
                    "WHERE " +
                    "    message_id = ? " +
                    "ORDER BY " +
                    "    host_id " +
                    " %1$s ";

    private static final String SELECT_MESSAGE_HOSTS_LIST_COUNT_QUERY =
            "SELECT " +
                    "    count(*) " +
                    "FROM " +
                    "    tbl_message_hosts " +
                    "WHERE " +
                    "    message_id = ? ";

    private static final String DELETE_MESSAGE_HOSTS_TEMPLATE_QUERY =
            "DELETE FROM " +
                    "    tbl_message_hosts " +
                    "WHERE " +
                    "    message_id = ? " +
                    "AND " +
                    "    host_id IN (%1$s)";

    private static final String INSERT_MESSAGE_HOSTS_TEMPLATE_QUERY =
            "INSERT IGNORE INTO " +
                    "   tbl_message_hosts (message_id, host_id) " +
                    "VALUES %1$s";


    private static final String ADDITIONAL_FIELDS_SUBQUERY = ", " +
            "   (SELECT " +
            "        count(*) " +
            "    FROM " +
            "        tbl_message_hosts mh " +
            "    WHERE " +
            "        mh.message_id = dm.message_id " +
            "    ) AS " + FIELD_HOSTS_COUNT + ", " +
            "   (SELECT " +
            "        mh.host_id " +
            "    FROM " +
            "        tbl_message_hosts mh " +
            "    WHERE " +
            "        mh.message_id = dm.message_id " +
            "    ORDER BY " +
            "        mh.host_id " +
            "    LIMIT 1 " +
            "    ) AS " + FIELD_FIRST_HOST + " ";

    private HostInfoService hostInfoService;
    private TblHostsMainDao tblHostsMainDao;

    private final ParameterizedRowMapper<HostOwnersDraftMessageInfo> messageInfoMapper = new ParameterizedRowMapper<HostOwnersDraftMessageInfo>() {
        @Override
        public HostOwnersDraftMessageInfo mapRow(ResultSet rs, int i) throws SQLException {
            DraftMessageInfo draftMessageInfo = draftMessageInfoMapper.mapRow(rs, i);
            long hostsCount = rs.getLong(FIELD_HOSTS_COUNT);
            BriefHostInfo firstHost = null;
            long firstHostId = rs.getLong(FIELD_FIRST_HOST);
            if (!rs.wasNull()) {
                try {
                    firstHost = tblHostsMainDao.getBriefHostInfoByHostId(firstHostId);
                } catch (InternalException e) {
                    log.warn("Failed to find host name for " + firstHostId, e);
                }
            }
            return new HostOwnersDraftMessageInfo(draftMessageInfo, hostsCount, firstHost);
        }
    };

    @Override
    protected NotificationTypeEnum getNotificationType() {
        return NotificationTypeEnum.HOST_OWNERS;
    }

    @Override
    protected WMCHistoryObjectTypeEnum getWmcHistoryObjectType() {
        return WMCHistoryObjectTypeEnum.HOST_OWNERS_MESSAGE;
    }

    @Override
    protected ParameterizedRowMapper<HostOwnersDraftMessageInfo> getMapper() {
        return messageInfoMapper;
    }

    @Override
    protected String getAdditionalFieldsString() {
        return ADDITIONAL_FIELDS_SUBQUERY;
    }

    @Override
    protected Map<String, Object> getNotifierRequestParams(DraftMessageInfo message) throws InternalException {
        Map<String, Object> params = new LinkedHashMap<String, Object>();

        // extra parameter for HOST_OWNERS_MESSAGES with GROUPING
        if (message.getHeader().contains(PATTERN_HOST_NAMES_LIST) ||
                message.getContent().contains(PATTERN_HOST_NAMES_LIST)) {
            params.put("grouped", "true");
        }
        params.put(PARAM_HOST_LIST, getHostsListForMessage(message.getMessageId(), null));

        return params;
    }


    private void internalAddHosts(Long messageId, Collection<Long> hosts) throws InternalException {
        StringBuilder insertValues = new StringBuilder();
        boolean first = true;
        for (Long host : hosts) {
            if (!first) {
                insertValues.append(", ");
            }
            first = false;
            insertValues.append("(").append(messageId).append(", ").append(host).append(")");
        }
        String insertSql = String.format(INSERT_MESSAGE_HOSTS_TEMPLATE_QUERY, insertValues.toString());
        getJdbcTemplate(WMCPartition.nullPartition()).update(insertSql);
    }

    public void removeHosts(final long authorId, final long messageId, Long[] hostIds) throws InternalException, UserException {
        // check permissions
        checkPermissions(authorId);

        HostOwnersDraftMessageInfo message = getDraftMessage(messageId);

        // check message is not sent or deleted
        if ((message.getState() != HistoryActionEnum.ADD) && (message.getState() != HistoryActionEnum.EDIT)) {
            throw new UserException(WMCUserProblem.MESSAGE_IS_READ_ONLY, "Now you can't edit any options of this message. It's because of a state: " + message.getState());
        }

        String updateSql = String.format(DELETE_MESSAGE_HOSTS_TEMPLATE_QUERY, SqlUtil.getCommaSeparatedList(Arrays.asList(hostIds)));
        getJdbcTemplate(WMCPartition.nullPartition()).update(updateSql, messageId);
    }

    public void addHosts(final long authorId, final long messageId, List<String> hosts, List<String> incorrectHosts) throws InternalException, UserException {
        // check permissions
        checkPermissions(authorId);
        Set<Long> hostIds = new HashSet<Long>();
        for (String host : hosts) {
            Long hostId = hostInfoService.getHostIdByHostName(IDN.toASCII(host), false);
            if (hostId == null) {
                incorrectHosts.add(host);
            } else {
                hostIds.add(hostId);
            }
        }

        HostOwnersDraftMessageInfo message = getDraftMessage(messageId);

        // check message is not sent or deleted
        if ((message.getState() != HistoryActionEnum.ADD) && (message.getState() != HistoryActionEnum.EDIT)) {
            throw new UserException(WMCUserProblem.MESSAGE_IS_READ_ONLY, "Now you can't edit any options of this message. It's because of a state: " + message.getState());
        }

        if (hosts.size() > 0) {
            internalAddHosts(messageId, hostIds);
        }
    }

    public List<Long> getHostsListForMessage(long messageId, Pager pager) throws InternalException {
        return getJdbcTemplate(WMCPartition.nullPartition()).pageableSelect(SELECT_MESSAGE_HOSTS_LIST_COUNT_QUERY,
                SELECT_MESSAGE_HOSTS_LIST_QUERY, new LongRowMapper(), pager, messageId);
    }

    public long addNewMessage(final long authorId, final List<String> hosts, final List<String> incorrectHosts) throws UserException, InternalException {
        long id = addNewMessage(authorId);
        addHosts(authorId, id, hosts, incorrectHosts);
        return id;
    }

    @Required
    public void setHostInfoService(HostInfoService hostInfoService) {
        this.hostInfoService = hostInfoService;
    }

    @Required
    public void setTblHostsMainDao(TblHostsMainDao tblHostsMainDao) {
        this.tblHostsMainDao = tblHostsMainDao;
    }
}
