package ru.yandex.webmaster3.worker.digest;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import javax.activation.DataSource;
import javax.mail.util.ByteArrayDataSource;

import NWebmaster.proto.digest.Digest;
import com.google.common.base.Strings;
import com.google.common.collect.Range;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.digest.graphics.draw.ChartSettings;
import ru.yandex.webmaster3.core.notification.LanguageEnum;
import ru.yandex.webmaster3.core.notification.UTMLabels;
import ru.yandex.webmaster3.core.searchquery.SpecialGroup;
import ru.yandex.webmaster3.core.solomon.metric.SolomonCounter;
import ru.yandex.webmaster3.core.solomon.metric.SolomonKey;
import ru.yandex.webmaster3.core.solomon.metric.SolomonMetricConfiguration;
import ru.yandex.webmaster3.core.solomon.metric.SolomonMetricRegistry;
import ru.yandex.webmaster3.core.turbo.model.autoparser.AutoparserToggleState;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedType;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.W3Collectors;
import ru.yandex.webmaster3.core.util.WwwUtil;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.abt.AbtService;
import ru.yandex.webmaster3.storage.abt.model.Experiment;
import ru.yandex.webmaster3.storage.achievements.model.AchievementService;
import ru.yandex.webmaster3.storage.digest.DigestMessage;
import ru.yandex.webmaster3.storage.host.CommonDataType;
import ru.yandex.webmaster3.storage.notifications.service.TrackingEmailSenderService;
import ru.yandex.webmaster3.storage.settings.SettingsService;
import ru.yandex.webmaster3.storage.turbo.dao.statistics.TurboDomainStatisticsYDao;
import ru.yandex.webmaster3.storage.turbo.service.autoparser.TurboAutoparserInfoService;
import ru.yandex.webmaster3.storage.user.dao.PersonalInfoCacheYDao;
import ru.yandex.webmaster3.storage.user.notification.NotificationType;
import ru.yandex.webmaster3.tanker.I18nDigestDynamic;
import ru.yandex.webmaster3.tanker.WebmasterLinksProvider;
import ru.yandex.webmaster3.tanker.digest.html.HtmlDomUtil;
import ru.yandex.webmaster3.worker.digest.blog.YaBlogsApiService;
import ru.yandex.webmaster3.worker.digest.graphics.draw.BuildClicksGraphicsUtil;
import ru.yandex.webmaster3.worker.digest.html.DigestBlocks;
import ru.yandex.webmaster3.worker.digest.html.DigestBlocksBuilder;
import ru.yandex.webmaster3.worker.digest.html.DigestData;
import ru.yandex.webmaster3.worker.digest.html.DigestDataBuilder;
import ru.yandex.webmaster3.worker.digest.html.DigestI18n;
import ru.yandex.webmaster3.worker.digest.html.DigestTextUtil;
import ru.yandex.webmaster3.worker.digest.html.DigestTrackingInfo;
import ru.yandex.webmaster3.worker.notifications.auto.AutoNotificationsSenderService;

/**
 * @author avhaliullin
 */
@Setter
@NoArgsConstructor
public class DigestNotificationService {
    private static final Logger log = LoggerFactory.getLogger(DigestNotificationService.class);
    private static final String FULL_DIGEST_MESSAGE_TYPE = NotificationType.DIGEST.toString();
    private static final String LITE_DIGEST_MESSAGE_TYPE = NotificationType.DIGEST_LITE.toString();

    private SolomonMetricConfiguration metricConfiguration;
    private SolomonMetricRegistry solomonMetricRegistry;
    private ChartSettings chartSettings;
    private SettingsService settingsService;
    private TrackingEmailSenderService trackingEmailSenderService;
    private PersonalInfoCacheYDao personalInfoCacheYDao;
    private YaBlogsApiService yaBlogsApiService;
    private String serviceBaseUrl;
    private TurboAutoparserInfoService turboAutoparserInfoService;
    private TurboDomainStatisticsYDao turboDomainStatisticsYDao;
    private AbtService abtService;
    private AchievementService achievementService;

    private SolomonCounter fullDigestCounter;
    private SolomonCounter liteDigestCounter;

    @Autowired
    public DigestNotificationService(SolomonMetricConfiguration metricConfiguration,
                                     SolomonMetricRegistry solomonMetricRegistry, ChartSettings chartSettings,
                                     TrackingEmailSenderService trackingEmailSenderService,
                                     PersonalInfoCacheYDao personalInfoCacheYDao, YaBlogsApiService yaBlogsApiService,
                                     String serviceBaseUrl, TurboAutoparserInfoService turboAutoparserInfoService,
                                     TurboDomainStatisticsYDao turboDomainStatisticsYDao, AbtService abtService,
                                     AchievementService achievementService) {
        this.metricConfiguration = metricConfiguration;
        this.solomonMetricRegistry = solomonMetricRegistry;
        this.chartSettings = chartSettings;
        this.trackingEmailSenderService = trackingEmailSenderService;
        this.personalInfoCacheYDao = personalInfoCacheYDao;
        this.yaBlogsApiService = yaBlogsApiService;
        this.serviceBaseUrl = serviceBaseUrl;
        this.turboAutoparserInfoService = turboAutoparserInfoService;
        this.turboDomainStatisticsYDao = turboDomainStatisticsYDao;
        this.abtService = abtService;
        this.achievementService = achievementService;
    }

    public void init() {
        fullDigestCounter = solomonMetricRegistry.createCounter(
                metricConfiguration,
                SolomonKey.create(AutoNotificationsSenderService.SOLOMON_LABEL_MESSAGE_TYPE, FULL_DIGEST_MESSAGE_TYPE)
                        .withLabel(AutoNotificationsSenderService.SOLOMON_LABEL_NOTIFICATION_CHANNEL, "EMAIL")
        );
        liteDigestCounter = solomonMetricRegistry.createCounter(
                metricConfiguration,
                SolomonKey.create(AutoNotificationsSenderService.SOLOMON_LABEL_MESSAGE_TYPE, LITE_DIGEST_MESSAGE_TYPE)
                        .withLabel(AutoNotificationsSenderService.SOLOMON_LABEL_NOTIFICATION_CHANNEL, "EMAIL")
        );
    }

    public boolean sendDigest(DigestMessage message, String toEmail) {
        //TODO: Logging, metrics
        Digest.DigestWeeklyReportMessage digest = message.getDigest();
        Digest.AllQueriesIndicators allQueries = digest.getAllQueries();

        Attaches attaches = new Attaches();
        if (clicksNewDataIsPresent(digest)) {
            List<Digest.ClicksByPeriod> oldData = new ArrayList<>();
            if (clicksOldDataIsPresent(digest)) {
                oldData = allQueries.getOldClicksByPeriodList();
            }
            byte[] clickDynamicsImage = BuildClicksGraphicsUtil.process(oldData, allQueries.getNewClicksByPeriodList(), chartSettings);
            attaches.attach(DigestAttachType.CLICKS_DYNAMICS, new ByteArrayDataSource(clickDynamicsImage, "image/png"));
        }
        DigestData renderData = createData(message);
        DigestBlocks digestBlocks = buildDigest(message, renderData, attaches.getAvailableAttaches());
        String html = buildHtml(digestBlocks, message.getHostId(), message.getLanguage());

        if (digestBlocks.isEmpty() || Strings.isNullOrEmpty(html)) {
            return true;
        }

        String title = message.getNotificationType() == NotificationType.DIGEST ?
                I18nDigestDynamic.DIGEST_MESSAGE_SUBJECT.newBuilder(message.getLanguage())
                        .host(DigestTextUtil.readableHost(message.getHostId()))
                        .dateRange(DigestTextUtil.dateRangeInSubject(message.getLanguage(), renderData.getDigestFrom(), renderData.getDigestTo()))
                        .render() :
                I18nDigestDynamic.lite_digest_message_subject.newBuilder(message.getLanguage())
                        .host(DigestTextUtil.readableHost(message.getHostId()))
                        .dateRange(DigestTextUtil.dateRangeInSubject(message.getLanguage(), renderData.getDigestFrom(), renderData.getDigestTo()))
                        .render();

        final Map<String, DataSource> inlines = renderData.getClicks().isPresent() ? attaches.getDataSources() : null;
        DigestTrackingInfo trackingInfo = new DigestTrackingInfo(message.getHostId(), digestBlocks.getBlockTypes(), message.getUserId(), message.getNotificationType().toString(), DateTime.now());
        log.info("Sending email DIGEST to user {} {} {} tracking info {}", message.getUserId(), toEmail, IdUtils.hostIdToUrl(message.getHostId()), trackingInfo);
        boolean isSent = trackingEmailSenderService.sendEmail(toEmail, toEmail, title, html, inlines, trackingInfo);
        if (isSent) {
            if (message.getNotificationType() == NotificationType.DIGEST){
                fullDigestCounter.update();
            } else {
                liteDigestCounter.update();
            }
        }

        return isSent;
    }

    public DigestBlocks buildDigest(DigestMessage message, DigestData renderData, Set<DigestAttachType> availableAttaches) {
        WebmasterLinksProvider linksProvider = new WebmasterLinksProvider(
                serviceBaseUrl,
                UTMLabels.createEmail(
                        message.getNotificationType() == NotificationType.DIGEST ?
                                "digest" :
                                "digest_lite",
                        renderData.getDigestTo(),
                        null
                )
        );

        return new DigestBlocksBuilder(
                renderData.getHost(),
                renderData.getDigestFrom(),
                renderData.getDigestTo(),
                new DigestI18n(message.getLanguage()),
                linksProvider,
                availableAttaches,
                chartSettings,
                message.getNotificationType(),
                achievementService,
                false
        ).build(renderData);
    }

    public String buildHtml(DigestMessage message, DigestData renderData, Set<DigestAttachType> availableAttaches) {
        return buildHtml(buildDigest(message, renderData, availableAttaches), message.getHostId(), message.getLanguage());
    }

    public String buildHtml(DigestBlocks digestBlocks, WebmasterHostId hostId, LanguageEnum lang) {
        if (digestBlocks.isEmpty()) {
            log.info("Ignoring empty digest message for " + hostId);
            return "";
        }

        return HtmlDomUtil.makePageHtml(digestBlocks.getDigest())
                .replace(
                        DigestBlocksBuilder.CUSTOM_BLOCK_PLACEHOLDER,
                        lang == LanguageEnum.EN ?
                                settingsService.getSettingCached(CommonDataType.DIGEST_CUSTOM_BLOCK_EN).getValue() :
                                settingsService.getSettingCached(CommonDataType.DIGEST_CUSTOM_BLOCK_RU).getValue()
                ).replace(
                        DigestBlocksBuilder.CUSTOM_BLOCK_PLACEHOLDER_HOST,
                        IdUtils.hostIdToWebIdString(hostId)
                );
    }

    private void saveHtmlToFile(String html, String pathToFile) throws IOException {
        log.debug("{}", pathToFile);
        try (FileWriter w = new FileWriter(pathToFile)) {
            w.write(html);
        }
    }

    public DigestData createData(DigestMessage digestMessage) {
        Digest.DigestWeeklyReportMessage digest = digestMessage.getDigest();

        DigestDataBuilder builder = new DigestDataBuilder(new DigestI18n(digestMessage.getLanguage()));
        builder.withHost(digestMessage.getHostId());
        builder.withPeriod(digestMessage.getDigestPeriodEnd().minusDays(6), digestMessage.getDigestPeriodEnd());

        boolean isYmlMarketTeaser = false;
        boolean hasOffAutoparser = false;
        boolean isFirstLiteDigest = abtService.isInExperiment(digestMessage.getUserId(), Experiment.DIGEST_LITE_FIRST_MESSAGE);
        long pagesCount = 0;
        try {
            String domain = WwwUtil.cutWWWAndM(digestMessage.getHostId());
            AutoparserToggleState toggleState = turboAutoparserInfoService.getAutoparseCheckBoxState(domain);
            if (toggleState == AutoparserToggleState.OFF) {
                hasOffAutoparser = true;
                pagesCount = turboDomainStatisticsYDao.getValue(domain, TurboFeedType.AUTO);
                if (pagesCount == 0L) {
                    pagesCount = turboDomainStatisticsYDao.getValue(domain, TurboFeedType.AUTO_DISABLED);
                }
            }
        } catch (WebmasterYdbException e) {
            log.error("Unable to get autoparser toggle state");
            //ignore
        }
        Digest.IKSState oldIks = digest.getIks().getOldState();
        Digest.IKSState newIks = digest.getIks().getNewState();
        String owner = digest.getIks().getOwner();
        Digest.CheckList checklist = digest.getChecklist();
        builder.withChecklist(
                checklist.hasOldStatus() ? checklist.getOldStatus().getProblemsList() : Collections.EMPTY_LIST,
                checklist.hasNewStatus() ? checklist.getNewStatus().getProblemsList() : Collections.EMPTY_LIST,

                digest.getGeoRegions().hasOldState() ? digest.getGeoRegions().getOldState().getRegionsList() : Collections.EMPTY_LIST,
                digest.getGeoRegions().hasNewState() ? digest.getGeoRegions().getNewState().getRegionsList() : Collections.EMPTY_LIST,

                digest.getBeautyUrls().hasOldState() ? digest.getBeautyUrls().getOldState() : "",
                digest.getBeautyUrls().hasNewState() ? digest.getBeautyUrls().getNewState() : "",

                digest.getRecrawlUrls().getNewStateCount(),

                digest.getUserVerifications().getNewVerificationsList(),
                personalInfoCacheYDao,

                isYmlMarketTeaser,
                hasOffAutoparser,
                pagesCount,

                oldIks,
                newIks,
                owner
        );

        //ачивки
        builder.withAchievements(
                digest.getAchievements().getAchievementsOldList(),
                digest.getAchievements().getAchievementsNewList()
        );

        // только новые
        builder.withSearchable(
                digest.getSitetrees().getOldState().getNumOfDocsOnSearch(), // на прошлой неделе
                digest.getSitetrees().getNewState().getNumOfDocsOnSearch(), //всего
                digest.getSitetrees().getNewState().getNewDocsOnSearch(),  //добавленные
                digest.getSitetrees().getNewState().getGoneDocsOnSearch(),//измененные
                digest.getSitetrees().getUrlHistoryList());

        //TODO: workaround for WMC-4645
        Optional<Digest.GroupClicksSample> allQueriesGroupOpt = findAllQueriesGroup(digest.getGroupQueries());
        if (allQueriesGroupOpt.isPresent()) {
            Digest.GroupClicksSample allQueriesGroup = allQueriesGroupOpt.get();
            builder.withClicks(allQueriesGroup.getClicks().getOldValue(), allQueriesGroup.getClicks().getNewValue(),
                    clicksOldDataIsPresent(digest));
        } else {
            builder.withClicks(digest.getAllQueries().getClicks().getOldValue(), digest.getAllQueries().getClicks().getNewValue(),
                    clicksOldDataIsPresent(digest));
        }

        builder.withQueries(digest.getTopQueries().getNegativeQueryClicksSamplesList(),
                digest.getTopQueries().getPositiveQueryClicksSamplesList());

        builder.withQueriesGroups(digest.getGroupQueries().getNegativeQueryClicksSamplesList(),
                digest.getGroupQueries().getPositiveQueryClicksSamplesList());

        builder.withImportantUrl(digest.getImportantUrls().getEventsList());

        builder.withIsFirstLiteDigest(isFirstLiteDigest);

        builder.withRecommendedUrl(digest.getRecommendedUrls().getRecommendedUrlsList());

        builder.withBlogPosts(yaBlogsApiService.getBlogPosts(Range.closed(digestMessage.getDigestPeriodEnd().minusDays(6),
                digestMessage.getDigestPeriodEnd())));

        builder.withReviews(digest.getReviews());
        builder.withMirrors(digest.getMirrors());
        builder.withMetrikaProblem(checklist.hasNewStatus() ? checklist.getNewStatus().getProblemsList() : Collections.EMPTY_LIST);

        return builder.build();

    }

    private Optional<Digest.GroupClicksSample> findAllQueriesGroup(Digest.GroupQuerySamples samples) {
        String allQueriesGroupIdString = SpecialGroup.ALL_QUERIES.getGroupId().toString();
        return Stream.concat(
                samples.getNegativeQueryClicksSamplesList().stream(),
                samples.getPositiveQueryClicksSamplesList().stream()
        ).filter(group -> group.getGroupId().equals(allQueriesGroupIdString)).findAny();
    }

    public boolean clicksDataIsPresent(Digest.DigestWeeklyReportMessage digest) {
        return digest.hasAllQueries() && digest.getAllQueries() != null;
    }

    public boolean clicksOldDataIsPresent(Digest.DigestWeeklyReportMessage digest) {
        return clicksDataIsPresent(digest) && digest.getAllQueries().getOldClicksByPeriodList() != null &&
                digest.getAllQueries().getOldClicksByPeriodList().size() == 7;
    }

    public boolean clicksNewDataIsPresent(Digest.DigestWeeklyReportMessage digest) {
        return clicksDataIsPresent(digest) && digest.getAllQueries().getNewClicksByPeriodList() != null &&
                digest.getAllQueries().getNewClicksByPeriodList().size() == 7;
    }

    private static class Attaches {
        private final Map<DigestAttachType, DataSource> attaches = new EnumMap<>(DigestAttachType.class);

        public Set<DigestAttachType> getAvailableAttaches() {
            return attaches.keySet();
        }

        public Map<String, DataSource> getDataSources() {
            return attaches.entrySet().stream().map(e -> Pair.of(e.getKey().getCid(), e.getValue())).collect(W3Collectors.toHashMap());
        }

        public Attaches attach(DigestAttachType type, DataSource dataSource) {
            attaches.put(type, dataSource);
            return this;
        }
    }
}
