package ru.yandex.wmconsole.periodic;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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.scheduler.ExecutionContext;
import ru.yandex.common.util.db.StringRowMapper;
import ru.yandex.wmconsole.data.info.BriefHostInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmconsole.service.HostnameService;
import ru.yandex.wmconsole.service.dao.TblPeriodicCounterDao;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.util.scheduler.timetable.AbstractTaskExecutor;

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

    private static final String FIELD_HOST_ID = "host_id";
    private static final String FIELD_HOST_NAME = "name";

    private static final int QUERY_HOST_NUMBER = 10000;

    private static final String SELECT_HOST_NAMES_QUERY =
            "SELECT " +
                    "    host_id, name " +
                    "FROM " +
                    "    tbl_hosts " +
                    "ORDER BY " +
                    "    host_id " +
                    "LIMIT " +
                    "    ?, " + QUERY_HOST_NUMBER;

    private static final String SELECT_HOSTS_QUERY =
            "SELECT " +
                    "    name " +
                    "FROM " +
                    "    tbl_hosts " +
                    "WHERE " +
                    "    name " +
                    "IN (%s)";

    private HostnameService hostnameService;

    private TblPeriodicCounterDao tblPeriodicCounterDao;

    @SuppressWarnings("unchecked")
    private synchronized void queryUserBase() throws InternalException {
        int startHostNum = tblPeriodicCounterDao.getCurrentHostNum();
        logUserDbConnections();

        List<BriefHostInfo> hosts = getJdbcTemplate(WMCPartition.nullPartition()).query(SELECT_HOST_NAMES_QUERY,
                new ParameterizedRowMapper<BriefHostInfo>() {
                    @Override
                    public BriefHostInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
                        return new BriefHostInfo(rs.getLong(FIELD_HOST_ID), rs.getString(FIELD_HOST_NAME), null);
                    }
                }, startHostNum);

        if (hosts == null || hosts.size() == 0) {
            tblPeriodicCounterDao.updateCurrentHostNum(0);
        } else {
            tblPeriodicCounterDao.updateCurrentHostNum(startHostNum + QUERY_HOST_NUMBER);

            int dbCount = WMCPartition.getHostDbCount(getDatabaseCount());
            List<BriefHostInfo>[] hostsByDb = new List[dbCount];
            for (int i = 0; i < hostsByDb.length; i++) {
                hostsByDb[i] = new ArrayList<BriefHostInfo>();
            }

            for (BriefHostInfo host : hosts) {
                int dbIndex = WMCPartition.getDatabaseIndex(getDatabaseCount(), host.getName());
                hostsByDb[dbIndex].add(host);
            }

            for (int dbIndex = 0; dbIndex < dbCount; dbIndex++) {
                logConnections(dbIndex);
                updateHostNames(dbIndex, hostsByDb[dbIndex]);
                logConnections(dbIndex);
            }
        }
        logUserDbConnections();
    }

    private void updateHostNames(int dbIndex, List<BriefHostInfo> hosts) throws InternalException {
        log.info(getClass().toString() + ": request for update user host names. Host number: " + hosts.size());
        if (hosts.size() == 0) {
            return;
        }

        Map<String, BriefHostInfo> hostName2hostInfo = new HashMap<String, BriefHostInfo>();
        StringBuilder hostStr = new StringBuilder();
        for (BriefHostInfo host : hosts) {
            hostName2hostInfo.put(host.getName().toLowerCase(), host);
            if (hostStr.length() > 0) {
                hostStr.append(",");
            }
            hostStr.append("'").append(host.getName()).append("'");
        }
        List<String> hostDbHostNames = getJdbcTemplate(new WMCPartition(dbIndex)).query(String.format(
                SELECT_HOSTS_QUERY, hostStr.toString()), new StringRowMapper());
        log.info(getClass().toString() + ": preparing for update user host names. Updated host number: " + hostDbHostNames.size());
        for (String hostDbHostName : hostDbHostNames) {
            BriefHostInfo briefHostInfo = hostName2hostInfo.get(hostDbHostName.toLowerCase());
            if (briefHostInfo != null) {
                String hostName = briefHostInfo.getName();
                if (hostName.equalsIgnoreCase(hostDbHostName) && !hostName.equals(hostDbHostName)) {
                    log.info(getClass().toString() + ": updating user host name: " + hostDbHostName + " -> " + hostName);
                    hostnameService.changeName(briefHostInfo, hostDbHostName);
                }
            }
        }
    }

    @Override
    public String runWithRELogging(ExecutionContext context) {
        log.info(getClass().toString() + " job started");
        try {
            queryUserBase();
        } catch (RuntimeException e) {
            log.error("RuntimeException occured in thread " + Thread.currentThread().getName(), e);
        } catch (InternalException e) {
            log.error("InternalException occured in thread " + Thread.currentThread().getName(), e);
        }
        return getClass().getName() + " task executed";
    }

    @Required
    public void setHostnameService(HostnameService hostnameService) {
        this.hostnameService = hostnameService;
    }

    @Required
    public void setTblPeriodicCounterDao(TblPeriodicCounterDao tblPeriodicCounterDao) {
        this.tblPeriodicCounterDao = tblPeriodicCounterDao;
    }
}
