package ru.yandex.webmaster.periodic.wm3.sync;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.common.scheduler.ExecutionContext;
import ru.yandex.webmaster.common.host.dao.TblHostsHostDao;
import ru.yandex.webmaster.common.host.dao.TblHostsMainDao;
import ru.yandex.webmaster.common.sitemap.RemovedUserSitemap;
import ru.yandex.webmaster.common.sitemap.SitemapService;
import ru.yandex.webmaster.common.sitemap.TblRemovedUserSitemapsDao;
import ru.yandex.webmaster.periodic.service.NewWebmasterCoordinatorProxyService;
import ru.yandex.wmconsole.data.info.BriefHostInfo;
import ru.yandex.wmconsole.data.info.HostDbHostInfo;
import ru.yandex.wmconsole.data.info.SitemapInfo;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.util.scheduler.timetable.AbstractTaskExecutor;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
 * @author avhaliullin
 */
public class SynchronizeSitemapsTask extends AbstractTaskExecutor {
    private static final long UNKNOWN_USER_ID = 120160451;
    private static final Logger log = LoggerFactory.getLogger(SynchronizeSitemapsTask.class);
    private static final int HOSTS_BATCH_SIZE = 2000;

    private NewWebmasterCoordinatorProxyService newWebmasterCoordinatorProxyService;
    private SitemapService sitemapService;
    private TblHostsMainDao tblHostsMainDao;
    private TblHostsHostDao tblHostsHostDao;
    private TblRemovedUserSitemapsDao tblRemovedUserSitemapsDao;

    @Override
    public String runWithRELogging(ExecutionContext context) throws InternalException {
        long fromHostId = -1;
        boolean haveMoreHosts = true;
        while (haveMoreHosts) {
            List<BriefHostInfo> userDBHosts = tblHostsMainDao.listVerifiedHosts(fromHostId, HOSTS_BATCH_SIZE);
            Map<String, BriefHostInfo> hostName2hostInfo = new HashMap<>();
            for (BriefHostInfo hostInfo : userDBHosts) {
                hostName2hostInfo.put(hostInfo.getName().toLowerCase(), hostInfo);
            }
            Map<Long, List<RemovedUserSitemap>> userDBHostId2RemovedSitemaps =
                    tblRemovedUserSitemapsDao.listRemovedSitemapsForHosts(userDBHosts);
            if (userDBHosts.isEmpty()) {
                haveMoreHosts = false;
            } else {
                fromHostId = userDBHosts.get(userDBHosts.size() - 1).getId();

                List<List<HostDbHostInfo>> hostDBHosts = tblHostsHostDao.listHostsForNames(userDBHosts);
                for (List<HostDbHostInfo> hostsForDB : hostDBHosts) {

                    Map<Long, List<SitemapInfo>> hostId2Sitemaps = sitemapService.listUserSitemapsForHostsInOneDB(hostsForDB);
                    for (HostDbHostInfo hostDbHostInfo : hostsForDB) {
                        List<SitemapInfo> oldWmSitemaps = hostId2Sitemaps.get(hostDbHostInfo.getHostDbHostId());
                        if (oldWmSitemaps == null) {
                            oldWmSitemaps = Collections.emptyList();
                        }
                        List<RemovedUserSitemap> oldWmRemovedSitemapsList =
                                userDBHostId2RemovedSitemaps.get(hostName2hostInfo.get(hostDbHostInfo.getName().toLowerCase()).getId());
                        if (oldWmRemovedSitemapsList == null) {
                            oldWmRemovedSitemapsList = Collections.emptyList();
                        }
                        List<NewWebmasterCoordinatorProxyService.SitemapInfo> newWmSitemaps =
                                newWebmasterCoordinatorProxyService.getSitemaps(hostDbHostInfo);
                        log.info("For host {} sitemaps count in old webmaster: {}, removed sitemaps in old webmaster: {}, " +
                                        "total sitemaps in new webmaster: {}",
                                hostDbHostInfo.getName(), oldWmSitemaps.size(), oldWmRemovedSitemapsList.size(), newWmSitemaps.size());
                        Map<String, NewWebmasterCoordinatorProxyService.SitemapInfo> newWmAddedSitemaps = new HashMap<>();
                        Map<String, NewWebmasterCoordinatorProxyService.SitemapInfo> newWmRemovedSitemaps = new HashMap<>();
                        Map<String, SitemapInfo> oldWmAddedSitemaps = new HashMap<>();
                        Map<String, RemovedUserSitemap> oldWmRemovedSitemaps = new HashMap<>();

                        for (NewWebmasterCoordinatorProxyService.SitemapInfo sitemapInfo : newWmSitemaps) {
                            if (sitemapInfo.deleted) {
                                newWmRemovedSitemaps.put(sitemapInfo.url, sitemapInfo);
                            } else {
                                newWmAddedSitemaps.put(sitemapInfo.url, sitemapInfo);
                            }
                        }

                        for (SitemapInfo sitemapInfo : oldWmSitemaps) {
                            oldWmAddedSitemaps.put(sitemapInfo.getUrl(), sitemapInfo);
                        }
                        for (RemovedUserSitemap sitemap : oldWmRemovedSitemapsList) {
                            oldWmRemovedSitemaps.put(sitemap.getSitemapUrl(), sitemap);
                        }

                        for (SitemapInfo sitemapInfo : oldWmAddedSitemaps.values()) {
                            String url = sitemapInfo.getUrl();
                            if (newWmAddedSitemaps.containsKey(url)) {
                                // sitemap есть в обоих ВМ
                                continue;
                            }
                            NewWebmasterCoordinatorProxyService.SitemapInfo removedInNew = newWmRemovedSitemaps.get(url);
                            if (removedInNew != null && removedInNew.modificationDate.after(sitemapInfo.getSubmittedDate())) {
                                log.info("Sitemap {} for host {} recently removed from new wm, removing it from old wm", url, hostDbHostInfo.getName());
                                removeFromOldWM(hostDbHostInfo, removedInNew.userId, sitemapInfo.getId(), url);
                            } else {
                                log.info("Sitemap {} for host {} recently added to old wm, adding it in new wm", url, hostDbHostInfo.getName());
                                addInNewWM(hostDbHostInfo, UNKNOWN_USER_ID, url);
                            }
                        }
                        for (NewWebmasterCoordinatorProxyService.SitemapInfo sitemapInfo : newWmAddedSitemaps.values()) {
                            String url = sitemapInfo.url;
                            if (oldWmAddedSitemaps.containsKey(url)) {
                                continue;
                            }
                            RemovedUserSitemap removedInOld = oldWmRemovedSitemaps.get(url);
                            if (removedInOld != null && removedInOld.getRemoveDate().after(sitemapInfo.modificationDate)) {
                                log.info("Sitemap {} for host {} recently removed from old wm, removing it from new wm", url, hostDbHostInfo.getName());
                                removeFromNewWM(hostDbHostInfo, sitemapInfo.id, url);
                            } else {
                                log.info("Sitemap {} for host {} recently added to new wm, adding it in old wm", url, hostDbHostInfo.getName());
                                addInOldWM(sitemapInfo.userId, hostDbHostInfo, url, sitemapInfo.modificationDate);
                            }
                        }
                    }
                }
            }
        }
        return "OK";
    }

    private void addInNewWM(HostDbHostInfo hostDbHostInfo, long userId, String url) {
        try {
            newWebmasterCoordinatorProxyService.addSitemap(hostDbHostInfo, userId, url);
        } catch (InternalException e) {
            logError("Failed to add sitemap in new wm", hostDbHostInfo, url, e);
        }
    }

    private void removeFromNewWM(HostDbHostInfo hostDbHostInfo, String sitemapId, String url) {
        try {
            newWebmasterCoordinatorProxyService.removeSitemap(hostDbHostInfo, sitemapId);
        } catch (InternalException e) {
            logError("Failed to remove sitemap from new wm", hostDbHostInfo, url, e);
        }
    }

    private void addInOldWM(long userId, HostDbHostInfo hostDbHostInfo, String url, Date submittedOn) {
        try {
            sitemapService.addSitemap(userId, userId, hostDbHostInfo, new URL(url), submittedOn);
        } catch (UserException | InternalException | MalformedURLException e) {
            logError("Failed to add sitemap in old wm", hostDbHostInfo, url, e);
        }
    }

    private void removeFromOldWM(HostDbHostInfo hostDbHostInfo, long userId, long sitemapId, String url) {
        try {
            sitemapService.internalRemoveSitemap(hostDbHostInfo, userId, sitemapId);
        } catch (UserException | InternalException e) {
            logError("Failed to remove sitemap from old wm", hostDbHostInfo, url, e);
        }
    }

    private void logError(String message, HostDbHostInfo hostDbHostInfo, String sitemapUrl, Throwable t) {
        log.error(message + " - for host " + hostDbHostInfo.getName() + " and sitemap " + sitemapUrl, t);
    }

    @Required
    public void setNewWebmasterCoordinatorProxyService(NewWebmasterCoordinatorProxyService newWebmasterCoordinatorProxyService) {
        this.newWebmasterCoordinatorProxyService = newWebmasterCoordinatorProxyService;
    }

    @Required
    public void setSitemapService(SitemapService sitemapService) {
        this.sitemapService = sitemapService;
    }

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

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

    @Required
    public void setTblRemovedUserSitemapsDao(TblRemovedUserSitemapsDao tblRemovedUserSitemapsDao) {
        this.tblRemovedUserSitemapsDao = tblRemovedUserSitemapsDao;
    }
}
