package ru.yandex.wmconsole.service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

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.util.collections.Cu;
import ru.yandex.common.util.concurrent.CommonThreadFactory;
import ru.yandex.wmconsole.common.service.AbstractUrlVisitorService;
import ru.yandex.wmconsole.data.UpdateStateEnum;
import ru.yandex.wmconsole.data.info.HostDbHostInfo;
import ru.yandex.wmconsole.data.info.SearchUpdateIntervalInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmconsole.service.dao.TblLogUpdaterDao;
import ru.yandex.wmconsole.service.dao.TblRobotdbInfoDao;
import ru.yandex.wmtools.common.error.InternalException;

/**
 * Created by IntelliJ IDEA.
 * User: baton
 * Date: 20.03.2007
 * Time: 10:09:24
 */
public class AddHostDataVisitorService extends AbstractUrlVisitorService<HostDbHostInfo> {
    private static final Logger log = LoggerFactory.getLogger(AddHostDataVisitorService.class);

    private static final String FIELD_HOST_ID = "host_id";
    private static final String FIELD_HOST_NAME = "host_name";
    private static final String FIELD_UPDATED_ON = "updated_on";

    private static final String SELECT_WAITING_QUERY =
            "SELECT " +
                    "   h.host_id AS " + FIELD_HOST_ID + ", " +
                    "   h.name AS " + FIELD_HOST_NAME + ", " +
                    "   rdb.updated_on AS " + FIELD_UPDATED_ON + " " +
                    "FROM " +
                    "   tbl_hosts h, " +
                    "   tbl_robotdb_info rdb " +
                    "WHERE " +
                    "   rdb.host_id = h.host_id AND " +
                    "   rdb.state = '" + UpdateStateEnum.NEW.getValue() + "' " +
                    "ORDER BY " + FIELD_UPDATED_ON + " " +
                    "LIMIT 1 " +
                    "FOR UPDATE";

    private final AtomicInteger numActive = new AtomicInteger(0);

    private TblRobotdbInfoDao tblRobotdbInfoDao;
    private DispatcherHttpService dispatcherHttpService;
    private HostMonHttpService hostMonHttpService;
    private TblLogUpdaterDao tblLogUpdaterDao;
    private volatile Date updateTime = null;

    private ExecutorService pool;

    public void init() {
        CommonThreadFactory threadFactory = new CommonThreadFactory(true, AddHostDataVisitorService.class.getSimpleName() + "-");
        pool = Executors.newFixedThreadPool(8, threadFactory);
    }

    protected int getThreadsCount() {
        return WMCPartition.getHostDbCount(getDatabaseCount());
    }

    @Override
    protected final HostDbHostInfo listFirstWaiting(int databaseIndex) throws InternalException {
        List<HostDbHostInfo> res = getJdbcTemplate(new WMCPartition(databaseIndex))
                .query(SELECT_WAITING_QUERY, hostDbHostInfoRowMapper);
        if ((res == null) || (res.size() == 0)) {
            return null;
        }

        return res.get(0);
    }

    @Override
    protected final HostDbHostInfo check(HostDbHostInfo hostInfo) {
        try {
            dispatcherHttpService.uploadHostData(hostInfo.getName(), hostInfo.getHostDbHostId(), getAndCacheUpdateTime());
        } catch (InternalException e) {
            throw new RuntimeException(e);
        }

        try {
            hostMonHttpService.hostMonUploadHostData(hostInfo.getName());
        } catch (InternalException ie) {
            log.error("Hostmonloader upload host data failed ", ie);
        }
        return hostInfo;
    }

    @Override
    protected final void updateStateToInProgress(HostDbHostInfo hostInfo) throws InternalException {
        tblRobotdbInfoDao.updateStateAndUpdatedOn(hostInfo, UpdateStateEnum.FIRST_IN_PROGRESS);
    }

    @Override
    protected final void updateStateToCurrentStateValue(HostDbHostInfo newHostInfo) {
    }

    @Override
    protected void onStateChanged(HostDbHostInfo hostDbHostInfo, HostDbHostInfo newT) throws InternalException {
    }

    @Override
    public final void fireCheck() {
        if (numActive.compareAndSet(0, getThreadsCount())) {
            int submitted = 0;
            try {
                for (int databaseIndex = 0; databaseIndex < getThreadsCount(); databaseIndex++) {
                    pool.submit(new CheckDatabaseThread(databaseIndex));
                    submitted++;
                }
            } catch (Exception e) {
                log.error("Error while submitting new task to AddHostVisitor", e);
                numActive.set(submitted);
            }
        } else {
            // Предыдущие потоки еще не завершились
            log.info("Previous threads are still running. Suspend checks");
            // Восстанавливаем работу системы, если пришли в недопустимое состояние
            int num = numActive.get();
            if (num < 0 || num > getThreadsCount()) {
                log.error("Changing numActive from " + num + " to 0");
                numActive.set(0);
            }
        }
    }

    private class CheckDatabaseThread implements Runnable {
        private int databaseIndex;

        public CheckDatabaseThread(int databaseIndex) {
            this.databaseIndex = databaseIndex;
        }

        @Override
        public void run() {
            logConnections(databaseIndex);
            try {
                // reload cache
                updateTime = null;
                getAndCacheUpdateTime();
            } catch (InternalException e) {
                log.error("Error while updating robot updateTime", e);
            }
            try {
                checkDatabase(databaseIndex);
            } finally {
                logConnections(databaseIndex);
                numActive.decrementAndGet();
            }
        }
    }

    private Date getAndCacheUpdateTime() throws InternalException {
        if (updateTime == null) {
            List<SearchUpdateIntervalInfo> intervalInfos = tblLogUpdaterDao.getLastUpdateIntervals(1);
            SearchUpdateIntervalInfo interval = Cu.firstOrNull(intervalInfos);
            updateTime = interval == null ? new Date() : interval.getStartTime().toDate();
        }
        return updateTime;
    }

    private static final ParameterizedRowMapper<HostDbHostInfo> hostDbHostInfoRowMapper =
            new ParameterizedRowMapper<HostDbHostInfo>() {
                @Override
                public HostDbHostInfo mapRow(ResultSet rs, int i) throws SQLException {
                    long hostId = rs.getLong(FIELD_HOST_ID);
                    String hostName = rs.getString(FIELD_HOST_NAME);
                    return new HostDbHostInfo(hostId, hostName);
                }
            };

    @Required
    public void setDispatcherHttpService(DispatcherHttpService dispatcherHttpService) {
        this.dispatcherHttpService = dispatcherHttpService;
    }

    @Required
    public void setHostMonHttpService(HostMonHttpService hostMonHttpService) {
        this.hostMonHttpService = hostMonHttpService;
    }

    @Required
    public void setTblRobotdbInfoDao(TblRobotdbInfoDao tblRobotdbInfoDao) {
        this.tblRobotdbInfoDao = tblRobotdbInfoDao;
    }

    @Required
    public void setTblLogUpdaterDao(TblLogUpdaterDao tblLogUpdaterDao) {
        this.tblLogUpdaterDao = tblLogUpdaterDao;
    }
}
