package ru.yandex.wmconsole.service;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.TransactionStatus;
import ru.yandex.common.framework.user.UserInfo;
import ru.yandex.common.framework.user.blackbox.BlackBoxUserInfo;
import ru.yandex.webmaster.common.host.HostEventService;
import ru.yandex.webmaster.common.host.dao.TblHostsHostDao;
import ru.yandex.webmaster.common.host.dao.TblHostsMainDao;
import ru.yandex.webmaster.common.host.dao.TblUsersHostsDao;
import ru.yandex.webmaster3.SafeNewWebmasterHttpService;
import ru.yandex.wmconsole.data.AddHostRedirectEnum;
import ru.yandex.wmconsole.data.FakeUser;
import ru.yandex.wmconsole.data.HostInfoStatusEnum;
import ru.yandex.wmconsole.data.LanguageEnum;
import ru.yandex.wmconsole.data.UpdateStateEnum;
import ru.yandex.wmconsole.data.VerificationStateEnum;
import ru.yandex.wmconsole.data.WMCHistoryObjectTypeEnum;
import ru.yandex.wmconsole.data.info.AddHostInfo;
import ru.yandex.wmconsole.data.info.BriefHostInfo;
import ru.yandex.wmconsole.data.info.HostDbHostInfo;
import ru.yandex.wmconsole.data.info.HostStatusInfo;
import ru.yandex.wmconsole.data.info.UserOptionsInfo;
import ru.yandex.wmconsole.data.info.UsersHostsInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmconsole.error.ClientException;
import ru.yandex.wmconsole.error.ClientProblem;
import ru.yandex.wmconsole.error.WMCExtraTagNameEnum;
import ru.yandex.wmconsole.service.dao.TblHostInfoDao;
import ru.yandex.wmconsole.service.dao.TblRobotdbInfoDao;
import ru.yandex.wmconsole.util.WwwUtil;
import ru.yandex.wmconsole.verification.VerificationTypeEnum;
import ru.yandex.wmtools.common.SupportedProtocols;
import ru.yandex.wmtools.common.data.HistoryActionEnum;
import ru.yandex.wmtools.common.error.ExtraTagInfo;
import ru.yandex.wmtools.common.error.ExtraTagNameEnum;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.InternalProblem;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.servantlet.AbstractServantlet;
import ru.yandex.wmtools.common.service.AbstractDbService;
import ru.yandex.wmtools.common.service.HistoryService;
import ru.yandex.wmtools.common.service.UserService;
import ru.yandex.wmtools.common.sita.DocumentFormatEnum;
import ru.yandex.wmtools.common.sita.RedirectConfig;
import ru.yandex.wmtools.common.sita.SitaIncompleteResponseException;
import ru.yandex.wmtools.common.sita.SitaRedirectCycleException;
import ru.yandex.wmtools.common.sita.SitaRedirectInfo;
import ru.yandex.wmtools.common.sita.SitaRedirectService;
import ru.yandex.wmtools.common.sita.SitaRequestTimeout;
import ru.yandex.wmtools.common.sita.SitaUrlFetchRequest;
import ru.yandex.wmtools.common.sita.SitaUrlFetchRequestBuilder;
import ru.yandex.wmtools.common.sita.SitaUrlFetchResponse;
import ru.yandex.wmtools.common.sita.UserAgentEnum;
import ru.yandex.wmtools.common.util.ServiceTransactionCallback;
import ru.yandex.wmtools.common.util.URLUtil;
import ru.yandex.wmtools.common.util.http.YandexHttpStatus;

import java.net.IDN;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;

/**
 * Adds the specified host to a users host list.
 * Uploads host info from robot database, if necessary. Calls addurl, if there is no info in robot database.
 *
 * @author senin
 * @author ailyin
 */
public class AddHostService extends AbstractDbService {
    private static final Logger log = LoggerFactory.getLogger(AddHostService.class);

    private UserService userService;
    private DispatcherHttpService dispatcherHttpService;
    private UsersHostsService usersHostsService;
    private AddHostDataVisitorService addHostDataVisitorService;
    private HostDbHostInfoService hostDbHostInfoService;
    private HostVisitorService hostVisitorService;
    private HistoryService historyService;
    private UserOptionsService userOptionsService;
    private HostInfoService hostInfoService;
    private SitaRedirectService sitaNoCheckRedirectService;
    private HostEventService hostEventService;
    private ConsistentMainMirrorService consistentMainMirrorService;
    private SafeNewWebmasterHttpService safeNewWebmasterHttpService;

    private TblRobotdbInfoDao tblRobotdbInfoDao;
    private TblHostsMainDao tblHostsMainDao;
    private TblHostsHostDao tblHostsHostDao;
    private TblUsersHostsDao tblUsersHostsDao;
    private TblHostInfoDao tblHostInfoDao;

    private int maxRedirects;
    private int socketTimeoutMillis;

    private int maxUserHostsCount;
    private long maxAddHostWithUpload;
    private long maxNotVerifiedHostsCount;
    private double minVerifiedRate;
    private int maxMirrorsCount;

    public long addHost(long userId, @NotNull FakeUser fakeUser, @NotNull URL url, String userIp, String frontendIp,
                        String yandexUid, String yaDomain)
            throws ClientException, InternalException {
        final AddHostInfo info =
                addHost(userId, fakeUser.getId(), url, userIp, frontendIp, yandexUid, false, yaDomain, "", true, false);
        return info.getHostId();
    }

    /**
     * Checks fraction of verified hosts to not verified hosts for user
     */
    public void checkVerifiedRate(long userId) throws InternalException, ClientException {
        int total = tblUsersHostsDao.countHostsAddedToUser(userId);
        int notVerified = tblUsersHostsDao.countUnverifiedHostsAddedToUser(userId);
        if (total == 0) {
            return;
        }
        int verified = total - notVerified;
        double verifiedRate = ((double) verified) / ((double) total);
        if (verifiedRate < minVerifiedRate && notVerified >= maxNotVerifiedHostsCount) {
            throw new ClientException(
                    ClientProblem.USER_EXCEEDED_NOT_VERIFIED_HOSTS_LIMIT,
                    "User exceeded allowed number of not verified hosts");
        }
    }

    /**
     * Adds the given host to a database and to users host list.
     *
     * @param userId         User, for which to add a host.
     * @param realUserId     User, really performing this operation (for example, support user in simulation mode).
     * @param url            host to be added
     * @param userIp         user ip
     * @param tryToVerify    true, if autoverification is to be used
     * @param lang           web interface language at the time of adding host
     * @param forceAddMirror force service to add not main mirror
     * @return Returns host id of added host in DB and redirect warning flag.
     * @throws ClientException   thrown if user has done something wrong
     * @throws InternalException thrown if we're experiencing internal problems
     */
    @Nullable
    public AddHostInfo addHostAllowMirrors(final long userId,
                                           final long realUserId,
                                           @NotNull URL url,
                                           final String userIp,
                                           final String frontendIp,
                                           final String yandexUid,
                                           final boolean tryToVerify,
                                           final String yaDomain,
                                           final String lang,
                                           final boolean forceAddMirror,
                                           final boolean fromWebmasterGui) throws ClientException, InternalException {
        final UserDbAddHostInfo[] hostUserDbInfo = new UserDbAddHostInfo[1];
        final UserDbAddHostInfo[] mainMirrorUserDbInfo = new UserDbAddHostInfo[1];

        // Check number of not verified sites
        checkVerifiedRate(userId);

        final String hostname = assertPortCorrect(url);

        // Check main mirror
        final AddHostMainMirrorInfo addHostMainMirrorInfo = checkMainMirrorAndAlreadyAdded(hostname, forceAddMirror, userId);

        try {
            url = SupportedProtocols.getURL(hostname);
        } catch (@NotNull MalformedURLException | URISyntaxException | SupportedProtocols.UnsupportedProtocolException e) {
            throw new InternalException(InternalProblem.INTERNAL_PROBLEM, "exception while parsing hostname", e);
        }

        // check for site availability and redirects
        checkHttpCode(url, fromWebmasterGui);

        /* first step: add to host db */
        addHostToHostDb(hostname);

        if (forceAddMirror && addHostMainMirrorInfo.getMainMirror() != null) {
            addHostToHostDb(addHostMainMirrorInfo.getMainMirror());
        }

        final long hostsCount[] = new long[1];
        BriefHostInfo hostInfo;
        try {
            /* second step: add to user db */
            hostInfo = (BriefHostInfo) getServiceTransactionTemplate(new WMCPartition(null, null, userId)).executeInService(
                    new ServiceTransactionCallback() {
                        @Override
                        public BriefHostInfo doInTransaction(TransactionStatus transactionStatus) throws ClientException, InternalException {
                            int count = tblUsersHostsDao.countHostsAddedToUser(userId);
                            if (count >= maxUserHostsCount) {
                                throw new ClientException(ClientProblem.USERS_HOSTS_COUNT_LIMIT_EXCEEDED,
                                        "Users host limit exceeded (" + maxUserHostsCount + ")");
                            }

                            Long mainMirrorId = null;
                            if (forceAddMirror && addHostMainMirrorInfo.getMainMirror() != null) {

                                mainMirrorUserDbInfo[0] = addHostToUserDb(
                                        realUserId, userId, addHostMainMirrorInfo.getMainMirror(), null, tryToVerify, false);

                                mainMirrorId = mainMirrorUserDbInfo[0].getHostInfo().getId();
                                if (usersHostsService.getMirrorsCount(userId, mainMirrorId) >= maxMirrorsCount) {
                                    throw new ClientException(ClientProblem.TOO_MANY_MIRRORS_FOR_HOST, "too many mirrors",
                                            new ExtraTagInfo(WMCExtraTagNameEnum.ORIGINAL_HOST_NAME, hostname),
                                            new ExtraTagInfo(WMCExtraTagNameEnum.MAIN_MIRROR, mainMirrorUserDbInfo[0].getHostInfo().getName()),
                                            new ExtraTagInfo(WMCExtraTagNameEnum.MAIN_MIRROR_HOST_ID, Long.toString(mainMirrorId)));
                                }
                            }
                            hostUserDbInfo[0] = addHostToUserDb(realUserId, userId, hostname, mainMirrorId, tryToVerify, true);

                            hostsCount[0] = usersHostsService.getHostsCounter(userId);
                            usersHostsService.increaseHostsCounter(userId, hostsCount[0], 2);

                            saveEmailLang(userId, lang);

                            return hostUserDbInfo[0].getHostInfo();
                        }
                    });
        } catch (UserException e) {
            throw (ClientException) e;
        }


        /* third step: update host state */
        try {
            forceUpdateHostInfo(hostname, true, hostsCount[0], userId);
            if (!StringUtils.isEmpty(addHostMainMirrorInfo.getMainMirror())) {
                forceUpdateHostInfo(addHostMainMirrorInfo.getMainMirror(), true, hostsCount[0], userId);
            }
        } catch (InternalException e) {
            /*
             * At the moment of execution of these queries host was already successfully added to the host and user
             * databases, so we don't report an error to the user. But we still log this error because it will cause
             * robot database info for the specified host to be NOT up-to-date till the next update.
             */
            log.error("addhost: failed to update tbl_robotdb_info state for host " + hostname);
        }

        if (tryToVerify) {
            if (hostUserDbInfo[0] != null && hostUserDbInfo[0].getUsersHostsInfo() != null) {    // trying to verify host automatically
                hostVisitorService.checkAutoVerification(hostUserDbInfo[0].getUsersHostsInfo());
            }
            if (mainMirrorUserDbInfo[0] != null && mainMirrorUserDbInfo[0].getUsersHostsInfo() != null) {
                hostVisitorService.checkAutoVerification(mainMirrorUserDbInfo[0].getUsersHostsInfo());
            }
        }

        try {
            hostEventService.hostIsAdded(hostInfo, userId, yaDomain, userIp, frontendIp, yandexUid);
            if (mainMirrorUserDbInfo[0] != null && mainMirrorUserDbInfo[0].getHostInfo() != null) {
                hostEventService.hostIsAdded(mainMirrorUserDbInfo[0].getHostInfo(), userId, yaDomain, userIp, frontendIp, yandexUid);
            }
        } catch (InternalException e) {
            log.error("Unable to add host event", e);
        }

        try {
            safeNewWebmasterHttpService.addHost(userId, hostInfo);
            if (mainMirrorUserDbInfo[0] != null && mainMirrorUserDbInfo[0].getHostInfo() != null) {
                safeNewWebmasterHttpService.addHost(userId, mainMirrorUserDbInfo[0].getHostInfo());
            }
        } catch (InternalException e) {
            log.error("Unable to synchronize host with new webmaster", e);
        }

        return new AddHostInfo(hostInfo.getId(), AddHostRedirectEnum.NO_REDIRECT, null, hostInfo.getMainMirrorId());
    }

    @Nullable
    private UserDbAddHostInfo addHostToUserDb(
            final long realUserId,
            final long userId,
            final String hostname,
            final @Nullable Long mainMirrorHostId,
            final boolean tryToVerify,
            final boolean throwExceptionOnAlreadyAdded) throws ClientException, InternalException {
        // trying to retrieve host id from a user database
        BriefHostInfo briefHostInfo = tblHostsMainDao.getHostIdByHostname(hostname);
        if (briefHostInfo == null) {                   // no info in user database
            briefHostInfo = tblHostsMainDao.addHostInfo(hostname, mainMirrorHostId);
        } else {
            final UsersHostsInfo usersHostsInfo = usersHostsService.getUsersHostsInfo(userId, briefHostInfo.getId());
            if (usersHostsInfo != null) {
                if (throwExceptionOnAlreadyAdded) {
                    throw new ClientException(ClientProblem.HOST_ALREADY_ADDED,
                            "Host has been already added to the users host list",
                            new ExtraTagInfo(WMCExtraTagNameEnum.MAIN_MIRROR, hostname),
                            new ExtraTagInfo(WMCExtraTagNameEnum.MAIN_MIRROR_HOST_ID, Long.toString(briefHostInfo.getId())));
                } else {
                    return new UserDbAddHostInfo(briefHostInfo, usersHostsInfo);
                }
            }
        }

        long uin = tblUsersHostsDao.addHostToUser(userId, briefHostInfo);

        historyService.addEvents(realUserId, userId, HistoryActionEnum.ADD, WMCHistoryObjectTypeEnum.HOST, briefHostInfo.getId());

        UsersHostsInfo usersHostsInfo = null;
        if (tryToVerify && (userId == realUserId)) {     // can we try auto-verify host?
            UserInfo userInfo = userService.getUserInfo(userId);
            if (userInfo != null) {
                usersHostsInfo = UsersHostsInfo.createUsersHostsInfoForJustAddedHostAutoVerification(uin, userId,
                        briefHostInfo.getId(), hostname, userInfo);
            }
        }

        return new UserDbAddHostInfo(briefHostInfo, usersHostsInfo);
    }


    @Nullable
    private UserDbAddHostInfo addHostToUserDbFromNewWmc(final long userId, final String hostname,
                                                        final @Nullable Long mainMirrorHostId,
                                                        VerificationStateEnum state,
                                                        VerificationTypeEnum type,
                                                        long uin) throws ClientException, InternalException {
        // trying to retrieve host id from a user database
        BriefHostInfo briefHostInfo = tblHostsMainDao.getHostIdByHostname(hostname);
        if (briefHostInfo == null) {                   // no info in user database
            briefHostInfo = tblHostsMainDao.addHostInfo(hostname, mainMirrorHostId);
        }

        java.sql.Date now = new java.sql.Date(System.currentTimeMillis());
        tblUsersHostsDao.addUserHostFromNewWmc(userId, briefHostInfo.getId(), state, type, uin, now);

        UsersHostsInfo usersHostsInfo = new UsersHostsInfo(state, uin, type, now, null, userId,
                briefHostInfo.getId(), hostname, new BlackBoxUserInfo(userId), null);
        return new UserDbAddHostInfo(briefHostInfo, usersHostsInfo);
    }

    public HostDbAddHostInfo addHostToHostDb(final String hostname) throws InternalException, ClientException {
        try {
            return getServiceTransactionTemplate(new WMCPartition(null, hostname, null)).<HostDbAddHostInfo>executeInService(
                    new ServiceTransactionCallback() {
                        @NotNull
                        @Override
                        public Object doInTransaction(TransactionStatus transactionStatus) throws ClientException, InternalException {
                            // trying to retrieve host id from a host database
                            HostDbHostInfo hostDbHostInfo = tblHostsHostDao.getHostDbHostIdByHostname(hostname);
                            if (hostDbHostInfo == null) {     // no info in host database

                                hostDbHostInfo = tblHostsHostDao.addHostInfoToHostDb(hostname);
                                if (hostDbHostInfo == null) {
                                    hostDbHostInfo = tblHostsHostDao.getHostDbHostIdByHostname(hostname);
                                    if (hostDbHostInfo == null) {
                                        throw new InternalException(InternalProblem.INTERNAL_PROBLEM, "Host " + hostname + " missing in host db and can't be added");
                                    }
                                }
                            }

                            // Устанавливаем статус host-info-status = WAITING
                            boolean isIndexed = hostInfoService.isIndexed(hostDbHostInfo);
                            if (!isIndexed) {
                                saveHostInfoStatus(hostDbHostInfo, HostInfoStatusEnum.WAITING);
                            }
                            long urlsCount = 0;
                            return new HostDbAddHostInfo(urlsCount);
                        }
                    });
        } catch (UserException e) {
            throw (ClientException) e;
        }
    }

    @Nullable
    public AddHostInfo addHostFromNewWebmaster(final long userId, @NotNull URL url, final VerificationTypeEnum type,
                                               final VerificationStateEnum state, final long uin) throws ClientException, InternalException {
        final UserDbAddHostInfo[] hostUserDbInfo = new UserDbAddHostInfo[1];

        // Check number of not verified sites
        final String hostname = assertPortCorrect(url);

        // Check main mirror
        final AddHostMainMirrorInfo addHostMainMirrorInfo = checkMainMirror(hostname, true, userId);

        try {
            url = SupportedProtocols.getURL(hostname);
        } catch (@NotNull MalformedURLException | URISyntaxException | SupportedProtocols.UnsupportedProtocolException e) {
            throw new InternalException(InternalProblem.INTERNAL_PROBLEM, "exception while parsing hostname", e);
        }

        /* first step: add to host db */
//        addHostToHostDb(hostname);
//
//        if (addHostMainMirrorInfo.getMainMirror() != null) {
//            addHostToHostDb(addHostMainMirrorInfo.getMainMirror());
//        }

        final long hostsCount[] = new long[1];
        BriefHostInfo hostInfo;
        try {
            /* second step: add to user db */
            hostInfo = (BriefHostInfo) getServiceTransactionTemplate(new WMCPartition(null, null, userId)).executeInService(
                    new ServiceTransactionCallback() {
                        @Override
                        public BriefHostInfo doInTransaction(TransactionStatus transactionStatus) throws ClientException, InternalException {
//                            int count = tblUsersHostsDao.countHostsAddedToUser(userId);
//                            if (count >= maxUserHostsCount) {
//                                throw new ClientException(ClientProblem.USERS_HOSTS_COUNT_LIMIT_EXCEEDED,
//                                        "Users host limit exceeded (" + maxUserHostsCount + ")");
//                            }
                            Long mainMirrorId = null;
                            if (addHostMainMirrorInfo.getMainMirror() != null) {
                                BriefHostInfo mirrorInfo = tblHostsMainDao.getHostIdByHostname(addHostMainMirrorInfo.getMainMirror());
                                if (mirrorInfo != null) {
                                    mainMirrorId = mirrorInfo.getId();
                                }
                            }

                            hostUserDbInfo[0] = addHostToUserDbFromNewWmc(userId, hostname, mainMirrorId, state, type, uin);

                            hostsCount[0] = usersHostsService.getHostsCounter(userId);
                            usersHostsService.increaseHostsCounter(userId, hostsCount[0], 2);

                            return hostUserDbInfo[0].getHostInfo();
                        }
                    });
        } catch (UserException e) {
            throw (ClientException) e;
        }


        /* third step: update host state */
//        try {
//            forceUpdateHostInfo(hostname, true, hostsCount[0], userId);
//            if (!StringUtils.isEmpty(addHostMainMirrorInfo.getMainMirror())) {
//                forceUpdateHostInfo(addHostMainMirrorInfo.getMainMirror(), true, hostsCount[0], userId);
//            }
//        } catch (InternalException e) {
            /*
             * At the moment of execution of these queries host was already successfully added to the host and user
             * databases, so we don't report an error to the user. But we still log this error because it will cause
             * robot database info for the specified host to be NOT up-to-date till the next update.
             */
//            log.error("addhost: failed to update tbl_robotdb_info state for host " + hostname);
//        }
        return new AddHostInfo(hostInfo.getId(), AddHostRedirectEnum.NO_REDIRECT, null, hostInfo.getMainMirrorId());
    }

    /**
     * Adds the given host to a database and to users host list.
     *
     * @param userId      User, for which to add a host.
     * @param realUserId  User, really performing this operation (for example, support user in simulation mode).
     * @param url         host to be added
     * @param userIp      user ip
     * @param tryToVerify true, if autoverification is to be used
     * @param lang
     * @return Returns host id of added host in DB and redirect warning flag.
     * @throws ClientException   thrown if user has done something wrong
     * @throws InternalException thrown if we're experiencing internal problems
     */
    @NotNull
    public AddHostInfo addHost(final long userId,
                               final long realUserId,
                               @NotNull URL url,
                               final String userIp,
                               final String frontendIp,
                               final String yandexUid,
                               final boolean tryToVerify,
                               final String yaDomain,
                               final String lang,
                               final boolean forceRedirects,
                               final boolean fromWebmasterGui) throws ClientException, InternalException {
        final UsersHostsInfo[] usersHostsInfo = new UsersHostsInfo[1];

        // Check number of not verified sites
        checkVerifiedRate(userId);

        final String hostname = assertPortCorrect(url);

        // Check main mirror
        checkMainMirror(hostname, false, userId, fromWebmasterGui, false);

        try {
            url = SupportedProtocols.getURL(hostname);
        } catch (MalformedURLException e) {
            //impossible
            throw new RuntimeException("Impossible", e);
        } catch (URISyntaxException e) {
            //impossible
            throw new RuntimeException("Impossible", e);
        } catch (SupportedProtocols.UnsupportedProtocolException e) {
            //impossible
            throw new RuntimeException("Impossible", e);
        }

        // check for site availability and redirects
        SitaRedirectInfo sitaRedirectInfo = checkHttpCode(url, fromWebmasterGui);
        RedirectInfo redirectInfo = checkRedirects(url, sitaRedirectInfo, forceRedirects);

        /* first step: add to host db */
        addHostToHostDb(hostname);

        final long hostsCount[] = new long[1];
        BriefHostInfo hostInfo = addHostToUserDb(userId, realUserId, tryToVerify, lang, usersHostsInfo, hostname, hostsCount);


        /* third step: update host state */
        try {
            forceUpdateHostInfo(hostname, true, hostsCount[0], userId);
        } catch (InternalException e) {
            /*
             * At the moment of execution of these queries host was already successfully added to the host and user
             * databases, so we don't report an error to the user. But we still log this error because it will cause
             * robot database info for the specified host to be NOT up-to-date till the next update.
             */
            log.error("addhost: failed to update tbl_robotdb_info state for host " + hostname);
        }

        if (tryToVerify) {
            if (usersHostsInfo[0] != null) {    // trying to verify host automatically
                hostVisitorService.checkAutoVerification(usersHostsInfo[0]);
            }
        }

        try {
            hostEventService.hostIsAdded(hostInfo, userId, yaDomain, userIp, frontendIp, yandexUid);
        } catch (InternalException e) {
            log.error("Unable to add host event", e);
        }

        try {
            safeNewWebmasterHttpService.addHost(userId, hostInfo);
        } catch (InternalException e) {
            log.error("Unable to synchronize host with new webmaster", e);
        }

        return new AddHostInfo(hostInfo.getId(), redirectInfo.getRedirectState(), redirectInfo.getTaget(), hostInfo.getMainMirrorId());
    }

    private BriefHostInfo addHostToUserDb(final long userId, final long realUserId, final boolean tryToVerify, final String lang, final UsersHostsInfo[] usersHostsInfo, final String hostname, final long[] hostsCount) throws InternalException, ClientException {
        BriefHostInfo hostInfo;
        try {
            /* second step: add to user db */
            hostInfo = (BriefHostInfo) getServiceTransactionTemplate(new WMCPartition(null, null, userId)).executeInService(
                    new ServiceTransactionCallback() {
                        @Override
                        public BriefHostInfo doInTransaction(TransactionStatus transactionStatus) throws ClientException, InternalException {
                            int count = tblUsersHostsDao.countHostsAddedToUser(userId);
                            if (count >= maxUserHostsCount) {
                                throw new ClientException(ClientProblem.USERS_HOSTS_COUNT_LIMIT_EXCEEDED,
                                        "Users host limit exceeded (" + maxUserHostsCount + ")");
                            }

                            UserDbAddHostInfo userDbAddHostInfo = addHostToUserDb(realUserId, userId, hostname, null, tryToVerify, true);
                            usersHostsInfo[0] = userDbAddHostInfo.getUsersHostsInfo();

                            hostsCount[0] = usersHostsService.getHostsCounter(userId);
                            usersHostsService.increaseHostsCounter(userId, hostsCount[0], 1);

                            saveEmailLang(userId, lang);

                            return userDbAddHostInfo.getHostInfo();
                        }
                    });
        } catch (UserException e) {
            throw (ClientException) e;
        }
        return hostInfo;
    }

    public SitaRedirectInfo checkHttpCode(URL url, boolean fromWebmasterGui)
            throws InternalException, ClientException {
        SitaUrlFetchRequest urlFetchRequest = new SitaUrlFetchRequestBuilder(url)
                .setUserAgent(UserAgentEnum.ROBOT)
                .setCheckAllowedInRobotsTxt(false)
                .setDocumentFormat(DocumentFormatEnum.DF_HTTP_RESPONSE)
                .setRequestTimeout(SitaRequestTimeout._15_SECONDS)
                .createSitaUrlFetchRequest();
        urlFetchRequest.setSitaSocketTimeoutMillis(socketTimeoutMillis);

        SitaUrlFetchResponse urlFetchResponse;
        try {
            urlFetchResponse = sitaNoCheckRedirectService.followRedirects(urlFetchRequest, new RedirectConfig(maxRedirects));
        } catch (SitaRedirectCycleException e) {
            ClientProblem problem = fromWebmasterGui ? ClientProblem.CYCLIC_REDIRECT : ClientProblem.HOST_NOT_AVAILABLE;
            throw new ClientException(problem, "Cyclic redirect to " + e.getRedirectUri());
        } catch (SitaIncompleteResponseException e) {
            throw new ClientException(ClientProblem.HOST_NOT_AVAILABLE, "Host is not available or request timeout exceeded");
        } catch (UserException e) {
            throw new InternalException(InternalProblem.PROCESSING_ERROR, "Unexpected exception", e);
        }
        if (urlFetchResponse.getSitaHttpStatus() == YandexHttpStatus.UNKNOWN) {
            throw new ClientException(ClientProblem.HOST_NOT_AVAILABLE, "Unable to download page");
        }
        if (urlFetchResponse.getSitaHttpStatus() != YandexHttpStatus.HTTP_200_OK) {
            if (fromWebmasterGui && urlFetchResponse.getSitaHttpStatusCode() < 600) {
                throw new ClientException(ClientProblem.URL_RETURNED_WRONG_CODE,
                        "Url " + url + " returned wrong code: " + urlFetchResponse.getSitaHttpStatusCode(),
                        new ExtraTagInfo(WMCExtraTagNameEnum.HTTP_CODE,
                                String.valueOf(urlFetchResponse.getSitaHttpStatusCode())));
            } else {
                throw new ClientException(ClientProblem.HOST_NOT_AVAILABLE,
                        "Host " + url + " is not responding");
            }
        }
        return urlFetchResponse.getSitaRedirectInfo();
    }

    @Nullable
    public RedirectInfo checkRedirects(URL url, @NotNull SitaRedirectInfo sitaRedirectInfo, boolean forceRedirects)
            throws ClientException, InternalException {

        final String sourceHostName = AbstractServantlet.getHostName(url);
        final String sourceDisplayHostName = processIDN(sourceHostName);

        if (!sitaRedirectInfo.hasRedirects()) {
            return new RedirectInfo(AddHostRedirectEnum.NO_REDIRECT, null);
        }

        List<URI> redirects = sitaRedirectInfo.getRedirects();
        final String target = redirects.get(redirects.size() - 1).toString();
        URL targetURL;
        final String targetHostName;
        final String targetDisplayName;
        try {
            targetURL = AbstractServantlet.prepareUrl(target, true);
            targetHostName = AbstractServantlet.getHostName(targetURL);
            targetDisplayName = processIDN(targetHostName);
        } catch (UserException e) {
            log.warn("redirect to invalid url " + target, e);
            // 3в) целью редиректа является невалидный URL
            return new RedirectInfo(AddHostRedirectEnum.REDIRECT_TO_NOT_WWW_MIRROR, target);
        }
        if (WwwUtil.equalsIgnoreWww(sourceHostName, targetHostName)) {
            final String mainMirror = consistentMainMirrorService.getMainMirror(targetHostName);
            if (!mainMirror.equalsIgnoreCase(targetHostName)) {
                if (mainMirror.equalsIgnoreCase(sourceHostName)) {
                    // 1) редирект на неглавное зеркало
                    // показываем предупреждение
                    return new RedirectInfo(AddHostRedirectEnum.REDIRECT_TO_WWW_MIRROR, targetDisplayName);
                } else {
                    // 3 б) редирект на неглавное зеркало другого сайта
                    return new RedirectInfo(AddHostRedirectEnum.REDIRECT_TO_NOT_WWW_MIRROR, targetDisplayName);
                }
            } else if (sourceHostName.equalsIgnoreCase(mainMirror)) {
                // target == mainmirror == url
                // редирект на самого себя
                return new RedirectInfo(AddHostRedirectEnum.NO_REDIRECT, null);
            } else {
                // 2) несклеенные сайты, так как исходный url является главным зеркалом
                // и target тоже является главным зеркалом
                // Предлагаем выбор:
                // Вы можете добавить сайт (цель перенаправления) или всё же
                // (добавить исходнеый сайт) и убрать перенаправление.»

                if (forceRedirects) {
                    return new RedirectInfo(AddHostRedirectEnum.NO_REDIRECT, null);
                }

                throw new ClientException(ClientProblem.URL_REDIRECTED, "site redirects to another site",
                        new ExtraTagInfo(WMCExtraTagNameEnum.REDIRECT_SOURCE, sourceDisplayHostName),
                        new ExtraTagInfo(WMCExtraTagNameEnum.REDIRECT_TARGET, targetDisplayName));
            }
        } else {
            // 3 а) сайт перенаправляет не на WWW!WWW
            return new RedirectInfo(AddHostRedirectEnum.REDIRECT_TO_NOT_WWW_MIRROR, targetDisplayName);
        }
    }

    private String assertPortCorrect(@NotNull final URL url) throws ClientException, InternalException {
        if (!isPortNumberCorrect(url)) {
            throw new ClientException(ClientProblem.ILLEGAL_PORT_NUMBER, "Illegal port number: " + url.getPort());
        }

        return SupportedProtocols.getCanonicalHostname(url);
    }

    public String checkMainMirror(@NotNull final String hostname, final boolean forceAddMirror, Long userId, boolean fromWebmasterGui, boolean useNewAlgorithm)
            throws ClientException, InternalException {

        String mainMirror = consistentMainMirrorService.getMainMirror(hostname);
        if (!forceAddMirror && !hostname.equalsIgnoreCase(mainMirror)) {
            final BriefHostInfo mainMirrorHost = tblHostsMainDao.getHostIdByHostname(mainMirror);

            if (useNewAlgorithm) {

                final BriefHostInfo hostInfo = tblHostsMainDao.getHostIdByHostname(hostname);
                if (hostInfo != null && usersHostsService.getUsersHostsInfo(userId, hostInfo.getId()) != null) {
                    // неглавное зеркало уже добавлено, тогда не показываем сообщение про зеркала
                    throw new ClientException(
                            ClientProblem.HOST_ALREADY_ADDED,
                            "Host has been already added to the users host list",
                            new ExtraTagInfo(WMCExtraTagNameEnum.ORIGINAL_HOST_NAME, hostname),
                            new ExtraTagInfo(WMCExtraTagNameEnum.ORIGINAL_HOST_ID, Long.toString(hostInfo.getId())));
                }
            }

            if (mainMirrorHost != null && usersHostsService.getUsersHostsInfo(userId, mainMirrorHost.getId()) != null) {
                // TODO: azakharov use HOST_IS_MIRROR_ALREADY_ADDED after api release
                ClientProblem problem = fromWebmasterGui ? ClientProblem.HOST_IS_MIRROR_ALREADY_ADDED : ClientProblem.HOST_ALREADY_ADDED;

                // Если добавляемый сайт является неглавным зеркалом и
                // главное зеркало уже добавлено пользователем, то
                // пользователя перенаправляют на страницу с информацией о сайте - главном зеркале
                throw new ClientException(problem,
                        "Host has been already added to the users host list",
                        new ExtraTagInfo(WMCExtraTagNameEnum.MAIN_MIRROR, mainMirror),
                        new ExtraTagInfo(WMCExtraTagNameEnum.MAIN_MIRROR_HOST_ID, Long.toString(mainMirrorHost.getId())),
                        new ExtraTagInfo(WMCExtraTagNameEnum.ORIGINAL_HOST_NAME, hostname));
            } else {
                // Если главного зеркала нет у пользователя, то пользователю сообщают что нужно добавлять
                // главное зеркало
                throw new ClientException(ClientProblem.HOST_IS_MIRROR, "Host is a mirror of " + mainMirror,
                        new ExtraTagInfo(ExtraTagNameEnum.ALT, processIDN(mainMirror)),
                        new ExtraTagInfo(WMCExtraTagNameEnum.ORIGINAL_HOST_NAME, hostname));
            }
        }
        return mainMirror;
    }

    /**
     * Complex logic for determining main mirror from database and from mirrors.trie
     *
     * If host is in database use main mirror info from database.
     * If host is not in database then use main mirror from mirrors.trie.
     * If main mirror of host determined by mirror.trie is in database and is not main mirror in database, then
     * add host as separate one (like it is main mirror in database).
     *
     * See also:
     * https://wiki.yandex-team.ru/users/azakharov/notmainmirrors#problemarazlichijadannyxozerkalaxvdispetchereivbazedannyxvovremjaobnovlenijabazy
     *
     * @param hostName       original host name from user in robot format
     * @param forceAddMirror allows to add not main mirror
     * @param userId         user identifier from passport
     * @return aggregated main mirror info
     * @throws InternalException
     */
    private AddHostMainMirrorInfo checkMainMirrorAndAlreadyAdded(final String hostName, final boolean forceAddMirror, Long userId) throws InternalException, ClientException {

        // (1) Is host in database
        final BriefHostInfo hostInfo = tblHostsMainDao.getHostIdByHostname(hostName);
        if (hostInfo != null) {

            checkAlreadyAdded(hostInfo, userId);

            if (hostInfo.getMainMirrorId() != null) {
                final BriefHostInfo mainMirrorInfo = tblHostsMainDao.getBriefHostInfoByHostId(hostInfo.getMainMirrorId());

                if (!forceAddMirror) {
                    checkMainMirrorAlreadyAdded(mainMirrorInfo, hostInfo.getName(), mainMirrorInfo.getName(), userId);
                }

                return new AddHostMainMirrorInfo(hostName, hostInfo, mainMirrorInfo.getName(), mainMirrorInfo);
            } else {
                return new AddHostMainMirrorInfo(hostName, hostInfo, null, null);
            }
        }

        // (2) Is host main mirror by mirrors.trie
        String mainMirror = dispatcherHttpService.getMainMirror(hostName, "webmaster-viewer::addHost");
        if (mainMirror.equalsIgnoreCase(hostName)) {
            return new AddHostMainMirrorInfo(hostName, null, null, null);
        }

        // (3) Is main mirror in webmaster database
        BriefHostInfo mainMirrorInfo = tblHostsMainDao.getHostIdByHostname(mainMirror);
        if (mainMirrorInfo == null) {
            return new AddHostMainMirrorInfo(hostName, null, mainMirror, null);
        }

        // (4) Is main mirror by mirrors.trie also main mirror in webmaster database
        if (mainMirrorInfo.getMainMirrorId() == null) {

            if (!forceAddMirror) {
                checkMainMirrorAlreadyAdded(mainMirrorInfo, hostName, mainMirrorInfo.getName(), userId);
            }

            return new AddHostMainMirrorInfo(hostName, null, mainMirror, null);
        }

        // (5) Add host as separate one
        return new AddHostMainMirrorInfo(hostName, null, null, null);
    }

    private AddHostMainMirrorInfo checkMainMirror(final String hostName, final boolean forceAddMirror, Long userId) throws InternalException, ClientException {

        // (1) Is host in database
        final BriefHostInfo hostInfo = tblHostsMainDao.getHostIdByHostname(hostName);
        if (hostInfo != null) {

            if (hostInfo.getMainMirrorId() != null) {
                final BriefHostInfo mainMirrorInfo = tblHostsMainDao.getBriefHostInfoByHostId(hostInfo.getMainMirrorId());

                if (!forceAddMirror) {
                    checkMainMirrorAlreadyAdded(mainMirrorInfo, hostInfo.getName(), mainMirrorInfo.getName(), userId);
                }

                return new AddHostMainMirrorInfo(hostName, hostInfo, mainMirrorInfo.getName(), mainMirrorInfo);
            } else {
                return new AddHostMainMirrorInfo(hostName, hostInfo, null, null);
            }
        }

        // (2) Is host main mirror by mirrors.trie
        String mainMirror = hostName;//dispatcherHttpService.getMainMirror(hostName, "webmaster-viewer::addHost");
        if (mainMirror.equalsIgnoreCase(hostName)) {
            return new AddHostMainMirrorInfo(hostName, null, null, null);
        }

        // (3) Is main mirror in webmaster database
        BriefHostInfo mainMirrorInfo = tblHostsMainDao.getHostIdByHostname(mainMirror);
        if (mainMirrorInfo == null) {
            return new AddHostMainMirrorInfo(hostName, null, mainMirror, null);
        }

        // (4) Is main mirror by mirrors.trie also main mirror in webmaster database
        if (mainMirrorInfo.getMainMirrorId() == null) {

            if (!forceAddMirror) {
                checkMainMirrorAlreadyAdded(mainMirrorInfo, hostName, mainMirrorInfo.getName(), userId);
            }

            return new AddHostMainMirrorInfo(hostName, null, mainMirror, null);
        }

        // (5) Add host as separate one
        return new AddHostMainMirrorInfo(hostName, null, null, null);
    }

    private void checkAlreadyAdded(@Nullable final BriefHostInfo hostInfo, final long userId) throws InternalException, ClientException {
        if (hostInfo != null && usersHostsService.getUsersHostsInfo(userId, hostInfo.getId()) != null) {
            // хост уже добавлен, тогда не показываем сообщение про зеркала
            throw new ClientException(
                    ClientProblem.HOST_ALREADY_ADDED,
                    "Host has been already added to the users host list",
                    new ExtraTagInfo(WMCExtraTagNameEnum.ORIGINAL_HOST_NAME, hostInfo.getName()),
                    new ExtraTagInfo(WMCExtraTagNameEnum.ORIGINAL_HOST_ID, Long.toString(hostInfo.getId())));
        }
    }

    private void checkMainMirrorAlreadyAdded(@Nullable final BriefHostInfo mainMirrorInfo, final String hostName, final String mainMirror, final long userId) throws InternalException, ClientException {
        if (mainMirrorInfo != null && usersHostsService.getUsersHostsInfo(userId, mainMirrorInfo.getId()) != null) {

            // Если добавляемый сайт является неглавным зеркалом и
            // главное зеркало уже добавлено пользователем, то
            // пользователя перенаправляют на страницу с информацией о сайте - главном зеркале
            throw new ClientException(ClientProblem.HOST_IS_MIRROR_ALREADY_ADDED,
                    "Host has been already added to the users host list",
                    new ExtraTagInfo(WMCExtraTagNameEnum.MAIN_MIRROR, mainMirror),
                    new ExtraTagInfo(WMCExtraTagNameEnum.MAIN_MIRROR_HOST_ID, Long.toString(mainMirrorInfo.getId())),
                    new ExtraTagInfo(WMCExtraTagNameEnum.ORIGINAL_HOST_NAME, hostName));
        } else {
            // Если главного зеркала нет у пользователя, то пользователю сообщают что нужно добавлять
            // главное зеркало
            throw new ClientException(ClientProblem.HOST_IS_MIRROR, "Host is a mirror of " + mainMirror,
                    new ExtraTagInfo(ExtraTagNameEnum.ALT, processIDN(mainMirror)),
                    new ExtraTagInfo(WMCExtraTagNameEnum.ORIGINAL_HOST_NAME, hostName));
        }
    }

    public static class AddHostMainMirrorInfo {
        final String hostName;
        final BriefHostInfo hostInfo;
        final String mainMirror;
        final BriefHostInfo mainMirrorHostInfo;

        public AddHostMainMirrorInfo(String hostName, BriefHostInfo hostInfo, String mainMirror, BriefHostInfo mainMirrorHostInfo) {
            this.hostName = hostName;
            this.hostInfo = hostInfo;
            this.mainMirror = mainMirror;
            this.mainMirrorHostInfo = mainMirrorHostInfo;
        }

        public String getHostName() {
            return hostName;
        }

        public BriefHostInfo getHostInfo() {
            return hostInfo;
        }

        public String getMainMirror() {
            return mainMirror;
        }

        public BriefHostInfo getMainMirrorHostInfo() {
            return mainMirrorHostInfo;
        }
    }

    //todo remove duplication
    private String processIDN(@NotNull String urlString) {
        try {
            URL url = SupportedProtocols.getURL(urlString);
            String decodedHost = IDN.toUnicode(url.getHost());
            if (!decodedHost.equals(url.getHost())) {
                return urlString.replace(url.getHost(), decodedHost);
            }
        } catch (MalformedURLException e) {
            // ignore
        } catch (URISyntaxException e) {
            // ignore
        } catch (SupportedProtocols.UnsupportedProtocolException e) {
            // ignore
        }
        return urlString;
    }

    private boolean isPortNumberCorrect(@NotNull URL url) {
        if (url.getPort() == -1) {
            return true;
        }
        return ((url.getPort() >= 1) && (url.getPort() <= 65535));
    }

    /**
     * Запустить подгрузку данных для хоста
     *
     * @param hostname      имя хоста
     * @param checkVerified проверять количество неподтвержденных сайтов. Если больше 10 - не запускать подгрузку
     * @throws InternalException
     */
    private void forceUpdateHostInfo(String hostname, boolean checkVerified, long hostsCount, long userId) throws InternalException {
        if (URLUtil.isSpamerDomain(hostname)) {
            log.info("DENY uploadHostData for spamer host " + hostname + " for " + userId);
            return;
        }
        if (checkVerified) { // вызвали из addhost, нужно проверять
            if (hostsCount > maxAddHostWithUpload) {
                log.info("DENY uploadHostData for " + userId + " with hostsCount = " + hostsCount);
                return;
            }
        } else {             // вызывали после подтверждения прав, нужно уменьшить счетчик
            usersHostsService.descreaseHostsCounter(userId);
        }
        HostDbHostInfo hostDbHostInfo = hostDbHostInfoService.getHostDbHostInfo(hostname);
        tblRobotdbInfoDao.updateState(hostDbHostInfo, UpdateStateEnum.NEW);
        addHostDataVisitorService.fireCheck();
    }

    private void saveHostInfoStatus(@NotNull HostDbHostInfo hostDbHostInfo, HostInfoStatusEnum status) throws InternalException {
        HostStatusInfo oldStatusInfo = hostInfoService.getHostInfoStatus(hostDbHostInfo);
        HostInfoStatusEnum oldStatusEnum = oldStatusInfo != null ? oldStatusInfo.getCalculatedHostInfoStatus() : null;

        if (oldStatusEnum == null ||
                HostInfoStatusEnum.WAITING.equals(oldStatusEnum) && !HostInfoStatusEnum.WAITING.equals(status)) {
            log.debug("saveAddUrlStatus: save status " + status + " for host with id = " + hostDbHostInfo.getHostDbHostId());
            tblHostInfoDao.addHostInfoOrUpdateStatus(hostDbHostInfo, status);
        }
    }

    private void saveEmailLang(long userId, @Nullable String lang) throws InternalException {
        if (lang == null) {
            return;
        }
        UserOptionsInfo options = userOptionsService.getUserOptions(userId);
        LanguageEnum language = LanguageEnum.fromString(lang);
        if (options.getEmailLanguage() == null && language != null) {
            UserOptionsInfo newOptions = options.replaceEmailLanguage(language);
            userOptionsService.updateUserOptions(newOptions);
        }
    }

    public static class RedirectInfo {
        private AddHostRedirectEnum redirectState;

        private String taget;

        public RedirectInfo(AddHostRedirectEnum redirectState, String taget) {
            this.redirectState = redirectState;
            this.taget = taget;
        }

        public AddHostRedirectEnum getRedirectState() {
            return redirectState;
        }

        public String getTaget() {
            return taget;
        }
    }

    public static class HostDbAddHostInfo {
        private long urlsCount;
//        private boolean updateRequired;

        public HostDbAddHostInfo(long urlsCount/*, boolean updateRequired*/) {
            this.urlsCount = urlsCount;
//            this.updateRequired = updateRequired;
        }

        public long getUrlsCount() {
            return urlsCount;
        }

//        public boolean isUpdateRequired() {
//            return updateRequired;
//        }
    }

    public static class UserDbAddHostInfo {
        private BriefHostInfo hostInfo;
        private UsersHostsInfo usersHostsInfo;

        public UserDbAddHostInfo(BriefHostInfo hostInfo, UsersHostsInfo usersHostsInfo) {
            this.hostInfo = hostInfo;
            this.usersHostsInfo = usersHostsInfo;
        }

        public BriefHostInfo getHostInfo() {
            return hostInfo;
        }

        public UsersHostsInfo getUsersHostsInfo() {
            return usersHostsInfo;
        }
    }

    @Required
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

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

    @Required
    public void setUsersHostsService(UsersHostsService usersHostsService) {
        this.usersHostsService = usersHostsService;
    }

    @Required
    public void setAddHostDataVisitorService(AddHostDataVisitorService addHostDataVisitorService) {
        this.addHostDataVisitorService = addHostDataVisitorService;
    }

    @Required
    public void setHostDbHostInfoService(HostDbHostInfoService hostDbHostInfoService) {
        this.hostDbHostInfoService = hostDbHostInfoService;
    }

    @Required
    public void setHistoryService(HistoryService historyService) {
        this.historyService = historyService;
    }

    @Required
    public void setUserOptionsService(UserOptionsService userOptionsService) {
        this.userOptionsService = userOptionsService;
    }

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

    @Required
    public void setHostVisitorService(HostVisitorService hostVisitorService) {
        this.hostVisitorService = hostVisitorService;
    }

    @Required
    public void setMaxRedirects(int maxRedirects) {
        this.maxRedirects = maxRedirects;
    }

    @Required
    public void setSocketTimeoutMillis(int socketTimeoutMillis) {
        this.socketTimeoutMillis = socketTimeoutMillis;
    }

    @Required
    public void setMaxUserHostsCount(int maxUserHostsCount) {
        this.maxUserHostsCount = maxUserHostsCount;
    }

    @Required
    public void setMaxAddHostWithUpload(long maxAddHostWithUpload) {
        this.maxAddHostWithUpload = maxAddHostWithUpload;
    }

    @Required
    public void setMaxNotVerifiedHostsCount(long maxNotVerifiedHostsCount) {
        this.maxNotVerifiedHostsCount = maxNotVerifiedHostsCount;
    }

    @Required
    public void setMinVerifiedRate(double minVerifiedRate) {
        this.minVerifiedRate = minVerifiedRate;
    }

    @Required
    public void setMaxMirrorsCount(int maxMirrorsCount) {
        this.maxMirrorsCount = maxMirrorsCount;
    }

    @Required
    public void setSitaNoCheckRedirectService(SitaRedirectService sitaRedirectService) {
        this.sitaNoCheckRedirectService = sitaRedirectService;
    }

    @Required
    public void setConsistentMainMirrorService(ConsistentMainMirrorService consistentMainMirrorService) {
        this.consistentMainMirrorService = consistentMainMirrorService;
    }

    @Required
    public void setSafeNewWebmasterHttpService(SafeNewWebmasterHttpService safeNewWebmasterHttpService) {
        this.safeNewWebmasterHttpService = safeNewWebmasterHttpService;
    }

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

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

    @Required
    public void setTblHostsHostDao(TblHostsHostDao tblHostsHostDao) {
        this.tblHostsHostDao = tblHostsHostDao;
    }

    @Required
    public void setTblUsersHostsDao(TblUsersHostsDao tblUsersHostsDao) {
        this.tblUsersHostsDao = tblUsersHostsDao;
    }

    @Required
    public void setHostEventService(HostEventService hostEventService) {
        this.hostEventService = hostEventService;
    }

    @Required
    public void setTblHostInfoDao(TblHostInfoDao tblHostInfoDao) {
        this.tblHostInfoDao = tblHostInfoDao;
    }
}
