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

import static tv.pluto.core.MongoDbManager.getDatabase;

import com.mongodb.BasicDBObject;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.Accumulators;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Projections;
import com.mongodb.client.model.UnwindOptions;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import tv.pluto.mongo.entities.vodcategoryentries.VodCategoryEntries;

public class VodCategoryEntriesUtils extends MongoUtils {

  private MongoCollection<Document> vodCategoryEntries;

  public VodCategoryEntriesUtils() {
    this.genericCollection = this.collectionVodCategoriesEntries;
    this.vodCategoryEntries = getDatabase().getCollection("vodcategoryentries", Document.class);
    this.setContentFeedsDates();
  }

  public VodCategoryEntries getVodCategoryEntry() {
    return getRandomItem(getVodCategoryEntries(10));
  }

  public List<VodCategoryEntries> getVodCategoryEntries(int limit) {
    List<VodCategoryEntries> result = new ArrayList<>();
    genericCollection
        .find()
        .limit(limit)
        .forEach(entries -> result.add((VodCategoryEntries) entries));
    return result;
  }

  public List<VodCategoryEntries> getAllVodCategoryEntriesWithinUpdatedDate(LocalDateTime time) {
    List<VodCategoryEntries> result = new ArrayList<>();
    collectionVodCategoriesEntries
        .find()
        .forEach(
            vodCategoryEntries -> {
              if (vodCategoryEntries.getUpdatedAt().equals(time)
                  || vodCategoryEntries.getUpdatedAt().isAfter(time))
                result.add(vodCategoryEntries);
            });
    return result;
  }

  public List<VodCategoryEntries> getVodCategoryEntriesBySeriesOrEpisodeIds(
      List<String> seriesIds, List<String> episodesIds, String[] projectionFields) {
    List<VodCategoryEntries> result = new ArrayList<>();

    List<BasicDBObject> mongoOrList =
        Arrays.asList(
            new BasicDBObject("series", new BasicDBObject("$in", transformStringsToIds(seriesIds))),
            new BasicDBObject(
                "episode", new BasicDBObject("$in", transformStringsToIds(episodesIds))));

    BasicDBObject query = new BasicDBObject("$or", mongoOrList);

    if (projectionFields != null) {
      BasicDBObject projection = new BasicDBObject();
      for (String field : projectionFields) {
        projection.put(field, 1);
      }
      genericCollection
          .find(query)
          .projection(projection)
          .forEach(entries -> result.add((VodCategoryEntries) entries));
    } else {
      genericCollection.find(query).forEach(entries -> result.add((VodCategoryEntries) entries));
    }

    return result;
  }

  public List<String> getVodCategoryEntriesWithSeries() {
    List<String> result = new ArrayList<>();

    MongoCursor<ObjectId> iterator =
        genericCollection
            .distinct("series", ObjectId.class)
            .filter(Filters.ne("series", null))
            .iterator();

    while (result.size() < LIMIT) {
      result.add(iterator.next().toString());
    }

    return result;
  }

  public List<String> getVodCategoryEntriesWithEpisodes() {
    List<String> result = new ArrayList<>();

    MongoCursor<ObjectId> iterator =
        genericCollection
            .distinct("episode", ObjectId.class)
            .filter(Filters.ne("episode", null))
            .iterator();

    while (result.size() < LIMIT) {
      result.add(iterator.next().toString());
    }

    return result;
  }

  public int getSumEpisodesFromVodCategory(String vodCategoryId) {
    String durationSum = "durationsSum";

    List<Bson> pipeline =
        Arrays.asList(
            Aggregates.match(Filters.eq("vodCategory", new ObjectId(vodCategoryId))),
            Aggregates.lookup("episodes", "episode", "_id", "episodeDetails"),
            Aggregates.unwind(
                "$episodeDetails", new UnwindOptions().preserveNullAndEmptyArrays(false)),
            Aggregates.group(null, Accumulators.sum(durationSum, "$episodeDetails.duration")),
            Aggregates.project(
                Projections.fields(Projections.excludeId(), Projections.include(durationSum))));

    return Objects.requireNonNullElse(
            vodCategoryEntries.aggregate(pipeline).first(), new Document(durationSum, 0))
        .getInteger(durationSum);
  }

  public int getSumEpisodesFromVodCategorySeries(String vodCategoryId) {
    String durationSum = "durationsSum";

    List<Bson> pipeline =
        Arrays.asList(
            Aggregates.match(Filters.eq("vodCategory", new ObjectId(vodCategoryId))),
            Aggregates.lookup("series", "series", "_id", "seriesDetails"),
            Aggregates.unwind(
                "$seriesDetails", new UnwindOptions().preserveNullAndEmptyArrays(false)),
            Aggregates.group(null, Accumulators.addToSet("seriesIds", "$seriesDetails._id")),
            Aggregates.unwind("$seriesIds", new UnwindOptions().preserveNullAndEmptyArrays(false)),
            Aggregates.lookup("episodes", "seriesIds", "series", "relatedEpisodes"),
            Aggregates.unwind(
                "$relatedEpisodes", new UnwindOptions().preserveNullAndEmptyArrays(false)),
            Aggregates.group(
                null,
                Accumulators.sum(
                    durationSum,
                    new Document("$ifNull", Arrays.asList("$relatedEpisodes.duration", 0)))),
            Aggregates.project(
                Projections.fields(Projections.excludeId(), Projections.include(durationSum))));

    return Objects.requireNonNullElse(
            vodCategoryEntries.aggregate(pipeline).first(), new Document(durationSum, 0))
        .getInteger(durationSum);
  }
}
