/*
 * (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 com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import net.datafaker.Faker;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import tv.pluto.mongo.entities.common.ObjectIdContentIdSet;
import tv.pluto.mongo.entities.series.Series;

public class SeriesUtils extends MongoUtils {

  public SeriesUtils() {
    super(new Series());
    this.setContentFeedsDates();
  }

  /**
   * Get list of random series ids
   *
   * @return list of ids
   */
  public List<String> getRandomSeriesIds() {
    return getDocuments(MongoUtils.LIMIT).stream()
        .map(series -> ((Series) series).getId())
        .collect(Collectors.toList());
  }

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

    BasicDBObject episodeCount = new BasicDBObject("episodeCount", new BasicDBObject("$gte", 3));

    BasicDBObject ageRangeSize =
        new BasicDBObject(
            "episodes.ageRange",
            new BasicDBObject("$exists", true).append("$not", new BasicDBObject("$size", 0)));

    List<Bson> aggregateQueryList =
        Arrays.asList(
            Aggregates.lookup("episodes", "_id", "series", "episodes"),
            Aggregates.match(episodeCount),
            Aggregates.match(ageRangeSize),
            Aggregates.limit(LIMIT),
            Aggregates.project(fields(include("_id"))),
            Aggregates.sort(new BasicDBObject("updatedAt", 1)));

    genericCollection
        .aggregate(aggregateQueryList)
        .forEach(series -> result.add(((Series) series).getId()));

    return result;
  }

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

    BasicDBObject episodeCount = new BasicDBObject("episodeCount", new BasicDBObject("$gte", 3));

    BasicDBObject ageRangeSize =
        new BasicDBObject("episodes.ageRange", new BasicDBObject("$exists", false));

    List<Bson> aggregateQueryList =
        Arrays.asList(
            Aggregates.lookup("episodes", "_id", "series", "episodes"),
            Aggregates.match(episodeCount),
            Aggregates.match(ageRangeSize),
            Aggregates.limit(LIMIT),
            Aggregates.project(fields(include("_id"))),
            Aggregates.sort(new BasicDBObject("updatedAt", 1)));

    genericCollection
        .aggregate(aggregateQueryList)
        .forEach(series -> result.add(((Series) series).getId()));

    return result;
  }

  // TODO use generics
  public List<Series> getSeriesWithUUID(int limit) {
    List<Series> result = new ArrayList<>();

    BasicDBObject query = new BasicDBObject("contentUUID", new BasicDBObject("$exists", true));

    genericCollection.find(query).limit(limit).forEach(series -> result.add((Series) series));

    return result;
  }

  // TODO use generics
  public List<Series> getSeriesWithoutUUID(int limit) {
    List<Series> result = new ArrayList<>();

    BasicDBObject query = new BasicDBObject("contentUUID", new BasicDBObject("$exists", false));

    genericCollection.find(query).limit(limit).forEach(series -> result.add((Series) series));

    return result;
  }

  public List<ObjectIdContentIdSet> extractIdsAndUUID(List<Series> series) {
    List<ObjectIdContentIdSet> result = new ArrayList<>();

    Faker faker = new Faker();

    if (series != null && !series.isEmpty()) {
      series.forEach(
          e ->
              result.add(
                  ObjectIdContentIdSet.builder()
                      .id(e.getId())
                      .contentUUID(faker.internet().uuid())
                      .build()));
    }

    return result;
  }

  public Series getRandomSeriesWithNameActiveRegionAndCustomReferences() {
    Bson filter =
        Filters.and(
            Filters.ne("name", null),
            Filters.ne("activeRegion", null),
            Filters.ne("customReferences", null));

    return (Series) genericCollection.find(filter).limit(1).first();
  }

  public List<Series> getSeriesByChannelId(ObjectId channelId) {
    BasicDBObject query = new BasicDBObject("channels", channelId);

    List<Series> result = new ArrayList<>();

    genericCollection
        .find(query)
        .forEach(
            series -> {
              result.add((Series) series);
            });

    return result;
  }

  public List<String> extractIdsFromSeries(List<Series> series) {
    return series.stream().map(Series::getId).toList();
  }

  public Series getPublishedSeriesByIdAndChannel(ObjectId seriesId, ObjectId channelId) {

    List<Series> seriesList = getSeriesByChannelId(channelId);

    return seriesList.stream()
        .filter(series -> series.getId().equals(seriesId.toString()) && series.getPublished())
        .findFirst()
        .orElse(null);
  }

  public Series getPublishedSeriesWithRatingAndActiveRegion() {
    BasicDBObject query = new BasicDBObject();
    query.put("published", true);
    query.put("rating", new BasicDBObject("$exists", true));
    query.put("activeRegion", new BasicDBObject("$exists", true));

    BasicDBObject projection = new BasicDBObject();
    projection.put("_id", 1);
    projection.put("name", 1);
    projection.put("rating", 1);
    projection.put("activeRegion", 1);

    return (Series) genericCollection.find(query).projection(projection).limit(1).first();
  }

  public Series getPublishedSeriesWithNotRatedAndActiveRegion() {
    // Use aggregation to find series with "Not Rated" that has episodes with clips
    List<Bson> aggregateQueryList =
        Arrays.asList(
            Aggregates.match(
                Filters.and(
                    Filters.eq("published", true),
                    Filters.eq("rating", "Not Rated"),
                    Filters.exists("activeRegion", true),
                    Filters.gt("episodeCount", 0))),
            Aggregates.lookup("episodes", "_id", "series", "episodes"),
            Aggregates.match(
                Filters.and(
                    Filters.exists("episodes.sources", true),
                    Filters.ne("episodes.sources", new ArrayList<>()))),
            Aggregates.limit(1),
            Aggregates.project(
                fields(include("_id", "name", "rating", "activeRegion", "episodeCount"))));

    return (Series) genericCollection.aggregate(aggregateQueryList).first();
  }

  public Series getPublishedSeriesWithRestrictiveClipsAndActiveRegion() {
    BasicDBObject query = new BasicDBObject();
    query.put("published", true);
    query.put("rating", new BasicDBObject("$in", Arrays.asList("TV-MA", "R", "NC-17")));
    query.put("activeRegion", new BasicDBObject("$exists", true));
    query.put("episodeCount", new BasicDBObject("$gt", 0));

    BasicDBObject projection = new BasicDBObject();
    projection.put("_id", 1);
    projection.put("name", 1);
    projection.put("rating", 1);
    projection.put("activeRegion", 1);

    return (Series) genericCollection.find(query).projection(projection).limit(1).first();
  }
}
