/*
 * (c) 2020 Pluto Inc.
 */
package tv.pluto.mongo.utils;

import static com.mongodb.client.model.Projections.fields;
import static com.mongodb.client.model.Projections.include;

import com.google.gson.reflect.TypeToken;
import com.mongodb.BasicDBObject;
import com.plutotv.test.lib.LogUtils;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.bson.types.ObjectId;
import tv.pluto.grpc.placeholder.Placeholder;
import tv.pluto.grpc.placeholder.PlaceholderRequest;
import tv.pluto.grpc.placeholder.PlaceholderResponse;
import tv.pluto.mongo.entities.clips.Clips;
import tv.pluto.mongo.entities.placeholder.BumperSet;
import tv.pluto.mongo.entities.placeholder.BumperSets;
import tv.pluto.mongo.entities.placeholder.Clip;
import tv.pluto.mongo.entities.placeholder.Country;

public class PlaceholderUtils extends MongoUtils {
  String defaultRawPlaceholders;

  {
    try {
      defaultRawPlaceholders =
          new String(
              Files.readAllBytes(Paths.get("src/main/resources/default-placeholoders.json")),
              StandardCharsets.UTF_8);
    } catch (IOException e) {
      LogUtils.logSevereMessage(e.getMessage());
    }
  }

  Type placeholdersListType = new TypeToken<ArrayList<Clip>>() {}.getType();

  // This hard coded values are taken from
  // https://github.com/Pluto-tv/inf-lib-dal/blob/master/bll/placeholder.js
  private static final String SUFFIX_PLACEHOLDER = "placeholder";

  private static final String SUFFIX_GENERIC_POST_BUMPER = "post_bumper";
  private static final String SUFFIX_GENERIC_PRE_BUMPER = "pre_bumper";
  private static final String SUFFIX_KIDS_POST_BUMPER = "kids_post_bumper";
  private static final String SUFFIX_KIDS_PRE_BUMPER = "kids_pre_bumper";
  private static final String PREFIX_CHANNEL_BUMPER = "channel_id";
  private static final String SUFFIX_ERROR = "error";
  private static final String SUFFIX_FILLER = "filler";
  private static final String TAG_DIVIDER = "_";

  private static final List<String> BASE_SUFFIXES =
      Arrays.asList(
          SUFFIX_GENERIC_POST_BUMPER, SUFFIX_GENERIC_PRE_BUMPER, SUFFIX_ERROR, SUFFIX_FILLER);
  private static final List<String> SUFFIXES =
      BASE_SUFFIXES.stream()
          .map(suffix -> SUFFIX_PLACEHOLDER + TAG_DIVIDER + suffix)
          .collect(Collectors.toList());

  private static final String DEFAULT_PREFIX = "plutotv";

  private static final String PLACEHOLDER_ERROR_TAG = DEFAULT_PREFIX + TAG_DIVIDER + SUFFIX_ERROR;
  private static final String PLACEHOLDER_FILLER_TAG = DEFAULT_PREFIX + TAG_DIVIDER + SUFFIX_FILLER;
  private static final String GENERIC_PRE_BUMPER_TAG =
      DEFAULT_PREFIX + TAG_DIVIDER + SUFFIX_GENERIC_PRE_BUMPER;
  private static final String GENERIC_POST_BUMPER_TAG =
      DEFAULT_PREFIX + TAG_DIVIDER + SUFFIX_GENERIC_POST_BUMPER;
  private static final String KIDS_PRE_BUMPER_TAG =
      DEFAULT_PREFIX + TAG_DIVIDER + SUFFIX_KIDS_PRE_BUMPER;
  private static final String KIDS_POST_BUMPER_TAG =
      DEFAULT_PREFIX + TAG_DIVIDER + SUFFIX_KIDS_POST_BUMPER;
  private static final String KIDS_VOD_BUMPER_TAG = "kids_vod_bumper";
  private static final String KIDS_DEFAULT_BUMPER_TAG = "kids_default_bumper";
  private static final String IDENT_CARD_BUMPER = "ident_card_bumper";

  private static final List<String> DEFAULT_PLACEHOLDERS =
      Arrays.asList(
          PLACEHOLDER_ERROR_TAG,
          PLACEHOLDER_FILLER_TAG,
          GENERIC_PRE_BUMPER_TAG,
          GENERIC_POST_BUMPER_TAG,
          KIDS_PRE_BUMPER_TAG,
          KIDS_POST_BUMPER_TAG);

  private static final String SOURCE_TYPE_HLS = "HLS";
  private static final String SOURCE_TYPE_DASH = "DASH";
  private static final String BATCH_PREFIX = "batch_";
  private static final String COUNTRY_PREFIX = "country_";

  private static final List<Double> REQUIRED_CLIP_DURATIONS =
      Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 15.0, 20.0, 25.0, 30.0);

  /**
   * Retrieve clips from with a given tag
   *
   * @param tag clip tag
   * @param duration list of clip durations to limit result set to.
   * @return list of clips
   */
  public List<Clip> getClips(String tag, List<Double> duration) {
    List<Clip> result = new ArrayList<>();

    BasicDBObject query =
        new BasicDBObject("tags", new BasicDBObject("$in", Collections.singletonList(tag)))
            .append("contentStatus", "ready")
            .append(
                "sources.type",
                new BasicDBObject("$in", Arrays.asList(SOURCE_TYPE_HLS, SOURCE_TYPE_DASH)));
    if (!duration.isEmpty()) {
      query.append("duration", new BasicDBObject("$in", duration));
    }

    List<Clips> clips = new ArrayList<>();
    collectionClips
        .find(query)
        .projection(
            fields(
                include(
                    "_id",
                    "duration",
                    "name",
                    "url",
                    "contentStatus",
                    "provider",
                    "tags",
                    "origin.url",
                    "sources")))
        .forEach(clips::add);

    clips.forEach(
        clip -> {
          Clip mappedClip = this.mapGrpcObjectToPojo(clip, Clip.class);
          mappedClip.setUrlOrigin(clip.getOrigin().getUrl());
          result.add(mappedClip);
        });

    return result;
  }

  public List<Clip> getClipsFilteredDuration(
      String tag, List<Double> duration, double filteredDuration) {
    List<Clip> result = new ArrayList<>();

    BasicDBObject query =
        new BasicDBObject("tags", new BasicDBObject("$in", Collections.singletonList(tag)))
            .append("contentStatus", "ready")
            .append(
                "sources.type",
                new BasicDBObject("$in", Arrays.asList(SOURCE_TYPE_HLS, SOURCE_TYPE_DASH)));
    if (!duration.isEmpty()) {
      query.append("duration", new BasicDBObject("$in", duration));
    }

    List<Clips> clips = new ArrayList<>();
    collectionClips
        .find(query)
        .projection(
            fields(
                include(
                    "_id",
                    "duration",
                    "name",
                    "url",
                    "contentStatus",
                    "provider",
                    "tags",
                    "origin.url",
                    "sources")))
        .forEach(clips::add);

    clips.forEach(
        clip -> {
          Clip mappedClip = this.mapGrpcObjectToPojo(clip, Clip.class);
          mappedClip.setUrlOrigin(clip.getOrigin().getUrl());

          if (clip.getDuration() >= filteredDuration) result.add(mappedClip);
        });

    return result;
  }

  public List<Clip> getDefaultFillerClips() {
    List<Clip> fillers = gson.fromJson(defaultRawPlaceholders, placeholdersListType);

    return fillers.stream()
        .filter(p -> !p.getTags().isEmpty() && p.getTags().contains("plutotv_filler"))
        .collect(Collectors.toList());
  }

  public Clip getDefaultErrorClip() {
    List<Clip> errors = gson.fromJson(defaultRawPlaceholders, placeholdersListType);

    return errors.stream()
        .filter(p -> !p.getTags().isEmpty() && p.getTags().contains("plutotv_error"))
        .collect(Collectors.toList())
        .get(0);
  }

  /**
   * Get all error placeholder Clips. Error clips need to be 30 second durations.
   *
   * @return list of clips
   */
  public List<Clip> getErrorPlaceholderClips(String tag) {
    return getClips(tag, Collections.singletonList(30.0));
  }

  /**
   * Selects random error clip
   *
   * @return Clips object
   */
  public Clip getErrorPlaceholderClip(String tag) {
    return getRandomItem(getErrorPlaceholderClips(tag));
  }

  public List<Clip> getFillerPlaceholderClips() {
    return getClips(PLACEHOLDER_FILLER_TAG, REQUIRED_CLIP_DURATIONS);
  }

  /**
   * Get a list of unique strings (batchNames) starting with a string (searchString)
   *
   * @param clips list of clips
   * @param searchString string for search
   * @return list of batch names
   */
  public List<String> getClipBatchNames(List<Clip> clips, String searchString) {
    return clips.stream()
        .filter(clip -> clip.getTags().stream().anyMatch(tag -> tag.startsWith(searchString)))
        .map(
            clip ->
                clip.getTags().stream()
                    .filter(tag -> tag.startsWith(searchString))
                    .findFirst()
                    .orElse(null))
        .distinct()
        .toList();
  }

  /**
   * Get a list of unique strings (batchNames) starting with a string (searchString) which include
   * all 14 required clip durations ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30]). If a duration
   * is missing or there are duplicates a 'batch' will be omitted.
   *
   * @param clips list of clips
   * @param searchString string to search
   * @return list of batch names
   */
  public Map<String, List<Clip>> filterValidFillerClipBatchNames(
      List<Clip> clips, String searchString) {
    Map<String, List<Clip>> completeBatches = new HashMap<>();

    List<Double> batchDurations = clips.stream().map(Clip::getDuration).sorted().toList();
    if (batchDurations.equals(REQUIRED_CLIP_DURATIONS)) {
      completeBatches.put(searchString, clips);
    }

    return completeBatches;
  }

  /**
   * Get a random batch from the list of provided batches.
   *
   * @param batches list of batches
   * @return batch
   */
  public List<Clip> getRandomBatch(Map<String, List<Clip>> batches) {
    List<List<Clip>> valuesList = new ArrayList<>(batches.values());
    if (valuesList.isEmpty()) {
      return Collections.emptyList();
    }
    return getRandomItem(valuesList);
  }

  /**
   * Find the first Clip in a list of Clips matching a batchName and duration.
   *
   * @param clips list of clips
   * @param batchName batch name
   * @param duration duration
   * @return list of clips
   */
  public Clip getClipByBatchNameAndDuration(List<Clip> clips, String batchName, int duration) {
    return clips.stream()
        .filter(
            clip ->
                clip.getTags().stream().anyMatch(tag -> tag.equals(batchName))
                    && clip.getDuration() == duration)
        .findFirst()
        .orElse(null);
  }

  public List<Clip> filterBumpersByTagsAndDuration(
      List<Clip> clips, List<String> tags, double duration) {
    return clips.stream()
        // TODO remove round after fix type
        .filter(
            clip -> clip.getTags().containsAll(tags) && Math.round(clip.getDuration()) == duration)
        .collect(Collectors.toList());
  }

  /**
   * Retrieve map of tags representing placeholder sets.
   *
   * @return map with tags
   */
  public Map<String, List<String>> getTags() {
    Map<String, List<String>> deviceTags = new HashMap<>();
    List<Clip> clips = new ArrayList<>();

    List<String> tagsForQuery =
        Stream.concat(SUFFIXES.stream(), DEFAULT_PLACEHOLDERS.stream())
            .collect(Collectors.toList());

    BasicDBObject query =
        new BasicDBObject("tags", new BasicDBObject("$in", tagsForQuery))
            .append("contentStatus", "ready")
            .append(
                "sources.type",
                new BasicDBObject("$in", Arrays.asList(SOURCE_TYPE_HLS, SOURCE_TYPE_DASH)));

    collectionClip.find(query).forEach(clips::add);

    clips.forEach(
        clip ->
            clip.getTags().stream()
                .filter((tag) -> !SUFFIXES.contains(tag))
                .forEach(
                    (tag) -> {
                      if (BASE_SUFFIXES.stream()
                          .anyMatch(suffix -> tag.endsWith(TAG_DIVIDER + suffix))) {
                        String mapKey = tag.split("_")[0];
                        if (!deviceTags.containsKey(mapKey)) {
                          deviceTags.put(mapKey, new ArrayList<>());
                        }
                        if (!deviceTags.get(mapKey).contains(tag)) {
                          deviceTags.get(mapKey).add(tag);
                        }
                      }
                    }));

    return deviceTags;
  }

  /**
   * Get error tag from list of tags.
   *
   * @param tags tags
   * @return filtered list of tags
   */
  public String placeholderErrorTag(List<String> tags) {
    return tags.stream().filter(tag -> tag.endsWith(SUFFIX_ERROR)).findFirst().orElse("");
  }

  /**
   * Get filler tag from list of tags.
   *
   * @param tags tags
   * @return filtered list of tags
   */
  public String placeholderFillerTag(List<String> tags) {
    return tags.stream().filter(tag -> tag.endsWith(SUFFIX_FILLER)).findFirst().orElse("");
  }

  /**
   * Get pre-bumber tag from list of tags.
   *
   * @param tags tags
   * @return filtered list of tags
   */
  public String placeholderGenericPreBumperTag(List<String> tags) {
    return tags.stream()
        .filter(tag -> tag.endsWith(SUFFIX_GENERIC_PRE_BUMPER))
        .findFirst()
        .orElse("");
  }

  /**
   * Get post-bumber tag from list of tags.
   *
   * @param tags tags
   * @return filtered list of tags
   */
  public String placeholderGenericPostBumperTag(List<String> tags) {
    return tags.stream()
        .filter(tag -> tag.endsWith(SUFFIX_GENERIC_POST_BUMPER))
        .findFirst()
        .orElse("");
  }

  /**
   * Get post-bumber tag from list of tags.
   *
   * @param tags tags
   * @return filtered list of tags
   */
  public String placeholderKidsPreBumperTag(List<String> tags) {
    return tags.stream().filter(tag -> tag.endsWith(SUFFIX_KIDS_PRE_BUMPER)).findFirst().orElse("");
  }

  /**
   * Get post-bumber tag from list of tags.
   *
   * @param tags tags
   * @return filtered list of tags
   */
  public String placeholderKidsPostBumperTag(List<String> tags) {
    return tags.stream()
        .filter(tag -> tag.endsWith(SUFFIX_KIDS_POST_BUMPER))
        .findFirst()
        .orElse("");
  }

  /**
   * Return placeholders for different device sets.
   *
   * @param placeholderRequest Placeholder request object
   * @return map with placeholders
   */
  public PlaceholderResponse getPlaceholderSets(PlaceholderRequest placeholderRequest) {
    PlaceholderResponse devicePlaceholders = new PlaceholderResponse();
    Map<String, List<String>> deviceTags = getTags();

    for (Map.Entry<String, List<String>> entry : deviceTags.entrySet()) {
      LogUtils.logInfoMessage(entry.getKey() + "/" + entry.getValue());

      Placeholder placeholder = getPlaceholders(placeholderRequest, entry.getValue());

      // Only add if placeholder has ANY content
      boolean hasContent =
          (placeholder.getFillers() != null && !placeholder.getFillers().isEmpty())
              || placeholder.getError() != null
              || placeholder.getPreBumper() != null
              || placeholder.getPostBumper() != null;

      if (hasContent) {
        devicePlaceholders.getPlaceholders().put(entry.getKey(), placeholder);
      }
    }

    return devicePlaceholders;
  }

  /**
   * Return a placeholders object. Only VALID filler clip batches are considered. Valid filler clips
   * batches have all 14 different required clip durations.
   *
   * @param placeholderRequest placeholderRequest object
   * @return Placeholder object
   */
  public Placeholder getPlaceholders(PlaceholderRequest placeholderRequest, List<String> tags) {
    Placeholder placeholder = new Placeholder();

    if (placeholderRequest.isReturnFillerClips()) {
      List<Clip> allFillers = getClips(placeholderFillerTag(tags), REQUIRED_CLIP_DURATIONS);
      Map<String, List<Clip>> groupedBatches =
          filterValidFillerClipBatchNames(allFillers, BATCH_PREFIX);

      if (groupedBatches.isEmpty()) {
        boolean isDefaultSet = tags.contains("plutotv_filler");

        if (isDefaultSet) {
          LogUtils.logInfoMessage(
              "getPlaceholders - No filler clip batches found.  Using DAL defaults.");
          groupedBatches.put("batch_default", getDefaultFillerClips());
        } else {
          LogUtils.logInfoMessage(
              "getPlaceholders - No valid filler batches found for custom device set - not using fallback");
        }
      }

      if (!groupedBatches.isEmpty()) {
        List<Clip> selectedFillerBatchTag = getRandomBatch(groupedBatches);
        placeholder.setFillers(
            selectedFillerBatchTag.stream()
                .sorted(Comparator.comparingDouble(Clip::getDuration).reversed())
                .collect(Collectors.toList()));
      }
    }

    if (placeholderRequest.isReturnErrorClip()) {
      placeholder.setError(getErrorPlaceholderClip(placeholderErrorTag(tags)));

      if (placeholder.getError() == null) {
        boolean isDefaultSet = tags.contains("plutotv_error");

        if (isDefaultSet) {
          LogUtils.logInfoMessage("getPlaceholders - No error clip found. Using DAL default.");
          placeholder.setError(getDefaultErrorClip());
        } else {
          LogUtils.logInfoMessage(
              "getPlaceholders - No error clip found for custom device set - not using fallback");
        }
      }
    }

    return placeholder;
  }

  public Map<String, BumperSet> getBumperSets(Double duration, List<String> tags) {
    if (duration == null) {
      duration = 5.0;
    }

    if (tags == null || tags.isEmpty()) {
      tags = Arrays.asList(GENERIC_PRE_BUMPER_TAG, GENERIC_POST_BUMPER_TAG);
    }

    List<Clip> allPreBumperClips =
        getClipsFilteredDuration(
            placeholderGenericPreBumperTag(tags), Collections.emptyList(), duration);
    //        List<Clip> allPreBumperClips = getClips(placeholderGenericPreBumperTag(tags),
    // Collections.emptyList());
    // Find all pre bumper batches e.g. country_uk, country_gsa
    List<String> preBumperCountryTags = getClipBatchNames(allPreBumperClips, COUNTRY_PREFIX);

    List<Clip> allPostBumperClips =
        getClipsFilteredDuration(
            placeholderGenericPostBumperTag(tags), Collections.emptyList(), duration);
    //        List<Clip> allPostBumperClips = getClips(placeholderGenericPostBumperTag(tags),
    // Collections.emptyList());
    List<String> postBumperCountryTags = getClipBatchNames(allPostBumperClips, COUNTRY_PREFIX);

    // Find all batches common to pre and post bumpers.
    List<String> bumperCountryTags = new ArrayList<>(preBumperCountryTags);
    bumperCountryTags.retainAll(postBumperCountryTags);
    // 'country_default' has been introduced as a concept for kids mode (complex) bumpers.
    // Legacy consumers of bumpers should not see 'country_default'
    bumperCountryTags =
        bumperCountryTags.stream()
            .filter(tag -> !tag.equalsIgnoreCase("country_default"))
            .collect(Collectors.toList());

    Map<String, BumperSet> countryBumperSets = new HashMap<>();

    if (!bumperCountryTags.isEmpty()) {
      Double finalDuration = duration;
      bumperCountryTags.forEach(
          countryTag -> {
            LogUtils.logInfoMessage("countryTag: " + countryTag);
            Clip preBumper =
                getClipByBatchNameAndDuration(
                    allPreBumperClips, countryTag, finalDuration.intValue());
            Clip postBumper =
                getClipByBatchNameAndDuration(
                    allPostBumperClips, countryTag, finalDuration.intValue());
            if (preBumper != null && postBumper != null) {
              BumperSet bumperSets = new BumperSet();

              bumperSets.setPreBumper(preBumper);
              bumperSets.setPostBumper(postBumper);

              countryBumperSets.put(countryTag, bumperSets);
            }
          });
    }

    return countryBumperSets;
  }

  public Map<String, Country> getComplexBumperSets() {
    Double duration = 5.0;
    List<String> tags =
        Arrays.asList(
            GENERIC_PRE_BUMPER_TAG,
            GENERIC_POST_BUMPER_TAG,
            KIDS_PRE_BUMPER_TAG,
            KIDS_POST_BUMPER_TAG,
            IDENT_CARD_BUMPER);
    Map<String, Country> complexBumperSets = new HashMap<>();

    LogUtils.logInfoMessage("getComplexBumperSets - Get kids bumpers.");
    // plutotv_kids_pre_bumper
    List<Clip> allKidsPreBumperClips =
        getClips(placeholderKidsPreBumperTag(tags), Collections.emptyList());
    // plutotv_pre_bumper
    List<Clip> allGenericPreBumperClips =
        getClips(placeholderGenericPreBumperTag(tags), Collections.emptyList());
    List<Clip> allPreBumperClips =
        Stream.concat(allKidsPreBumperClips.stream(), allGenericPreBumperClips.stream())
            .collect(Collectors.toList());
    List<String> preBumperCountryTags = getClipBatchNames(allPreBumperClips, COUNTRY_PREFIX);

    List<Clip> allKidsPostBumperClips =
        getClips(placeholderKidsPostBumperTag(tags), Collections.emptyList());
    // plutotv_post_bumper
    List<Clip> allGenericPostBumperClips =
        getClips(placeholderGenericPostBumperTag(tags), Collections.emptyList());
    List<Clip> allPostBumperClips =
        Stream.concat(allKidsPostBumperClips.stream(), allGenericPostBumperClips.stream())
            .collect(Collectors.toList());

    List<String> postBumperCountryTags = getClipBatchNames(allPostBumperClips, COUNTRY_PREFIX);

    // Find all batches common to pre and post bumpers.
    List<String> bumperCountryTags = new ArrayList<>(preBumperCountryTags);
    bumperCountryTags.retainAll(postBumperCountryTags);

    if (!bumperCountryTags.isEmpty()) {
      bumperCountryTags.forEach(
          countryTag -> {
            // the country tag as found in the DB response needs to be cleaned to extract just the
            // country code.
            String cleanedCountryTag = cleanCountryTag(countryTag);
            // If the country tag can be cleaned (split by the underscore) use the cleaned version
            // in the constructed `complexBumperSets`; otherwise, skip altogether.
            if (cleanedCountryTag != null) {
              Map<String, BumperSets> countryBumperSets = new HashMap<String, BumperSets>() {};

              complexBumperSets.put(cleanedCountryTag, new Country(countryBumperSets));
              populateDefaultBumpers(
                  complexBumperSets, allPreBumperClips, allPostBumperClips, countryTag, duration);

              // 'country_default' should not have VOD language or channel bumpers... so skip for
              // it.
              if (!countryTag.equalsIgnoreCase("country_default")) {
                populateVODBumpers(
                    complexBumperSets, allPreBumperClips, allPostBumperClips, countryTag, duration);
                populateChannelBumpers(
                    complexBumperSets, allPreBumperClips, allPostBumperClips, countryTag, duration);
              }

              List<String> bumperSetsNames = new ArrayList<>();
              // If there are not both pre and post bumpers for a bumper set, we will remove the
              // bumper set.
              complexBumperSets
                  .get(cleanedCountryTag)
                  .getBumperSets()
                  .forEach(
                      (bumperSetsName, bumperSets) -> {
                        if (!validatePreAndPostExistWithContents(bumperSets)) {
                          bumperSetsNames.add(bumperSetsName);
                        }
                      });

              bumperSetsNames.forEach(
                  name -> complexBumperSets.get(cleanedCountryTag).getBumperSets().remove(name));
            }
          });
    }

    return complexBumperSets;
  }

  public String cleanCountryTag(String countryTag) {
    if (countryTag == null) {
      return null;
    } else {
      String[] splitCountry = countryTag.split("_");

      if (splitCountry.length > 1) {
        return splitCountry[1];
      } else {
        return null;
      }
    }
  }

  public void populateDefaultBumpers(
      Map<String, Country> complexBumperSets,
      List<Clip> allPreBumperClips,
      List<Clip> allPostBumperClips,
      String countryTag,
      Double duration) {

    String cleanedCountryTag = cleanCountryTag(countryTag);

    List<Clip> defaultGenericPreBumpers =
        filterBumpersByTagsAndDuration(
            allPreBumperClips, Arrays.asList(countryTag, GENERIC_PRE_BUMPER_TAG), duration);
    List<Clip> defaultGenericPostBumpers =
        filterBumpersByTagsAndDuration(
            allPostBumperClips, Arrays.asList(countryTag, GENERIC_POST_BUMPER_TAG), duration);

    List<Clip> defaultKidsPreBumpers =
        filterBumpersByTagsAndDuration(
            allPreBumperClips, Arrays.asList(countryTag, KIDS_DEFAULT_BUMPER_TAG), duration);
    List<Clip> defaultKidsPostBumpers =
        filterBumpersByTagsAndDuration(
            allPostBumperClips, Arrays.asList(countryTag, KIDS_DEFAULT_BUMPER_TAG), duration);

    List<Clip> identCardPreBumpers =
        filterBumpersByTagsAndDuration(
            allPreBumperClips, Arrays.asList(countryTag, IDENT_CARD_BUMPER), duration);

    List<Clip> identCardPostBumpers =
        filterBumpersByTagsAndDuration(
            allPostBumperClips, Arrays.asList(countryTag, IDENT_CARD_BUMPER), duration);

    BumperSets defaultBumperSets = new BumperSets();
    BumperSets kidsDefaultBumperSets = new BumperSets();
    BumperSets identCardBumperSets = new BumperSets();

    kidsDefaultBumperSets.setPreBumpers(filterUniqueFromList(defaultKidsPreBumpers));
    kidsDefaultBumperSets.setPostBumpers(filterUniqueFromList(defaultKidsPostBumpers));

    defaultBumperSets.setPreBumpers(filterUniqueFromList(defaultGenericPreBumpers));
    defaultBumperSets.setPostBumpers(filterUniqueFromList(defaultGenericPostBumpers));

    identCardBumperSets.setPreBumpers(filterUniqueFromList(identCardPreBumpers));
    identCardBumperSets.setPostBumpers(filterUniqueFromList(identCardPostBumpers));

    Map<String, BumperSets> countryBumperSets = new HashMap<>();
    countryBumperSets.put("kids_default", kidsDefaultBumperSets);
    countryBumperSets.put("default", defaultBumperSets);
    countryBumperSets.put("ident_card", identCardBumperSets);

    complexBumperSets.get(cleanedCountryTag).setBumperSets(countryBumperSets);
  }

  public <T> List<T> filterUniqueFromList(List<T> list) {
    return list.stream().distinct().collect(Collectors.toList());
  }

  public void populateVODBumpers(
      Map<String, Country> complexBumperSets,
      List<Clip> allPreBumperClips,
      List<Clip> allPostBumperClips,
      String countryTag,
      Double duration) {

    String cleanedCountryTag = cleanCountryTag(countryTag);

    List<Clip> vodPreBumpers =
        filterBumpersByTagsAndDuration(
            allPreBumperClips, Arrays.asList(countryTag, KIDS_VOD_BUMPER_TAG), duration);
    List<Clip> vodPostBumpers =
        filterBumpersByTagsAndDuration(
            allPostBumperClips, Arrays.asList(countryTag, KIDS_VOD_BUMPER_TAG), duration);

    if (!vodPreBumpers.isEmpty() && !vodPostBumpers.isEmpty()) {
      vodPreBumpers.forEach(
          clip -> {
            BasicDBObject query = new BasicDBObject();
            query.put("_id", new ObjectId(clip.getId()));

            String language =
                Objects.requireNonNull(
                        collectionClips.find(query).projection(fields(include("language"))).first())
                    .getLanguage();
            String vodName = "vod:kids:" + language;

            if (language != null
                && !complexBumperSets.get(cleanedCountryTag).getBumperSets().containsKey(vodName)) {
              complexBumperSets
                  .get(cleanedCountryTag)
                  .getBumperSets()
                  .put(vodName, new BumperSets());
            }
            if (complexBumperSets
                    .get(cleanedCountryTag)
                    .getBumperSets()
                    .get(vodName)
                    .getPreBumpers()
                == null) {
              complexBumperSets
                  .get(cleanedCountryTag)
                  .getBumperSets()
                  .get(vodName)
                  .setPreBumpers(new ArrayList<>());
            }

            complexBumperSets
                .get(cleanedCountryTag)
                .getBumperSets()
                .get(vodName)
                .getPreBumpers()
                .add(clip);
          });

      vodPostBumpers.forEach(
          clip -> {
            BasicDBObject query = new BasicDBObject();
            query.put("_id", new ObjectId(clip.getId()));

            String language =
                Objects.requireNonNull(
                        collectionClips.find(query).projection(fields(include("language"))).first())
                    .getLanguage();
            String vodName = "vod:kids:" + language;

            if (language != null
                && !complexBumperSets.get(cleanedCountryTag).getBumperSets().containsKey(vodName)) {
              complexBumperSets
                  .get(cleanedCountryTag)
                  .getBumperSets()
                  .put(vodName, new BumperSets());
            }
            if (complexBumperSets
                    .get(cleanedCountryTag)
                    .getBumperSets()
                    .get(vodName)
                    .getPostBumpers()
                == null) {
              complexBumperSets
                  .get(cleanedCountryTag)
                  .getBumperSets()
                  .get(vodName)
                  .setPostBumpers(new ArrayList<>());
            }

            complexBumperSets
                .get(cleanedCountryTag)
                .getBumperSets()
                .get(vodName)
                .getPostBumpers()
                .add(clip);
          });
    }
  }

  public void populateChannelBumpers(
      Map<String, Country> complexBumperSets,
      List<Clip> allPreBumperClips,
      List<Clip> allPostBumperClips,
      String countryTag,
      Double duration) {

    String cleanedCountryTag = cleanCountryTag(countryTag);

    List<Clip> channelPreBumpers =
        filterBumpersByTagsAndDuration(
                allPreBumperClips, Collections.singletonList(countryTag), duration)
            .stream()
            .filter(
                clip ->
                    clip.getTags().stream().anyMatch(tag -> tag.contains(PREFIX_CHANNEL_BUMPER)))
            .collect(Collectors.toList());

    List<Clip> channelPostBumpers =
        filterBumpersByTagsAndDuration(
                allPostBumperClips, Collections.singletonList(countryTag), duration)
            .stream()
            .filter(
                clip ->
                    clip.getTags().stream().anyMatch(tag -> tag.contains(PREFIX_CHANNEL_BUMPER)))
            .collect(Collectors.toList());

    if (!channelPreBumpers.isEmpty() && !channelPostBumpers.isEmpty()) {
      channelPreBumpers.forEach(
          clip -> {
            List<String> channelTags =
                clip.getTags().stream()
                    .filter(tag -> tag.contains(PREFIX_CHANNEL_BUMPER))
                    .collect(Collectors.toList());

            channelTags.forEach(
                tag -> {
                  String[] splitTag = tag.split("_");
                  // 3rd item in array should be the channel id and should have a value
                  if (splitTag.length > 2 && !splitTag[2].isEmpty()) {
                    String customChannelName = "channel:" + tag.split("_")[2];

                    BasicDBObject query = new BasicDBObject();
                    query.put("_id", new ObjectId(clip.getId()));

                    String language =
                        Objects.requireNonNull(
                                collectionClips
                                    .find(query)
                                    .projection(fields(include("language")))
                                    .first())
                            .getLanguage();

                    if (language != null
                        && !complexBumperSets
                            .get(cleanedCountryTag)
                            .getBumperSets()
                            .containsKey(customChannelName)) {
                      complexBumperSets
                          .get(cleanedCountryTag)
                          .getBumperSets()
                          .put(customChannelName, new BumperSets());
                    }
                    if (complexBumperSets
                            .get(cleanedCountryTag)
                            .getBumperSets()
                            .get(customChannelName)
                            .getPreBumpers()
                        == null) {
                      complexBumperSets
                          .get(cleanedCountryTag)
                          .getBumperSets()
                          .get(customChannelName)
                          .setPreBumpers(new ArrayList<>());
                    }

                    complexBumperSets
                        .get(cleanedCountryTag)
                        .getBumperSets()
                        .get(customChannelName)
                        .getPreBumpers()
                        .add(clip);
                  }
                });
          });

      channelPostBumpers.forEach(
          clip -> {
            List<String> channelTags =
                clip.getTags().stream()
                    .filter(tag -> tag.contains(PREFIX_CHANNEL_BUMPER))
                    .collect(Collectors.toList());

            channelTags.forEach(
                tag -> {
                  String[] splitTag = tag.split("_");
                  // 3rd item in array should be the channel id and should have a value
                  if (splitTag.length > 2 && !splitTag[2].isEmpty()) {
                    String customChannelName = "channel:" + tag.split("_")[2];

                    BasicDBObject query = new BasicDBObject();
                    query.put("_id", new ObjectId(clip.getId()));

                    String language =
                        Objects.requireNonNull(
                                collectionClips
                                    .find(query)
                                    .projection(fields(include("language")))
                                    .first())
                            .getLanguage();

                    if (language != null
                        && !complexBumperSets
                            .get(cleanedCountryTag)
                            .getBumperSets()
                            .containsKey(customChannelName)) {
                      complexBumperSets
                          .get(cleanedCountryTag)
                          .getBumperSets()
                          .put(customChannelName, new BumperSets());
                    }
                    if (complexBumperSets
                            .get(cleanedCountryTag)
                            .getBumperSets()
                            .get(customChannelName)
                            .getPostBumpers()
                        == null) {
                      complexBumperSets
                          .get(cleanedCountryTag)
                          .getBumperSets()
                          .get(customChannelName)
                          .setPostBumpers(new ArrayList<>());
                    }

                    complexBumperSets
                        .get(cleanedCountryTag)
                        .getBumperSets()
                        .get(customChannelName)
                        .getPostBumpers()
                        .add(clip);
                  }
                });
          });
    }
  }

  public boolean validatePreAndPostExistWithContents(BumperSets bumperSets) {
    return bumperSets.getPreBumpers() != null
        && !bumperSets.getPreBumpers().isEmpty()
        && bumperSets.getPostBumpers() != null
        && !bumperSets.getPostBumpers().isEmpty();
  }
}
