/*
 * (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.mongodb.BasicDBObject;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
import java.util.stream.Collectors;
import org.bson.types.ObjectId;
import tv.pluto.mongo.entities.clips.Clips;
import tv.pluto.mongo.entities.comscore.ComScore;
import tv.pluto.mongo.entities.comscore.ComScoreMetadata;
import tv.pluto.mongo.entities.comscore.ComScoreStreamingTag;
import tv.pluto.mongo.entities.partners.Partners;

public class ComScoreUtils extends MongoUtils {

  PartnersUtils partnersUtils = new PartnersUtils();
  ClipsUtils clipsUtils = new ClipsUtils();

  /**
   * Get id of the clip that has partner with comScore
   *
   * @return clip
   */
  public String getIdOfClipWithComScore() {
    BasicDBObject query = new BasicDBObject();
    String partnerWithComScoreId = partnersUtils.getPartnerWithComScore().getId();
    query.put("partner", new ObjectId(partnerWithComScoreId));
    return Objects.requireNonNull(collectionClips.find(query).first()).getId();
  }

  /**
   * Get ids of the clips that has partner with comScore
   *
   * @return clip
   */
  public List<String> getIdsOfClipWithComScore() {
    List<String> result = new ArrayList<>();
    List<ObjectId> partnersIds = partnersUtils.getIdsPartnersWithComScore();
    BasicDBObject query = new BasicDBObject();

    query.put("partner", new BasicDBObject("$in", partnersIds));
    collectionClips
        .find(query)
        .projection(fields(include("_id")))
        .limit(MongoUtils.LIMIT)
        .forEach(clip -> result.add(clip.getId()));

    return result;
  }

  /**
   * Get id of the clip that has partner without comScore
   *
   * @return clip
   */
  public String getIdOfClipWithoutComScore() {
    BasicDBObject query = new BasicDBObject();
    List<Partners> partnerWithoutComScore = partnersUtils.getPartnersWithoutComScore();
    ObjectId partnerWithoutComScoreId = new ObjectId();
    Clips clip;

    for (Partners partner : partnerWithoutComScore) {
      query.put("partner", new ObjectId(partner.getId()));

      clip = collectionClips.find(query).projection(fields(include("partner"))).first();

      if (clip != null) {
        partnerWithoutComScoreId = new ObjectId(partner.getId());
        break;
      }
    }

    query.put("partner", partnerWithoutComScoreId);
    return Objects.requireNonNull(collectionClips.find(query).first()).getId();
  }

  /**
   * Get id of the clip that has no partner
   *
   * @return id of the clip
   */
  public String getIdOfClipWithoutPartner() {
    BasicDBObject query = new BasicDBObject();
    query.append("partner", new BasicDBObject("$exists", false));
    return Objects.requireNonNull(collectionClips.find(query).first()).getId();
  }

  public ComScore getComScoreForClip(String clipId, String playbackType) {
    Clips clip = clipsUtils.getDocumentById(clipId);

    return createComScoreData(clip, playbackType, null);
  }

  public ComScore getComScoreForClip(String clipId, String playbackType, String episodeGenre) {
    Clips clip = clipsUtils.getDocumentById(clipId);

    return createComScoreData(clip, playbackType, episodeGenre);
  }

  public Map<String, ComScore> getComScoreForClips(List<String> clipIds, String playbackType) {
    List<Clips> clips = clipsUtils.getDocumentsByIds(clipIds);

    Map<String, ComScore> result = new HashMap<>();

    for (Clips clip : clips) {
      result.put(clip.getId(), getComScoreForClip(clip.getId(), playbackType));
    }

    return result;
  }

  /**
   * Creates an object representing the comScore data generated from the clip
   *
   * @param clip Clips object
   * @return ComScore object
   */
  public ComScore createComScoreData(Clips clip, String playbackType, String episodeGenre) {
    ComScore result = new ComScore();

    ComScoreStreamingTag comScoreStreamingTag = new ComScoreStreamingTag();
    ComScoreMetadata comScoreMetadata = new ComScoreMetadata();

    String nsStDdt =
        clip.getOriginalReleaseDate() == null
            ? toComScoreDate(clip.getCreatedAt())
            : toComScoreDate(clip.getOriginalReleaseDate());
    String partnerId = clip.getPartner();
    Partners partner = null;

    if (!partnerId.isEmpty()) {
      partner = partnersUtils.getDocumentById(partnerId);
    }

    // Ensure we have hydrated the clip partner with the partner object.
    if (partner != null
        && partner.getComScore() != null
        && partner.getComScore().getC2() != null
        && !partner.getComScore().getC2().isEmpty()) {
      comScoreStreamingTag.setCustomerC2(partner.getComScore().getC2());

      comScoreMetadata.setNsStCi(clip.getId());
      comScoreMetadata.setNsStPr(clip.getName());
      comScoreMetadata.setNsStCl((int) Math.round(clip.getDuration() * 1000));
      // Parameters existing in inf-lib-schemas Comscore has communicated are no longer needed.
      // We will not remove them now to protect any clients who may need them given their code.
      // TODO will not be used. Will be removed later.
      //            comScoreMetadata.setNsStDdt(nsStDdt);
      //            comScoreMetadata.setNsStTdt(nsStDdt);
      comScoreMetadata.setNsStGe(
          clip.getGenre() != null && !clip.getGenre().isEmpty()
              ? clip.getGenre()
              : (episodeGenre != null && !episodeGenre.isEmpty() ? episodeGenre : "*null"));
      comScoreMetadata.setC3(
          partner.getComScore().getC3().isEmpty() ? "*null" : partner.getComScore().getC3());
      comScoreMetadata.setC4(
          partner.getComScore().getC4().isEmpty() ? "*null" : partner.getComScore().getC4());
      comScoreMetadata.setC6(
          partner.getComScore().getC6().isEmpty() ? "*null" : partner.getComScore().getC6());
    } else {
      comScoreStreamingTag.setCustomerC2("21027837");

      comScoreMetadata.setNsStCi(clip.getId());
      comScoreMetadata.setNsStPr(clip.getName());
      comScoreMetadata.setNsStCl((int) Math.round(clip.getDuration() * 1000));

      String nsStGe = "*null";
      if (!clip.getGenre().isEmpty()) {
        nsStGe = clip.getGenre();
      } else if (episodeGenre != null) {
        nsStGe = episodeGenre;
      }

      comScoreMetadata.setNsStGe(nsStGe); // new implementation only wants the genre
      comScoreMetadata.setC3(nsStGe); // new implementation only wants the genre
      comScoreMetadata.setC4("*null");
      comScoreMetadata.setC6(generateS6(playbackType));
    }

    // Ensure all fields are consistently set to "*null" if empty.
    comScoreMetadata.setNsStTi("*null");
    comScoreMetadata.setNsStIa("*null");
    comScoreMetadata.setNsStCe("*null");

    if (comScoreMetadata.getC3() == null || comScoreMetadata.getC3().isEmpty()) {
      comScoreMetadata.setC3("*null");
    }
    if (comScoreMetadata.getNsStGe() == null || comScoreMetadata.getNsStGe().isEmpty()) {
      comScoreMetadata.setNsStGe("*null");
    }

    result.setStreamingTag(comScoreStreamingTag);
    result.setMetadata(comScoreMetadata);

    return result;
  }

  /**
   * Return date formatted for comScore
   *
   * @param date date to parse
   * @return String with date
   */
  public String toComScoreDate(Date date) {
    if (date != null) {
      SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
      format.setTimeZone(TimeZone.getTimeZone("UTC"));
      return format.format(date);
    } else {
      return "";
    }
  }

  /**
   * Creates comScore affinity group based on clip category, genre and subgenre information.
   *
   * @param clip Clips object
   * @return String
   */
  public String toComScoreAffinityGroup(Clips clip) {
    List<String> list = new ArrayList<>();
    list.add(clip.getCategory());
    list.add(clip.getGenre());
    list.add(clip.getSubGenre());
    // TODO bug https://plutotv.atlassian.net/browse/ARCH-1799
    list.add(clip.getMeta().getCategory());

    list = list.stream().filter(i -> !i.isEmpty()).collect(Collectors.toList());

    if (list.size() > 0) {
      return String.join(",", list);
    } else {
      return "*null";
    }
  }

  private String generateS6(String playbackType) {
    String c6 = "*null";

    if (playbackType != null && !playbackType.isEmpty()) {
      if (playbackType.equalsIgnoreCase("linear")) {
        c6 = "Linear";
      }
      if (playbackType.equalsIgnoreCase("vod")) {
        c6 = "Vod";
      }
    }

    return c6;
  }
}
