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

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.plutotv.test.lib.LogUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import tv.pluto.mongo.entities.labpartner.Episode;
import tv.pluto.mongo.utils.ClipsUtils;
import tv.pluto.mongo.utils.MongoUtils;
import tv.pluto.mongo.utils.SeriesUtils;

public class LabPartnerEpisodeUtils extends MongoUtils {
  private final SeriesUtils seriesUtils = new SeriesUtils();
  private final ClipsUtils clipsUtils = new ClipsUtils();

  public LabPartnerEpisodeUtils() {
    super("episodes", new Episode());
  }

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

    String seriesId = getRandomItem(seriesUtils.getIdsOfSeriesWithMultipleEpisodesAndAgeRange());

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

    BasicDBObject query =
        new BasicDBObject("series", new ObjectId(seriesId)).append("ageRange", ageRangeSize);

    genericCollection
        .find(query)
        .sort(new BasicDBObject("updatedAt", 1))
        .forEach(e -> result.add(((Episode) e).getId()));

    return result;
  }

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

    BasicDBObject size = new BasicDBObject("$exists", true).append("$size", 0);

    BasicDBObject ageRangeSize = new BasicDBObject("ageRange", size);

    BasicDBObject seriesSize = new BasicDBObject("series_obj", new BasicDBObject("$size", 1));

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

    genericCollection.aggregate(aggregateQueryList).forEach(e -> result.add(((Episode) e).getId()));

    return result;
  }

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

    String seriesId =
        getRandomItem(seriesUtils.getIdsOfSeriesWithMultipleEpisodesWithoutAgeRange());

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

    BasicDBObject query =
        new BasicDBObject("series", new ObjectId(seriesId)).append("ageRange", ageRangeSize);

    genericCollection
        .find(query)
        .sort(new BasicDBObject("updatedAt", 1))
        .forEach(e -> result.add(((Episode) e).getId()));

    return result;
  }

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

    List<String> seriesIds = seriesUtils.getIdsOfSeriesWithMultipleEpisodesAndAgeRange();

    BasicDBObject query =
        new BasicDBObject("series", new BasicDBObject("$in", transformStringsToIds(seriesIds)));

    genericCollection
        .find(query)
        .sort(new BasicDBObject("updatedAt", 1))
        .forEach(e -> result.add(((Episode) e).getId()));

    return result;
  }

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

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

    BasicDBObject rdeRangeSize = new BasicDBObject("series_obj.ratingDescriptors", size);

    BasicDBObject seriesSize = new BasicDBObject("series_obj", new BasicDBObject("$size", 1));

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

    genericCollection.aggregate(aggregateQueryList).forEach(e -> result.add(((Episode) e).getId()));

    return result;
  }

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

    BasicDBObject size = new BasicDBObject("$exists", false);

    BasicDBObject rdeRangeSize = new BasicDBObject("series_obj.ratingDescriptors", size);

    BasicDBObject seriesSize = new BasicDBObject("series_obj", new BasicDBObject("$size", 1));

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

    genericCollection.aggregate(aggregateQueryList).forEach(e -> result.add(((Episode) e).getId()));

    return result;
  }

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

    BasicDBObject size = new BasicDBObject("$exists", true).append("$size", 0);

    BasicDBObject rdeRangeSize = new BasicDBObject("series_obj.ratingDescriptors", size);

    BasicDBObject seriesSize = new BasicDBObject("series_obj", new BasicDBObject("$size", 1));

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

    genericCollection.aggregate(aggregateQueryList).forEach(e -> result.add(((Episode) e).getId()));

    return result;
  }

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

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

    BasicDBObject rdeRangeSize = new BasicDBObject("ratingDescriptors", size);

    BasicDBObject seriesSize = new BasicDBObject("series_obj", new BasicDBObject("$size", 1));

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

    genericCollection.aggregate(aggregateQueryList).forEach(e -> result.add(((Episode) e).getId()));

    return result;
  }

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

    BasicDBObject size = new BasicDBObject("$exists", false);

    BasicDBObject rdeRangeSize = new BasicDBObject("ratingDescriptors", size);

    BasicDBObject seriesSize = new BasicDBObject("series_obj", new BasicDBObject("$size", 1));

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

    genericCollection.aggregate(aggregateQueryList).forEach(e -> result.add(((Episode) e).getId()));

    return result;
  }

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

    BasicDBObject size = new BasicDBObject("$exists", true).append("$size", 0);

    BasicDBObject rdeRangeSize = new BasicDBObject("ratingDescriptors", size);

    BasicDBObject seriesSize = new BasicDBObject("series_obj", new BasicDBObject("$size", 1));

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

    genericCollection.aggregate(aggregateQueryList).forEach(e -> result.add(((Episode) e).getId()));

    return result;
  }

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

    List<String> clipIds = clipsUtils.getClipsIdsWithRd();
    LogUtils.logInfoMessage("Size of clipIds: " + clipIds.size());

    for (String clipId : clipIds) {
      BasicDBObject elemMatch =
          new BasicDBObject("$elemMatch", new BasicDBObject("clip", new ObjectId(clipId)));
      BasicDBObject query = new BasicDBObject("sources", elemMatch);
      Episode episode = (Episode) genericCollection.find(query).first();

      if (episode != null && episode.getSeries() != null) {
        result.add(episode.getId());
        break;
      }
    }

    return result;
  }

  // TODO optimize
  public List<String> getIdsOfEpisodesWithSingleSeriesWithoutRdClips() {
    List<String> result = new ArrayList<>();

    List<String> clipIds = clipsUtils.getClipsIdsWithoutRd();

    for (String clipId : clipIds) {
      BasicDBObject elemMatch =
          new BasicDBObject("$elemMatch", new BasicDBObject("clip", new ObjectId(clipId)));
      BasicDBObject query = new BasicDBObject("sources", elemMatch);
      Episode episode = (Episode) genericCollection.find(query).first();

      if (episode != null && episode.getSeries() != null) {
        result.add(episode.getId());
        break;
      }
    }

    return result;
  }

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

    List<String> clipIds = clipsUtils.getClipsIdsWithRdZero();

    for (String clipId : clipIds) {
      BasicDBObject elemMatch =
          new BasicDBObject("$elemMatch", new BasicDBObject("clip", new ObjectId(clipId)));
      BasicDBObject query = new BasicDBObject("sources", elemMatch);
      Episode episode = (Episode) genericCollection.find(query).first();

      if (episode != null && episode.getSeries() != null) {
        result.add(episode.getId());
        break;
      }
    }

    return result;
  }
}
