package ru.yandex.canvas.service.video;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.google.common.base.Strings;
import com.google.common.io.Resources;

import ru.yandex.canvas.TimeDelta;
import ru.yandex.canvas.model.HtmlCharacterEscapes;
import ru.yandex.canvas.model.video.PythiaParams;
import ru.yandex.canvas.model.video.TrackingEvents;
import ru.yandex.canvas.model.video.addition.AdditionElement;
import ru.yandex.canvas.model.video.files.StreamFormat;

import static java.util.stream.Collectors.toList;
import static ru.yandex.canvas.VideoConstants.VIDEO_AD_SYSTEM_DEFAULT;
import static ru.yandex.canvas.service.video.AdParams.AUCTION_VIEW_NOTICES_MACRO;
import static ru.yandex.canvas.service.video.AdParams.VIEW_NOTICES_COUNT;

public class VAST {
    private static final String DEFAULT_TEMPLATE_NAME = "video_vast_template.xml";

    public static final String SKIP_URL = "${TRACKING_URL_PREFIX}?action-id=9";
    public static final String VAST_CLICK_URL_PLACEHOLDER = "${VAST_CLICK_URL}";
    public static final String RETURN_AFTER_CLICK_THROUGH_EVENT_URL = AUCTION_VIEW_NOTICES_MACRO + "?test-tag=136";
    public static final String SURVEY_CONFIG_MACROS = "${SURVEY_CONFIG}";

    private String parameterizedXml;

    public VAST(String xml) {
        parameterizedXml = xml;
    }

    public String injectDcParams(DCParams dcParams) throws IOException {
        String dc = dcParams.toJSON();
        return parameterizedXml.replace(AdParams.RTB_HOST_DC_PARAMS_MACRO, dc);
    }

    public static String injectMediaFiles(String adParamsJSON, List<StreamFormat> mediaFiles) throws JsonProcessingException {
        //раскрыть макрос ${MEDIA_FILES}
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.getFactory().setCharacterEscapes(new HtmlCharacterEscapes(false));
        String mediaFilesJSON = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(mediaFiles);
        return adParamsJSON.replace(AdParams.RTB_HOST_MEDIA_FILES_MACRO, mediaFilesJSON);
    }

    public String getParameterizedXml() {
        return this.parameterizedXml;
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private Long creativeId;
        private String templateName = DEFAULT_TEMPLATE_NAME;
        private TimeDelta duration;
        private TimeDelta skipOffset;
        private String theme;
        private String strmPrefix;
        private List<StreamFormat> mediaFiles;
        private BigInteger videoMetaId;//идентификатор видео в VH CMS
        private String playerId;
        private String estKaltura;
        private String estMobileVertical;
        private AdParams.FirstFrameParameters firstFrame;
        private List<AdditionElement> elements;
        private String vpaidPcodeUrl;
        private Boolean addPixelImpression;
        private Boolean showVpaid = true;
        private Boolean showVideoClicks;
        private String packshotUrl;
        private String icon;
        private Long soundBtn;
        private Long adLabel;
        private Long countdown;
        private Boolean hasAbuseButton;
        private Boolean hasSkipButton;
        private PythiaParams pythia;
        private String skipUrl;
        private Boolean useVpaidImpressions;
        private Boolean useTrackingEvents;
        private String adSystem = VIDEO_AD_SYSTEM_DEFAULT;
        private Boolean isStock = false;
        private Boolean socialAdvertisement = false;
        private Boolean auctionRenderUrlMacros = false;
        private Boolean injectMediaFilesMacros = true;

        private Mustache loadTemplater() throws IOException, URISyntaxException {
            String injectionHtml = Resources.toString(Resources.getResource(templateName), StandardCharsets.UTF_8);
            return new DefaultMustacheFactory().compile(new StringReader(injectionHtml), "data_params.json");
        }

        public VAST build()
                throws IOException, URISyntaxException {
            Mustache templater = loadTemplater();

            Map<String, Object> vars = new HashMap<>();

            if (mediaFiles == null) {
                mediaFiles = Collections.emptyList();
            }

            var nonEmptyMediaFiles =
                    mediaFiles.stream().filter(f -> !Strings.isNullOrEmpty(f.getId())).collect(toList());

            var parameters = new AdParams()
                    .setDuration(duration)
                    .setElements(elements)
                    .setVideoMetaId(videoMetaId)
                    .setPlayerId(playerId)
                    .setEstKaltura(estKaltura)
                    .setEstMobileVertical(estMobileVertical)
                    .setTheme(theme)
                    .setSkipDelay(skipOffset)
                    .setShowSkipButton(hasSkipButton)
                    .setHasAbuseButton(hasAbuseButton)
                    .setPackshotImageUrl(packshotUrl)
                    .setFirstFrame(firstFrame)
                    .setPythia(pythia)
                    .setSocialAdvertisement(socialAdvertisement)
                    .setIsStock(isStock);

            List<Map<String, String>> viewNoticeMacros = new ArrayList<>();
            if (addPixelImpression) {
                viewNoticeMacros = IntStream.range(1, VIEW_NOTICES_COUNT + 1).boxed()
                        .map(ind -> Map.of("view_notice_macro", String.format("${%s%d}",
                                AdParams.VIEW_NOTICES_MACRO_PREFIX, ind)))
                        .collect(toList());
            }

            if (!useVpaidImpressions) {
                var encounters = parameters.getEncounters();
                encounters.add(AdParams.AUCTION_IMPRESSION_URL_MACRO);
                encounters.add(AUCTION_VIEW_NOTICES_MACRO);
                encounters.addAll(viewNoticeMacros.stream().map(d -> d.get("view_notice_macro")).collect(toList()));
            }

            if (useTrackingEvents) {
                var startUrls = List.of(TrackingEvents.RENDER_TRACKING_URL);
                if (auctionRenderUrlMacros) {
                    startUrls = List.of(TrackingEvents.RENDER_TRACKING_URL, "${AUCTION_RENDER_URL}");
                }
                var trackingEvents = new TrackingEvents()
                        .setStartUrls(startUrls)
                        .setTrueViewUrls(List.of(TrackingEvents.TRUE_VIEW_TRACKING_URL))
                        .setShowHp(List.of(TrackingEvents.SHOW_HP_URL))
                        .setClickHp(List.of(TrackingEvents.CLICK_HP_URL))
                        .setAdsFinish(List.of(TrackingEvents.ADS_FINISH_URL))
                        .setReturnAfterClickThroughUrls(List.of(RETURN_AFTER_CLICK_THROUGH_EVENT_URL));
                parameters.setTrackingEvents(trackingEvents);
            }
            String adParamsJSON = parameters.toJson();
            if (injectMediaFilesMacros) {
                adParamsJSON = injectMediaFiles(adParamsJSON, nonEmptyMediaFiles);
            }

            vars.put("vpaid_pcode_url", vpaidPcodeUrl);
            vars.put("creative_id", creativeId);
            if (skipOffset != null) {
                vars.put("skipoffset", skipOffset.toString());
            }
            vars.put("duration", duration.toString());
            vars.put("duration_seconds", (long) duration.asSeconds());
            vars.put("strm_prefix", strmPrefix);
            vars.put("add_pixel_impression", viewNoticeMacros);
            vars.put("show_vpaid", showVpaid);
            vars.put("show_video_clicks", showVideoClicks);
            vars.put("click_through_url_placeholder", VAST_CLICK_URL_PLACEHOLDER);
            vars.put("soundbtn_layout", soundBtn);
            vars.put("adlabel_layout", adLabel);
            vars.put("countdown_layout", countdown);
            vars.put("mediafiles", nonEmptyMediaFiles);
            vars.put("icon", icon);
            vars.put("skip_url", getSkipUrl());
            vars.put("ad_parameters", adParamsJSON);
            vars.put("use_vpaid_impressions", useVpaidImpressions);
            vars.put("use_custom_tracking", !useTrackingEvents);
            vars.put("ad_system", adSystem);

            StringWriter writer = new StringWriter();

            templater.execute(writer, vars);

            writer.flush();

            return new VAST(writer.toString());
        }

        private Builder() {
        }

        public Builder setCreativeId(Long creativeId) {
            this.creativeId = creativeId;
            return this;
        }

        public Builder setDuration(TimeDelta duration) {
            this.duration = duration;
            return this;
        }

        public Builder setSkipOffset(TimeDelta skipOffset) {
            this.skipOffset = skipOffset;
            return this;
        }

        public Builder setTheme(String theme) {
            this.theme = theme;
            return this;
        }

        public Builder setStrmPrefix(String strmPrefix) {
            this.strmPrefix = strmPrefix;
            return this;
        }

        public Builder setMediaFiles(List<StreamFormat> mediaFiles) {
            this.mediaFiles = mediaFiles;
            return this;
        }

        public Builder setVideoMetaId(BigInteger videoMetaId) {
            this.videoMetaId = videoMetaId;
            return this;
        }

        public Builder setPlayerId(String playerId) {
            this.playerId = playerId;
            return this;
        }

        public Builder setEstKaltura(String estKaltura) {
            this.estKaltura = estKaltura;
            return this;
        }

        public Builder setEstMobileVertical(String estMobileVertical) {
            this.estMobileVertical = estMobileVertical;
            return this;
        }

        public Builder setFirstFrame(AdParams.FirstFrameParameters firstFrame) {
            this.firstFrame = firstFrame;
            return this;
        }

        public Builder setElements(List<AdditionElement> elements) {
            this.elements = elements;
            return this;
        }

        public Builder setVpaidPcodeUrl(String vpaidPcodeUrl) {
            this.vpaidPcodeUrl = vpaidPcodeUrl;
            return this;
        }

        public Builder setAddPixelImpression(Boolean addPixelImpression) {
            this.addPixelImpression = addPixelImpression;
            return this;
        }

        public Builder setPackshotUrl(String url) {
            this.packshotUrl = url;
            return this;
        }

        public String getPackshotUrl() {
            return packshotUrl;
        }

        public Builder setIcon(String icon) {
            this.icon = icon;
            return this;
        }

        public Builder setSoundBtn(Long soundBtn) {
            this.soundBtn = soundBtn;
            return this;
        }

        public Builder setAdLabel(Long adLabel) {
            this.adLabel = adLabel;
            return this;
        }

        public Builder setCountdown(Long countdown) {
            this.countdown = countdown;
            return this;
        }

        public Builder setHasAbuseButton(Boolean hasAbuseButton) {
            this.hasAbuseButton = hasAbuseButton;
            return this;
        }

        public Builder setHasSkipButton(Boolean hasSkipButton) {
            this.hasSkipButton = hasSkipButton;
            return this;
        }

        public Builder setPythia(PythiaParams pythia) {
            this.pythia = pythia;
            return this;
        }

        public Builder setSkipUrl(String skipUrl) {
            this.skipUrl = skipUrl;
            return this;
        }

        public String getSkipUrl() {
            return skipUrl == null ? SKIP_URL : skipUrl;
        }

        public Builder setShowVpaid(Boolean showVpaid) {
            this.showVpaid = showVpaid;
            return this;
        }

        public Builder setShowVideoClicks(Boolean showVideoClicks) {
            this.showVideoClicks = showVideoClicks;
            return this;
        }

        /**
         * Установить специальный шаблон VAST.
         *
         * @param templateName
         */
        public Builder setCustomTemplateName(String templateName) {
            this.templateName = templateName;
            return this;
        }

        public Builder setUseVpaidImpressions(Boolean useVpaidImpressions) {
            this.useVpaidImpressions = useVpaidImpressions;
            return this;
        }

        public Builder setUseTrackingEvents(Boolean useTrackingEvents) {
            this.useTrackingEvents = useTrackingEvents;
            return this;
        }

        public Builder setCustomAdSystem(String adSystem) {
            this.adSystem = adSystem;
            return this;
        }

        public Builder setIsStock(Boolean isStock) {
            this.isStock = isStock;
            return this;
        }

        public Builder setSocialAdvertisement(Boolean socialAdvertisement) {
            this.socialAdvertisement = socialAdvertisement;
            return this;
        }

        public Builder setAuctionRenderUrlMacros(Boolean auctionRenderUrlMacros) {
            this.auctionRenderUrlMacros = auctionRenderUrlMacros;
            return this;
        }

        public Builder setInjectMediaFilesMacros(Boolean injectMediaFilesMacros) {
            this.injectMediaFilesMacros = injectMediaFilesMacros;
            return this;
        }
    }
}
