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

import static tv.pluto.ip.dal.series.SeriesOuterClass.GetAllPagedSort;
import static tv.pluto.ip.dal.series.SeriesOuterClass.GetByIdsRequest;

import com.mongodb.BasicDBObject;
import com.plutotv.test.lib.LogUtils;
import com.plutotv.test.lib.ttfb.grpc.GrpcCall;
import com.plutotv.test.lib.ttfb.grpc.TTFBGrpcValidator;
import io.grpc.ManagedChannel;
import io.grpc.StatusRuntimeException;
import io.qameta.allure.Description;
import io.qameta.allure.Issue;
import io.qameta.allure.TmsLink;
import java.lang.reflect.InvocationTargetException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.assertj.core.api.Assertions;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.testng.asserts.SoftAssert;
import tv.pluto.core.BaseTest;
import tv.pluto.grpc.series.SeriesClient;
import tv.pluto.ip.dal.series.SeriesOuterClass;
import tv.pluto.mongo.entities.clips.Clips;
import tv.pluto.mongo.entities.common.Image;
import tv.pluto.mongo.entities.common.ObjectIdContentIdSet;
import tv.pluto.mongo.entities.episodes.Episodes;
import tv.pluto.mongo.entities.episodes.Source;
import tv.pluto.mongo.entities.ratingdescriptors.RatingDescriptors;
import tv.pluto.mongo.entities.series.Series;
import tv.pluto.mongo.utils.ClipsUtils;
import tv.pluto.mongo.utils.DocumentUtils;
import tv.pluto.mongo.utils.MongoUtils;
import tv.pluto.mongo.utils.RatingDescriptorsUtils;
import tv.pluto.mongo.utils.SeriesUtils;
import tv.pluto.mongo.utils.episodes.EpisodeUtils;
import tv.pluto.utils.CommonUtils;

public class SeriesTestSuite extends BaseTest {
  private static final String CREATE_ERROR =
      "INTERNAL: Series validation failed: metadataLanguage: metadataLanguage is required., "
          + "description: description is required., summary: summary is required., subGenre: subGenre is required., "
          + "genre: genre is required., subCategory: subCategory is required., category: category is required., "
          + "type: type is required., tags: must be at least 1 tag., seasons: must be at least 1 season";
  private SeriesClient seriesClient;
  private SeriesClient seriesClientAuth;
  private SeriesUtils seriesUtils;
  private EpisodeUtils episodeUtils;
  private ClipsUtils clipsUtils;
  private RatingDescriptorsUtils ratingDescriptorsUtils;

  @BeforeClass(alwaysRun = true)
  private void setUp() {
    seriesClient = new SeriesClient(grpcChannelManager.getChannel(), false);
    seriesClientAuth = new SeriesClient(grpcChannelManager.getChannel(), true);
    seriesUtils = new SeriesUtils();
    episodeUtils = new EpisodeUtils();
    clipsUtils = new ClipsUtils();
    ratingDescriptorsUtils = new RatingDescriptorsUtils();
  }

  @Test
  @Description("GetAllPaged - verify Series")
  @TmsLink("28501393")
  public void C28501393_TEST() {
    int limit = 5;
    int offset = 0;
    SeriesOuterClass.GetAllPagedResponse getAllPaged =
        seriesClient.getAllPaged(limit, offset, null, null, true);

    Assert.assertTrue(limit >= getAllPaged.getMetadata().getTotalItems());
    Assert.assertEquals(getAllPaged.getMetadata().getCurrentOffset(), offset);

    List<String> listSeriesIds =
        getAllPaged.getDocumentsList().stream()
            .map(SeriesOuterClass.Series::getId)
            .collect(Collectors.toList());
    List<Series> listSeries =
        seriesUtils.mapGrpcObjectsToPojo(getAllPaged.getDocumentsList(), Series.class);
    List<Series> listDbSeries = seriesUtils.getDocumentsByIds(listSeriesIds);
    listSeries.sort(Comparator.comparing(Series::getId));
    listDbSeries.sort(Comparator.comparing(Series::getId));

    Assert.assertEquals(listSeries, listDbSeries);
  }

  @Test(dataProvider = "sort-values")
  @Description("GetAllPaged - sort by created time")
  @TmsLink("28501394")
  public void C28501394_TEST(int sort) {
    int limit = 10;
    int offset = 0;

    List<SeriesOuterClass.GetAllPagedSort> grpcSort =
        Collections.singletonList(
            SeriesOuterClass.GetAllPagedSort.newBuilder()
                .setField("createdAt")
                .setValue(1)
                .build());

    SeriesOuterClass.GetAllPagedResponse getAllPaged =
        new SeriesClient(grpcChannelManager.getChannel(), false)
            .getAllPaged(limit, offset, grpcSort, null, false);

    Assert.assertTrue(limit <= getAllPaged.getMetadata().getTotalItems());
    Assert.assertEquals(offset, getAllPaged.getMetadata().getCurrentOffset());

    List<String> listSeriesName =
        getAllPaged.getDocumentsList().stream()
            .map(SeriesOuterClass.Series::getCreatedAt)
            .collect(Collectors.toList());
    List<String> manuallySorted = new ArrayList<>(listSeriesName);

    if (sort == 1) Collections.sort(manuallySorted);
    else Collections.sort(manuallySorted, Comparator.reverseOrder());

    Assert.assertEquals(listSeriesName, manuallySorted);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "INVALID_ARGUMENT.*")
  @Description("GetAllPaged - INVALID_ARGUMENT")
  @TmsLink("28501395")
  public void C28501395_TEST()
      throws NoSuchFieldException, InvocationTargetException, IllegalAccessException {
    List<SeriesOuterClass.GetAllPagedSort> grpcSort =
        Collections.singletonList(
            SeriesOuterClass.GetAllPagedSort.newBuilder().setField("test").setValue(1).build());
    seriesClient.getAllPagedException(1, 0, grpcSort, null, true);
  }

  @Test
  @Description("Valid request - GetSeriesById")
  @TmsLink("28501330")
  public void C28501330_TEST() {
    Series mongoSeries = seriesUtils.getRandomDocument();
    String randomSeriesId = mongoSeries.getId();
    CommonUtils.printTestCaseNameWithId("C742992", randomSeriesId);

    Series mappedSeries =
        seriesUtils.mapGrpcObjectToPojo(
            seriesClient.getSeriesById(randomSeriesId).getDocument(), Series.class);

    Assert.assertEquals(mongoSeries, mappedSeries);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Series document for requested ID not found")
  @Description("Invalid request - GetSeriesById")
  @TmsLink("28501331")
  public void C28501331_TEST() {
    seriesClient.getSeriesById(MongoUtils.INVALID_ID_VALUE);
  }

  @Test
  @Description("Valid request with single episode - GetSeriesByIds")
  @TmsLink("28501332")
  public void C28501332_TEST() {
    Series mongoSeries = seriesUtils.getRandomDocument();
    List<String> randomSeriesIds = Collections.singletonList(mongoSeries.getId());

    List<Series> mongoSeriesList = seriesUtils.getDocumentsByIds(randomSeriesIds);

    GetByIdsRequest getByIdsRequest =
        GetByIdsRequest.newBuilder().addAllIds(randomSeriesIds).build();

    List<?> mappedSeriesList =
        seriesUtils.mapGrpcObjectsToPojo(
            seriesClient.getSeriesByIds(getByIdsRequest).getDocumentsList(), Series.class);

    Assert.assertEquals(mongoSeriesList, mappedSeriesList);
  }

  @Test
  @Description("Valid request with multiple episodes - GetSeriesByIds")
  @TmsLink("28501333")
  public void C28501333_TEST() {
    List<String> randomSeriesIds = seriesUtils.getRandomSeriesIds();

    List<Series> mongoSeriesList = seriesUtils.getDocumentsByIds(randomSeriesIds);

    GetByIdsRequest getByIdsRequest =
        GetByIdsRequest.newBuilder().addAllIds(randomSeriesIds).build();

    List<?> mappedSeriesList =
        seriesUtils.mapGrpcObjectsToPojo(
            seriesClient.getSeriesByIds(getByIdsRequest).getDocumentsList(), Series.class);

    Assert.assertEquals(mongoSeriesList, mappedSeriesList);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Series documents for requested IDs not found")
  @Description("Invalid request with single episode - GetSeriesByIds")
  @TmsLink("28501334")
  public void C28501334_TEST() {
    List<String> randomSeriesIds = Collections.singletonList(MongoUtils.INVALID_ID_VALUE);

    GetByIdsRequest getByIdsRequest =
        GetByIdsRequest.newBuilder().addAllIds(randomSeriesIds).build();

    Assert.assertTrue(seriesClient.getSeriesByIds(getByIdsRequest).getDocumentsList().isEmpty());
  }

  @Test
  @Description("Valid request with invalid series - GetSeriesByIds")
  @TmsLink("28501335")
  public void C28501335_TEST() {
    List<String> randomSeriesIds = seriesUtils.getRandomSeriesIds();
    randomSeriesIds.add(MongoUtils.INVALID_ID_VALUE);

    List<Series> mongoSeriesList = seriesUtils.getDocumentsByIds(randomSeriesIds);

    GetByIdsRequest getByIdsRequest =
        GetByIdsRequest.newBuilder().addAllIds(randomSeriesIds).build();

    List<?> mappedSeriesList =
        seriesUtils.mapGrpcObjectsToPojo(
            seriesClient.getSeriesByIds(getByIdsRequest).getDocumentsList(), Series.class);

    Assert.assertEquals(mongoSeriesList, mappedSeriesList);
  }

  @Test
  @Description("GetDocumentsUpdatedBetweenDates - Time period for which there is series")
  @TmsLink("28501350")
  public void C28501350_TEST() {
    List<Series> grpcSeries = new ArrayList<>();

    Iterator<tv.pluto.ip.dal.series.SeriesOuterClass.Series> series =
        seriesClient.getDocumentsUpdatedBetweenDates(
            seriesUtils.updatedAtMinString, seriesUtils.updatedAtMaxString);

    // In some cases contentFeeds method could return about 50k objects
    // To reduce the time of test we get and validate only 10 objects
    int count = 0;
    // get first 10 series
    while (count < 10) {
      try {
        grpcSeries.add(seriesUtils.mapGrpcObjectToPojo(series.next(), Series.class));
      } catch (NoSuchElementException e) {
        // In case there will be less than 10 objects
        break;
      }
      count++;
    }

    List<String> ids = grpcSeries.stream().map(Series::getId).collect(Collectors.toList());

    List<Series> mongoSeries = seriesUtils.getDocumentsByIds(ids);

    mongoSeries.sort(Comparator.comparing(Series::getId));
    grpcSeries.sort(Comparator.comparing(Series::getId));

    // Verify that dates in gRPC response are correct
    grpcSeries.forEach(
        e ->
            Assert.assertTrue(
                seriesUtils.updatedAtMin.isBefore(e.getUpdatedAt()),
                "updatedAt doesn't match for series with ID=" + e.getId()));

    // Verify that objects in gRPC response are correct
    Assert.assertEquals(mongoSeries, grpcSeries);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_MESSAGE)
  @Description("GetDocumentsUpdatedBetweenDates - Time period without series")
  @TmsLink("28501351")
  public void C28501351_TEST() {
    seriesClient.getDocumentsUpdatedBetweenDates(failStartDateTime, failEndDateTime).next();
  }

  @Test(
      dataProvider = "fail-content-feeds-test-data",
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = INVALID_ARGUMENT_MESSAGE)
  @Description(
      "GetDocumentsUpdatedBetweenDates - Invalid values for start_date_time and end_date_time")
  @TmsLink("28501352")
  public void C28501352_TEST(String startDateTime, String endDateTime) {
    seriesClient.getDocumentsUpdatedBetweenDates(startDateTime, endDateTime).next();
  }

  @Test(
      dataProvider = "less-or-equal-fail-content-feeds-test-data",
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = INTERNAL_ERROR_MESSAGE)
  @Description(
      "GetDocumentsUpdatedBetweenDates - end_date_time is less than or equal to start_date_time")
  @TmsLink("28501353")
  public void C28501353_TEST(String startDateTime, String endDateTime) {
    seriesClient.getDocumentsUpdatedBetweenDates(startDateTime, endDateTime).next();
  }

  @Test(priority = -1)
  @Description("UpdateContentMamUniqueIdsByIds - series with contentUUID")
  @TmsLink("28501344")
  public void C28501344_TEST() {
    List<Series> mongoSeries = seriesUtils.getSeriesWithUUID(5);

    List<ObjectIdContentIdSet> objectIdContentIdSets = seriesUtils.extractIdsAndUUID(mongoSeries);

    seriesClient.updateContentMamUniqueIdsByIds(objectIdContentIdSets);

    Map<String, String> contentMap = seriesUtils.getIdsFromContentSet(objectIdContentIdSets);

    SoftAssert softAssert = new SoftAssert();

    contentMap.forEach(
        (id, contentUUID) -> {
          Series updatedSeries = seriesUtils.getDocumentById(id);
          softAssert.assertEquals(updatedSeries.getContentUUID(), contentUUID);
        });

    softAssert.assertAll();
  }

  @Test(priority = -1)
  @Description("UpdateContentMamUniqueIdsByIds - series without contentUUID")
  @TmsLink("28501345")
  public void C28501345_TEST() {
    List<Series> mongoSeries = seriesUtils.getSeriesWithoutUUID(5);

    List<ObjectIdContentIdSet> objectIdContentIdSets = seriesUtils.extractIdsAndUUID(mongoSeries);

    seriesClient.updateContentMamUniqueIdsByIds(objectIdContentIdSets);

    Map<String, String> contentMap = seriesUtils.getIdsFromContentSet(objectIdContentIdSets);

    SoftAssert softAssert = new SoftAssert();

    contentMap.forEach(
        (id, contentUUID) -> {
          Series updatedSeries = seriesUtils.getDocumentById(id);
          softAssert.assertEquals(updatedSeries.getContentUUID(), contentUUID);
        });

    softAssert.assertAll();
  }

  @Test
  @Description("CFeedsSyncAllIds - stream returns CFEEDS stream of Series ids")
  @TmsLink("28501362")
  public void C28501362_TEST() {
    Iterator<?> cFeedsSyncAllIds =
        seriesClient.getCFeedsResponse(null, null, null, "cFeedsSyncAllIds");
    List<String> listGrpcIds = new ArrayList<>();

    for (int i = 0; i < 100; i++) {
      String id = ((SeriesOuterClass.Id) cFeedsSyncAllIds.next()).getId();
      listGrpcIds.add(id);
    }

    List<String> seriesIds =
        seriesUtils.getDocumentsByIds(listGrpcIds).stream()
            .map(o -> ((Series) o).getId())
            .sorted()
            .collect(Collectors.toList());
    Collections.sort(listGrpcIds);

    Assert.assertTrue(seriesIds.size() > 0);
    Assert.assertTrue(CommonUtils.isTwoListsEqual(listGrpcIds, seriesIds));
  }

  @Test
  @Description("cFeedsSyncDocuments - last_run_at")
  @TmsLink("28501363")
  public void C28501363_TEST() {
    Series series = seriesUtils.getRandomDocument();
    Iterator<?> cFeedsSyncAllIds =
        seriesClient.getCFeedsResponse(
            null, series.getUpdatedAt() + "Z", null, "cFeedsSyncDocuments");

    int total = 0;
    while (cFeedsSyncAllIds.hasNext() && total < 100) {
      Series testSeries = seriesUtils.mapGrpcObjectToPojo(cFeedsSyncAllIds.next(), Series.class);
      boolean isAfter =
          testSeries.getUpdatedAt().isAfter(series.getUpdatedAt())
              || testSeries.getUpdatedAt().isEqual(series.getUpdatedAt());

      if (!isAfter) {
        LogUtils.logSevereMessage(
            "MongoDB random series: " + series.getId() + ", time: " + series.getUpdatedAt());
        LogUtils.logSevereMessage(
            "Series id: " + testSeries.getId() + ", time: " + testSeries.getUpdatedAt());
      }
      Assert.assertTrue(isAfter);
      total++;
    }

    Assert.assertTrue(total > 0);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_MESSAGE)
  @Description("cFeedsSyncDocuments - last_run_at future time")
  @TmsLink("28501364")
  public void C28501364_TEST() {
    String futureTime = CommonUtils.localDateTimeStringPlusDays(10);
    seriesClient.getCFeedsResponse(null, futureTime, null, "cFeedsSyncDocuments").next();
  }

  @Test
  @Description("UpdateFieldsRequest - update the series")
  @TmsLink("28501365")
  public void C28501365_TEST() {
    String createdSeriesId = getNewCreatedSeries();
    CommonUtils.printTestCaseNameWithId("C12177848", createdSeriesId);

    Assert.assertNotNull(createdSeriesId);
    Series series = seriesUtils.getDocumentById(createdSeriesId);
    LogUtils.logInfoMessage("Random series id: " + series.getId());

    String updatedName = CommonUtils.slightlyModifyString(series.getName());
    String updatedDescription = CommonUtils.slightlyModifyString(series.getDescription());
    String updatedDisplayName = CommonUtils.slightlyModifyString(series.getDisplayName());
    boolean updatePlutoTv = !series.getPlutoTvOO();

    SeriesOuterClass.Series updateSeries =
        SeriesOuterClass.Series.newBuilder()
            .setName(updatedName)
            .setType(series.getType())
            .setDescription(updatedDescription)
            .setDisplayName(updatedDisplayName)
            .setPlutoTvOO(updatePlutoTv)
            .build();

    List<String> listUpdatedFields =
        List.of("name", "type", "description", "displayName", "plutoTvOO");
    LogUtils.logInfoMessage("List of updated fields: " + listUpdatedFields);

    SeriesOuterClass.UpdateFieldsResponse getUpdateDocument =
        seriesClient.getUpdateFields(series.getId(), updateSeries, listUpdatedFields);
    SeriesOuterClass.Series seriesResponse = getUpdateDocument.getDocument();

    Series seriesMangoDB = seriesUtils.getDocumentById(series.getId());
    Series grpcResponse = seriesUtils.mapGrpcObjectToPojo(seriesResponse, Series.class);
    seriesMangoDB.setUpdatedAt(grpcResponse.getUpdatedAt());
    seriesMangoDB.setThumbnail(null);
    grpcResponse.setThumbnail(null);

    Assert.assertEquals(grpcResponse, seriesMangoDB);
    Assert.assertEquals(seriesMangoDB.getName(), updatedName);
    Assert.assertEquals(seriesMangoDB.getType(), series.getType());
    Assert.assertEquals(seriesMangoDB.getDescription(), updatedDescription);
    Assert.assertEquals(seriesMangoDB.getDisplayName(), updatedDisplayName);
    Assert.assertEquals(seriesMangoDB.getPlutoTvOO().booleanValue(), updatePlutoTv);

    seriesClientAuth.deleteById(series.getId());
  }

  @Test
  @Description("UpdateFieldsRequest - update the series with an error")
  @TmsLink("28501366")
  public void C28501366_TEST() {
    boolean isFail = false;
    String notValidId = UUID.randomUUID().toString();
    List<String> listUpdatedFields = List.of("name", "type", "description", "displayName");

    SeriesOuterClass.Series revertSeries =
        SeriesOuterClass.Series.newBuilder()
            .setName("test")
            .setType("test")
            .setDescription("test")
            .setDisplayName("test")
            .build();

    try {
      seriesClient.getUpdateFields(notValidId, revertSeries, listUpdatedFields);
    } catch (StatusRuntimeException e) {
      isFail = true;
      LogUtils.logInfoMessage(e.getStatus().getDescription());
      Assert.assertEquals(e.getStatus().getCode().toString(), "INTERNAL");
    }
    Assert.assertTrue(isFail);
  }

  @Test
  @Description("Verify 16x9 poster field in series")
  @TmsLink("28501367")
  public void C28501367_TEST() {
    Series seriesMango = seriesUtils.getRandomDocumentWithTheField("poster16_9", true);
    Image poster169 = seriesMango.getPoster16_9();

    SeriesOuterClass.Series seriesResponse =
        seriesClient.getSeriesById(seriesMango.getId()).getDocument();
    Image grpcPoster169 =
        seriesUtils.mapGrpcObjectToPojo(seriesResponse.getPoster169(), Image.class);

    Assert.assertNotNull(poster169);
    Assert.assertEquals(poster169, grpcPoster169);
  }

  @Test
  @Description("UpdateFields - updated customReference in Series")
  @TmsLink("28501368")
  public void C28501368_TEST() {
    BasicDBObject query = new BasicDBObject("$exists", true).append("$size", 1);

    Series mongoSeries = seriesUtils.getDocumentByKeyValueObject("customReferences", query);
    String randomSeriesId = mongoSeries.getId();

    List<String> listReferences =
        List.of("sponsorshipPartner", "entitlementPartner", "deliveryPartner");
    String updatedReferenceType = CommonUtils.getRandomValueFromList(listReferences);
    String id = mongoSeries.getCustomReferences().get(0).getId();
    String value = mongoSeries.getCustomReferences().get(0).getValue();
    LogUtils.logInfoMessage(
        "Series type from: "
            + mongoSeries.getCustomReferences().get(0).getType()
            + ", to: "
            + updatedReferenceType);

    SeriesOuterClass.CustomReference customReference =
        SeriesOuterClass.Series.newBuilder()
            .addCustomReferencesBuilder()
            .setId(id)
            .setValue(value)
            .setType(updatedReferenceType)
            .build();

    SeriesOuterClass.Series series =
        SeriesOuterClass.Series.newBuilder()
            .addAllCustomReferences(List.of(customReference))
            .build();
    SeriesOuterClass.UpdateFieldsResponse updateFields =
        seriesClient.getUpdateFields(randomSeriesId, series, List.of("customReferences"));

    Assert.assertEquals(
        updatedReferenceType, updateFields.getDocument().getCustomReferences(0).getType());

    Series mappedSeries = seriesUtils.mapGrpcObjectToPojo(updateFields.getDocument(), Series.class);

    mongoSeries.getCustomReferences().get(0).setType(updatedReferenceType);
    Assertions.assertThat(mongoSeries).isEqualToIgnoringGivenFields(mappedSeries, "updatedAt");
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "INTERNAL.*")
  @Description("Create - the new series without required fields")
  @TmsLink("28501372")
  public void C28501372_TEST() {
    SeriesOuterClass.Series buildSeries =
        SeriesOuterClass.Series.newBuilder().setName(UUID.randomUUID().toString()).build();

    seriesClientAuth.createSeries(buildSeries);
  }

  @Test
  @Description("GetPopulatedSeriesById - vodCategoryEntries path")
  @TmsLink("28501369")
  public void C28501369_TEST() {
    String path = "vodCategoryEntries";
    SeriesOuterClass.PopulatePath.Builder populateBuilder =
        SeriesOuterClass.PopulatePath.newBuilder().setPath(path);
    Series seriesMango = seriesUtils.getExistsAndSizeNotZero(path);
    SeriesOuterClass.GetPopulatedSeriesByIdResponse getPopulatedResponse =
        seriesClient.getPopulatedSeriesById(seriesMango.getId(), populateBuilder);
    SeriesOuterClass.SeriesPopulated series = getPopulatedResponse.getSeries();

    List<ObjectId> listChannelIds =
        series.getChannelsList().stream().map(c -> new ObjectId(c.getId())).toList();

    Assert.assertEquals(series.getId(), seriesMango.getId());
    Assert.assertEquals(series.getName(), seriesMango.getName());
    Assert.assertEquals(series.getCategory(), seriesMango.getCategory());
    Assert.assertEquals(series.getSubCategory(), seriesMango.getSubCategory());
    Assert.assertEquals(series.getGenre(), seriesMango.getGenre());
    Assert.assertEquals(series.getSubGenre(), seriesMango.getSubGenre());
    Assert.assertEquals(series.getSummary(), seriesMango.getSummary());
    Assert.assertEquals(series.getEpisodeCount(), seriesMango.getEpisodeCount());
    Assert.assertEquals(series.getActiveRegion(), seriesMango.getActiveRegion());
    Assert.assertEquals(series.getMetadataLanguage(), seriesMango.getMetadataLanguage());
    Assert.assertEquals(series.getTagsList(), seriesMango.getTags());
    Assert.assertEquals(listChannelIds, seriesMango.getChannels());
    Assert.assertEquals(series.getRegionFilterList(), seriesMango.getRegionFilter());
    Assert.assertEquals(series.getRating(), seriesMango.getRating());
  }

  @Test
  @Description("GetPopulatedSeriesById - customReferences path")
  @TmsLink("28501370")
  public void C28501370_TEST() {
    String path = "customReferences";
    SeriesOuterClass.PopulatePath.Builder value =
        SeriesOuterClass.PopulatePath.newBuilder().setPath("value");

    SeriesOuterClass.PopulatePath.Builder populateBuilder =
        SeriesOuterClass.PopulatePath.newBuilder().setPath(path).setPopulate(value);

    Series seriesMango = seriesUtils.getExistsAndSizeNotZero(path);
    SeriesOuterClass.GetPopulatedSeriesByIdResponse getPopulatedResponse =
        seriesClient.getPopulatedSeriesById(seriesMango.getId(), populateBuilder);
    SeriesOuterClass.SeriesPopulated series = getPopulatedResponse.getSeries();

    Assert.assertEquals(series.getId(), seriesMango.getId());
    Assert.assertEquals(series.getGenre(), seriesMango.getGenre());
    Assert.assertEquals(series.getNotes(), seriesMango.getNotes());
    Assert.assertEquals(series.getDescription(), seriesMango.getDescription());
    Assert.assertEquals(series.getRating(), seriesMango.getRating());
    Assert.assertEquals(series.getCategory(), seriesMango.getCategory());
    Assert.assertEquals(series.getName(), seriesMango.getName());
    Assert.assertEquals(
        series.getCustomReferences(0).getId(), seriesMango.getCustomReferences().get(0).getId());
    Assert.assertEquals(
        series.getCustomReferences(0).getType(),
        seriesMango.getCustomReferences().get(0).getType());
  }

  @Test
  @Description("GetPopulatedSeriesById - response has a ratingDescriptors array")
  @TmsLink("28501381")
  public void C28501381_TEST() {
    Series seriesMango = seriesUtils.getExistsAndSizeNotZero("ratingDescriptors");

    SeriesOuterClass.GetPopulatedSeriesByIdResponse getPopulatedResponse =
        seriesClient.getPopulatedSeriesById(seriesMango.getId(), null);
    SeriesOuterClass.SeriesPopulated series = getPopulatedResponse.getSeries();

    Assert.assertEquals(seriesMango.getId(), getPopulatedResponse.getSeries().getId());
    Assert.assertFalse(series.getRatingDescriptorsList().isEmpty());
    Assert.assertEquals(series.getRatingDescriptorsList(), seriesMango.getRatingDescriptors());
  }

  @Test
  @Description("GetPopulatedSeriesById - response without a ratingDescriptors array")
  @TmsLink("28501382")
  public void C28501382_TEST() {
    Series seriesMango = seriesUtils.getRandomDocumentWithTheField("ratingDescriptors", false);
    CommonUtils.printTestCaseNameWithId("C21903801", seriesMango.getId());

    SeriesOuterClass.GetPopulatedSeriesByIdResponse getPopulatedResponse =
        seriesClient.getPopulatedSeriesById(seriesMango.getId(), null);

    Assert.assertTrue(getPopulatedResponse.getSeries().getRatingDescriptorsList().isEmpty());
    Assert.assertEquals(seriesMango.getId(), getPopulatedResponse.getSeries().getId());
    Assert.assertEquals(seriesMango.getName(), getPopulatedResponse.getSeries().getName());
    Assert.assertEquals(seriesMango.getRating(), getPopulatedResponse.getSeries().getRating());
    Assert.assertEquals(
        seriesMango.getDescription(), getPopulatedResponse.getSeries().getDescription());
  }

  @Test
  @Description("GetPopulatedSeriesById - response has a contentTimestamp field")
  @TmsLink("28501383")
  public void C28501383_TEST() {
    Series seriesMango = seriesUtils.getRandomDocumentWithTheField("contentTimestamp", true);
    CommonUtils.printTestCaseNameWithId("C21903802", seriesMango.getId());

    SeriesOuterClass.GetPopulatedSeriesByIdResponse getPopulatedResponse =
        seriesClient.getPopulatedSeriesById(seriesMango.getId(), null);

    Assert.assertNotNull(seriesMango.getContentTimestamp());
    Assert.assertEquals(seriesMango.getId(), getPopulatedResponse.getSeries().getId());
    Assert.assertEquals(seriesMango.getName(), getPopulatedResponse.getSeries().getName());
    Assert.assertEquals(seriesMango.getRating(), getPopulatedResponse.getSeries().getRating());
    Assert.assertEquals(
        seriesMango.getDescription(), getPopulatedResponse.getSeries().getDescription());
  }

  @Test
  @Description("GetPopulatedSeriesById - response hasn't a contentTimestamp field")
  @TmsLink("28501384")
  public void C28501384_TEST() {
    Series seriesMango = seriesUtils.getRandomDocumentWithTheField("contentTimestamp", false);
    CommonUtils.printTestCaseNameWithId("C21903803", seriesMango.getId());

    SeriesOuterClass.GetPopulatedSeriesByIdResponse getPopulatedResponse =
        seriesClient.getPopulatedSeriesById(seriesMango.getId(), null);

    Assert.assertNull(seriesMango.getContentTimestamp());
    Assert.assertEquals(seriesMango.getId(), getPopulatedResponse.getSeries().getId());
    Assert.assertEquals(seriesMango.getName(), getPopulatedResponse.getSeries().getName());
    Assert.assertEquals(seriesMango.getRating(), getPopulatedResponse.getSeries().getRating());
    Assert.assertEquals(
        seriesMango.getDescription(), getPopulatedResponse.getSeries().getDescription());
  }

  @Test(priority = -1)
  @Description("Create - the new series")
  @TmsLink("28501371")
  public String C28501371_TEST() {
    String testData = UUID.randomUUID().toString().replace("-", "_");
    String metadata = "en";
    String type = "tv";
    int seasonNumber = 1;
    List<String> listRegions = List.of("us", "br", "ca", "de", "dk", "es", "fi", "fr");
    String region = CommonUtils.getRandomValueFromList(listRegions);

    SeriesOuterClass.Series buildSeries =
        SeriesOuterClass.Series.newBuilder()
            .setName(testData)
            .addRegionFilter(region)
            .setType(type)
            .setCategory(testData)
            .setSubCategory(testData)
            .setGenre(testData)
            .setSubGenre(testData)
            .setSummary(testData)
            .setMetadataLanguage(metadata)
            .addSeasons(SeriesOuterClass.Season.newBuilder().setNumber(seasonNumber).build())
            .setDescription(testData)
            .addTags(testData)
            .build();

    SeriesOuterClass.Series grpcSeries = seriesClientAuth.createSeries(buildSeries).getDocument();

    SoftAssert softAssert = new SoftAssert();
    softAssert.assertNotNull(grpcSeries.getId());
    softAssert.assertNotNull(seriesClient.getSeriesById(grpcSeries.getId()));
    softAssert.assertEquals(grpcSeries.getName(), testData);
    softAssert.assertEquals(grpcSeries.getCategory(), testData);
    softAssert.assertEquals(grpcSeries.getSubCategory(), testData);
    softAssert.assertEquals(grpcSeries.getGenre(), testData);
    softAssert.assertEquals(grpcSeries.getSubGenre(), testData);
    softAssert.assertEquals(grpcSeries.getSummary(), testData);
    softAssert.assertEquals(grpcSeries.getType(), type);
    softAssert.assertEquals(grpcSeries.getRegionFilter(0), region);
    softAssert.assertEquals(grpcSeries.getMetadataLanguage(), metadata);
    softAssert.assertEquals(grpcSeries.getDescription(), testData);
    softAssert.assertEquals(grpcSeries.getTags(0), testData);
    softAssert.assertEquals(grpcSeries.getSeasons(0).getNumber(), seasonNumber);
    softAssert.assertAll();

    return grpcSeries.getId();
  }

  private String getNewCreatedSeries() {
    return C28501371_TEST();
  }

  @Test
  @Description("DeleteById - delete the existing series")
  @TmsLink("28501373")
  public void C28501373_TEST() {
    String createdSeriesId = getNewCreatedSeries();
    CommonUtils.printTestCaseNameWithId("C21134840", createdSeriesId);

    Assert.assertNotNull(createdSeriesId);
    Assert.assertNotNull(seriesUtils.getDocumentById(createdSeriesId));
    LogUtils.logInfoMessage("Test seriesId: " + createdSeriesId);

    SeriesOuterClass.DeleteByIdResponse deleteByIdResponse =
        seriesClientAuth.deleteById(createdSeriesId);
    Assert.assertEquals(deleteByIdResponse.getDeletedCount(), 1);

    seriesUtils.getDocumentById(createdSeriesId);
    Assert.assertNull(seriesUtils.getDocumentById(createdSeriesId));
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "UNAUTHENTICATED: DB deletes not allowed without authentication")
  @Description("DeleteById - delete the existing series no authentication")
  @TmsLink("28501377")
  public void C28501377_TEST() {
    String createdSeriesId = getNewCreatedSeries();
    Assert.assertNotNull(createdSeriesId);
    Assert.assertNotNull(seriesUtils.getDocumentById(createdSeriesId));
    LogUtils.logInfoMessage("Test seriesId: " + createdSeriesId);

    seriesClient.deleteById(createdSeriesId);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Series documents for requested IDs not found")
  @Description("DeleteById - delete the existing series 2 times")
  @TmsLink("28501374")
  public void C28501374_TEST() {
    String createdSeriesId = getNewCreatedSeries();
    Assert.assertNotNull(createdSeriesId);
    Assert.assertNotNull(seriesUtils.getDocumentById(createdSeriesId));
    LogUtils.logInfoMessage("Test seriesId: " + createdSeriesId);

    SeriesOuterClass.DeleteByIdResponse deleteByIdResponse =
        seriesClientAuth.deleteById(createdSeriesId);
    Assert.assertEquals(deleteByIdResponse.getDeletedCount(), 1);

    seriesUtils.getDocumentById(createdSeriesId);
    Assert.assertNull(seriesUtils.getDocumentById(createdSeriesId));
    seriesClientAuth.deleteById(createdSeriesId);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Series documents for requested IDs not found")
  @Description("DeleteById - delete not existing series")
  @TmsLink("28501375")
  public void C28501375_TEST() {
    String notExistingId = "64a6db7fae99990999999999";
    Assert.assertNull(seriesUtils.getDocumentById(notExistingId));
    seriesClientAuth.deleteById(notExistingId);
  }

  @Test(
      expectedExceptions = IllegalArgumentException.class,
      expectedExceptionsMessageRegExp = "state should be: hexString has 24 characters")
  @Description("DeleteById - delete not existing series with invalid format")
  @TmsLink("28501376")
  public void C28501376_TEST() {
    String notExistingId = "testDAL";
    Assert.assertNull(seriesUtils.getDocumentById(notExistingId));
    new SeriesClient(grpcChannelManager.getChannel(), true).deleteById(notExistingId);
  }

  @Test
  @Description("UpdateFields - update list of the channels")
  @TmsLink("28501378")
  public void C28501378_TEST() {
    String createdSeriesId = getNewCreatedSeries();
    Assert.assertNotNull(createdSeriesId);
    Series series = seriesUtils.getDocumentById(createdSeriesId);
    List<String> listChannels =
        List.of("51f2bc1616a2a2f72d00018c", "5240c7901beac6ec4a003919", "61f2bc1616a2a2f72d00011a");

    LogUtils.logInfoMessage("Random series id: " + series.getId());

    SeriesOuterClass.Series updateSeries =
        SeriesOuterClass.Series.newBuilder()
            .setId(createdSeriesId)
            .addAllChannels(listChannels)
            .build();

    SeriesOuterClass.Series seriesResponse =
        seriesClient
            .getUpdateFields(series.getId(), updateSeries, List.of("channels"))
            .getDocument();

    Series seriesMangoDB = seriesUtils.getDocumentById(series.getId());
    Series grpcResponse = seriesUtils.mapGrpcObjectToPojo(seriesResponse, Series.class);

    List<String> listResponseChannels =
        grpcResponse.getChannels().stream().map(ObjectId::toString).collect(Collectors.toList());

    Assert.assertEquals(listChannels, listResponseChannels);
    Assert.assertNotSame(seriesMangoDB, grpcResponse);
    seriesClientAuth.deleteById(series.getId());
  }

  @Test
  @Description("UpdateFields - update regionFilter")
  @TmsLink("28501379")
  public void C28501379_TEST() {
    String createdSeriesId = getNewCreatedSeries();
    Assert.assertNotNull(createdSeriesId);
    Series series = seriesUtils.getDocumentById(createdSeriesId);
    List<String> listRegions = List.of("us", "br", "ca", "de", "dk", "es", "fi", "fr");
    List<String> listChannels =
        List.of("31f2bc1616a2a2f72d00018c", "1240c7901beac6ec4a003919", "61f2bc1616a2a2f72d00011a");
    LogUtils.logInfoMessage("Random series id: " + series.getId());

    SeriesOuterClass.Series updateSeries =
        SeriesOuterClass.Series.newBuilder()
            .setId(createdSeriesId)
            .addAllRegionFilter(listRegions)
            .addAllChannels(listChannels)
            .build();

    SeriesOuterClass.Series seriesResponse =
        seriesClient
            .getUpdateFields(series.getId(), updateSeries, List.of("channels", "regionFilter"))
            .getDocument();

    Series seriesMangoDB = seriesUtils.getDocumentById(series.getId());
    Series grpcResponse = seriesUtils.mapGrpcObjectToPojo(seriesResponse, Series.class);

    Assert.assertEquals(seriesMangoDB.getRegionFilter(), listRegions);
    Assert.assertEquals(listChannels.size(), grpcResponse.getChannels().size());

    Assert.assertNotNull(seriesMangoDB);
    Assert.assertNotNull(grpcResponse);

    Assert.assertNotSame(seriesMangoDB, grpcResponse);
    seriesClientAuth.deleteById(series.getId());
  }

  @Test
  @Description("UpdateFields - update regionFilter to the existing regionFilter")
  @TmsLink("28501380")
  public void C28501380_TEST() {
    String createdSeriesId = getNewCreatedSeries();
    Assert.assertNotNull(createdSeriesId);
    Series series = seriesUtils.getDocumentById(createdSeriesId);
    List<String> regionFilter = series.getRegionFilter();
    List<String> listChannels = List.of("91f2bc1616a2a2f72d00018c", "7240c7901beac6ec4a003919");
    CommonUtils.printTestCaseNameWithId("C21892240", series.getId());

    SeriesOuterClass.Series updateSeries =
        SeriesOuterClass.Series.newBuilder()
            .setId(createdSeriesId)
            .addAllRegionFilter(regionFilter)
            .addAllChannels(listChannels)
            .build();

    SeriesOuterClass.Series seriesResponse =
        seriesClient
            .getUpdateFields(series.getId(), updateSeries, List.of("channels", "regionFilter"))
            .getDocument();

    Series seriesMangoDB = seriesUtils.getDocumentById(series.getId());
    Series grpcResponse = seriesUtils.mapGrpcObjectToPojo(seriesResponse, Series.class);

    Assert.assertEquals(seriesMangoDB.getRegionFilter(), regionFilter);
    Assert.assertEquals(listChannels.size(), grpcResponse.getChannels().size());

    // Update - updates Thumbnail path. For now it is okay
    grpcResponse.setThumbnail(seriesMangoDB.getThumbnail());
    Assert.assertEquals(seriesMangoDB, grpcResponse);
    seriesClientAuth.deleteById(series.getId());
  }

  @Test
  @Description("Bug verification - DAL add ratingsDescriptors to series so updateFields can update")
  @TmsLink("28501387")
  public void C28501387_TEST() {
    String createdSeriesId = getNewCreatedSeries();
    Assert.assertNotNull(createdSeriesId);
    Series series = seriesUtils.getDocumentById(createdSeriesId);

    BasicDBObject query = new BasicDBObject("region", series.getActiveRegion().toUpperCase());

    List<RatingDescriptors> ratingDescriptors = ratingDescriptorsUtils.getDocuments(query, 5);
    List<String> slugs =
        ratingDescriptors.stream().map(RatingDescriptors::getSlug).collect(Collectors.toList());

    LogUtils.logInfoMessage("C22150199_TEST Series id: " + series.getId());

    String contentTimestamp = LocalDateTime.now(ZoneOffset.UTC).toString();

    SeriesOuterClass.Series updateSeries =
        SeriesOuterClass.Series.newBuilder()
            .setId(createdSeriesId)
            .addAllRatingDescriptors(slugs)
            .setContentTimestamp(contentTimestamp)
            .build();

    SeriesOuterClass.Series seriesResponse =
        seriesClient
            .getUpdateFields(
                series.getId(), updateSeries, List.of("ratingDescriptors", "contentTimestamp"))
            .getDocument();

    Series seriesMongoDB = seriesUtils.getDocumentById(series.getId());
    Series grpcResponse = seriesUtils.mapGrpcObjectToPojo(seriesResponse, Series.class);

    Assert.assertEquals(seriesMongoDB.getContentTimestamp(), grpcResponse.getContentTimestamp());
    Assert.assertEquals(seriesMongoDB.getRatingDescriptors(), grpcResponse.getRatingDescriptors());
    seriesClientAuth.deleteById(series.getId());
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = INTERNAL_MESSAGE)
  @Description("DeleteById - series has an episode with one or more VOD collections")
  @TmsLink("28501385")
  public void C28501385_TEST() {
    String seriesId = episodeUtils.getSeriesWithEpisodeAndVodCategoryEntries(1);
    new SeriesClient(grpcChannelManager.getChannel(), true).deleteById(seriesId);
  }

  @Test(enabled = false)
  @Description("DeleteById - delete series and episode")
  @TmsLink("28501388")
  public void C28501388_TEST() {
    SeriesClient seriesClientAuth = new SeriesClient(grpcChannelManager.getChannel(), true);
    Series series = seriesUtils.getDocumentByFieldAndValue("episodeCount", 1);
    String seriesId = series.getId();
    CommonUtils.printTestCaseNameWithId("C22155128", seriesId);

    Assert.assertEquals(series.getEpisodeCount().intValue(), 1);
    Episodes episodes = episodeUtils.getDocumentByFieldAndId("series", seriesId);
    Assert.assertFalse(episodes.getId().isEmpty());

    SeriesOuterClass.DeleteByIdResponse deleteByIdResponse = seriesClientAuth.deleteById(seriesId);
    Assert.assertEquals(deleteByIdResponse.getDeletedCount(), 1);

    Episodes episodesAfterRemoval = episodeUtils.getDocumentByFieldAndId("series", seriesId);
    Assert.assertNull(episodesAfterRemoval);
  }

  @Test
  @Description(
      "DeleteById - series contains one or more episodes with upcoming channel timeline airings")
  @TmsLink("28501386")
  public void C28501386_TEST() {
    boolean wasException = false;
    String seriesId = episodeUtils.getSeriesWithEpisodeTimelinesAfter();

    try {
      seriesClientAuth.deleteById(seriesId);
    } catch (RuntimeException e) {
      wasException = true;
      Assert.assertTrue(e.getMessage().contains("INTERNAL: The series"));
    }
    Assert.assertTrue(wasException);
  }

  @Test
  @Description("GetSeriesUsingExternalId - happy path")
  @TmsLink("28501389")
  public void C28501389_TEST() {
    Series series = seriesUtils.getExistsAndSizeNotZero("channels");
    String channelId = series.getChannels().get(0).toString();
    CommonUtils.printTestCaseNameWithId("C22155128", channelId);

    tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesUsingExternalIdRequest request =
        tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesUsingExternalIdRequest.newBuilder()
            .setPublished(series.getPublished())
            .setActiveRegion(series.getActiveRegion())
            .setGenre(series.getGenre())
            .setCategory(series.getCategory())
            .addAllChannels(List.of(channelId))
            .build();

    SeriesOuterClass.GetSeriesUsingExternalIdResponse seriesUsingExternalId =
        seriesClient.getSeriesUsingExternalId(request);

    List<Series> listSeries =
        seriesUtils.mapGrpcObjectsToPojo(seriesUsingExternalId.getDocumentsList(), Series.class);
    Assert.assertFalse(listSeries.isEmpty());

    List<String> listSeriesIds =
        listSeries.stream().map(Series::getId).collect(Collectors.toList());

    boolean allIdsInSeries =
        listSeries.stream()
            .flatMap(ser -> ser.getChannels().stream())
            .anyMatch(c -> c.toString().equals(channelId));

    List<Series> listSeriesDb = seriesUtils.getDocumentsByIds(listSeriesIds);

    Assert.assertTrue(allIdsInSeries);
    Assert.assertEquals(listSeriesDb.size(), listSeries.size());

    listSeriesDb.sort(Comparator.comparing(Series::getId));
    listSeries.sort(Comparator.comparing(Series::getId));

    Assert.assertEquals(listSeriesDb, listSeries);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "NOT_FOUND: No Series documents found for provided external ID")
  @Description("GetSeriesUsingExternalId - invalid channelId")
  @TmsLink("28501390")
  public void C28501390_TEST() {
    Series series = seriesUtils.getExistsAndSizeNotZero("channels");
    String channelId = "0000000616a2a2f72d00018a";

    tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesUsingExternalIdRequest request =
        tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesUsingExternalIdRequest.newBuilder()
            .setPublished(series.getPublished())
            .setActiveRegion(series.getActiveRegion())
            .setGenre(series.getGenre())
            .setCategory(series.getCategory())
            .addAllChannels(List.of(channelId))
            .build();

    seriesClient.getSeriesUsingExternalId(request);
  }

  @Test
  @Description("GetSeriesPartnerByIds - very by series id")
  @TmsLink("28501391")
  public void C28501391_TEST() {
    // some seriesHave more than 100,000 ids.
    // Episodes episodes = episodeUtils.getRandomDocument();
    // String seriesId = episodes.getSeries();

    List<String> listSeriesId =
        List.of(
            "55af192227f18b7f8bc28068",
            "55af192a27f18b7f8bc28279",
            "55af193327f18b7f8bc2852e",
            "55af18f827f18b7f8bc2798c",
            "55af18eb27f18b7f8bc275b8");
    int index = new Random().nextInt(listSeriesId.size());

    String seriesId = listSeriesId.get(index);
    CommonUtils.printTestCaseNameWithId("C24578633", seriesId);

    List<Episodes> listEpisodes = episodeUtils.getDocumentsByFieldAndId("series", seriesId);
    List<String> listClipsFromEpisodes =
        listEpisodes.stream()
            .flatMap(episode -> episode.getSources().stream().map(Source::getClip))
            .collect(Collectors.toList());

    LogUtils.logInfoMessage("listClipsFromEpisodes size: " + listClipsFromEpisodes.size());

    List<Clips> listClips = clipsUtils.getDocumentsByIds(listClipsFromEpisodes);
    Set<String> listAuthorsFromClips =
        listClips.stream()
            .filter(c -> !c.getPromotional() && !c.getProgrammingType().equals("Pluto TV Systems"))
            .map(c -> c.getAuthor().getName())
            .collect(Collectors.toSet());

    List<String> listSeries =
        listEpisodes.stream().map(Episodes::getSeries).collect(Collectors.toList());
    List<SeriesOuterClass.SeriesPartner> documentsList =
        seriesClient.getSeriesPartnerByIds(listSeries).getDocumentsList();
    List<SeriesOuterClass.SeriesPartner> listPartners =
        documentsList.stream().filter(d -> d.getId().equals(seriesId)).collect(Collectors.toList());

    Set<String> setPartners =
        listPartners.stream().map(p -> p.getAuthor()).collect(Collectors.toSet());

    String authorResponse = setPartners.iterator().next();
    int listAuthorSize = listAuthorsFromClips.size();
    LogUtils.logInfoMessage("listAuthorSize: " + listAuthorSize);

    if (listAuthorSize > 500) Assert.assertEquals("Over 500 episodes", authorResponse);
    else if (listAuthorSize > 1) Assert.assertEquals(authorResponse, "Multiple authors");
    else if (listAuthorsFromClips.size() == 1)
      Assert.assertEquals(authorResponse, listAuthorsFromClips.iterator().next());
    else Assert.assertEquals("No Author", authorResponse);
  }

  @Test
  @Description("GetSeriesPartnerByIds - invalid series id")
  @TmsLink("28501392")
  public void C28501392_TEST() {
    String seriesId = "55af192227f18b7f8bc00000";
    List<SeriesOuterClass.SeriesPartner> seriesList =
        seriesClient.getSeriesPartnerByIds(List.of(seriesId)).getDocumentsList();
    Assert.assertEquals("", seriesList.get(0).getAuthor());
  }

  @Test(groups = {"ttfb"})
  @Description("Check TTFB 90 percentile for the SeriesManager GetByIds operation.")
  @TmsLink("28501397")
  public void C28501397_TEST() {
    Series mongoSeries = seriesUtils.getRandomDocument();
    List<String> randomSeriesIds = Collections.singletonList(mongoSeries.getId());

    GetByIdsRequest getByIdsRequest =
        GetByIdsRequest.newBuilder().addAllIds(randomSeriesIds).build();

    GrpcCall grpcCall =
        new GrpcCall("SeriesManager.GetByIds", () -> seriesClient.getSeriesByIds(getByIdsRequest));

    TTFBGrpcValidator ttfbGrpcValidator = TTFBGrpcValidator.builder().build();

    ttfbGrpcValidator.validateTTFBForGrpcCalls(List.of(grpcCall));
  }

  @Test
  @Description("GetAllPaged - filter by id")
  @TmsLink("28501396")
  public void C28501396_TEST() {
    int limit = 5;
    int offset = 0;

    Series randomSeries = seriesUtils.getRandomDocument();
    String seriesId = randomSeries.getId();
    CommonUtils.printTestCaseNameWithId("C32334182", seriesId);

    SeriesOuterClass.Filter filter = seriesClient.seriesFilter("_id", seriesId, "string", null);

    SeriesOuterClass.GetAllPagedResponse getAllPaged =
        new SeriesClient(grpcChannelManager.getChannel(), false)
            .getAllPaged(limit, offset, null, List.of(filter), true);

    Assert.assertEquals(1, getAllPaged.getMetadata().getTotalItems());
    Assert.assertEquals(offset, getAllPaged.getMetadata().getCurrentOffset());

    Series grpcSeries =
        seriesUtils.mapGrpcObjectToPojo(getAllPaged.getDocumentsList().get(0), Series.class);

    Series dbSeries = seriesUtils.getDocumentById(seriesId);
    Assert.assertEquals(grpcSeries, dbSeries);
  }

  @Test
  @Description("GetAllPaged - filter by name")
  @TmsLink("28501398")
  public void C28501398_TEST() {
    int limit = 5;
    int offset = 0;

    Series randomSeries = seriesUtils.getRandomDocument();
    String name = randomSeries.getName();
    CommonUtils.printTestCaseNameWithField("C32334187", name);

    SeriesOuterClass.Filter filter = seriesClient.seriesFilter("name", name, "string", null);

    SeriesOuterClass.GetAllPagedResponse getAllPaged =
        new SeriesClient(grpcChannelManager.getChannel(), false)
            .getAllPaged(limit, offset, null, List.of(filter), true);

    Assert.assertTrue(limit >= getAllPaged.getMetadata().getTotalItems());
    Assert.assertEquals(offset, getAllPaged.getMetadata().getCurrentOffset());

    getAllPaged
        .getDocumentsList()
        .forEach(series -> Assert.assertTrue(series.getName().contains(name)));
  }

  @Test
  @Description("GetAllPaged - filter by activeRegion")
  @TmsLink("28501399")
  public void C28501399_TEST() {
    int limit = 5;
    int offset = 0;

    Series randomSeries = seriesUtils.getExistsAndNotEmpty("activeRegion");
    String testField = randomSeries.getActiveRegion();
    CommonUtils.printTestCaseNameWithField("C32334283", testField);

    SeriesOuterClass.Filter filter =
        seriesClient.seriesFilter("activeRegion", testField, "string", null);

    SeriesOuterClass.GetAllPagedResponse getAllPaged =
        new SeriesClient(grpcChannelManager.getChannel(), false)
            .getAllPaged(limit, offset, null, List.of(filter), true);

    Assert.assertTrue(limit >= getAllPaged.getMetadata().getTotalItems());
    Assert.assertEquals(getAllPaged.getMetadata().getCurrentOffset(), offset);

    getAllPaged
        .getDocumentsList()
        .forEach(series -> Assert.assertTrue(series.getActiveRegion().contains(testField)));
  }

  @Test(
      dataProvider = "sort-values",
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "INVALID_ARGUMENT.*")
  @Description("GetAllPaged - sorting by invalid field")
  @TmsLink("28501400")
  public void C28501400_TEST(int sortValue) {
    SeriesOuterClass.GetAllPagedSort sort =
        SeriesOuterClass.GetAllPagedSort.newBuilder()
            .setField("invalidField")
            .setValue(sortValue)
            .build();

    ManagedChannel channel = grpcChannelManager.getChannel();

    new SeriesClient(channel, false)
        .getAllPagedNoException(MAX_LIMIT, 0, List.of(sort), null, true);
  }

  @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = NOT_FOUND)
  @Description("GetAllPaged - filter by invalid field")
  @TmsLink("28501401")
  public void C28501401_TEST() {
    ManagedChannel channel = grpcChannelManager.getChannel();

    SeriesOuterClass.Filter filter =
        seriesClient.seriesFilter("name", UUID.randomUUID().toString(), "string", null);

    new SeriesClient(channel, false).getAllPagedNoException(1, 0, null, List.of(filter), true);
  }

  @Test
  @Description(
      "GetSeriesByNameAndActiveRegionAndCustomReferences - Request with valid name, activeRegion and customReferences.")
  @TmsLink("28501404")
  public void C28501404_TEST() {
    Series mongoSeries = seriesUtils.getRandomSeriesWithNameActiveRegionAndCustomReferences();

    SeriesOuterClass.CustomReference customReference =
        SeriesOuterClass.Series.newBuilder()
            .addCustomReferencesBuilder()
            .setId(mongoSeries.getCustomReferences().get(0).getId())
            .setValue(mongoSeries.getCustomReferences().get(0).getValue())
            .setType(mongoSeries.getCustomReferences().get(0).getType())
            .build();

    tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesByNameAndActiveRegionAndCustomReferencesRequest
        request =
            tv.pluto.ip.dal.series.SeriesOuterClass
                .GetSeriesByNameAndActiveRegionAndCustomReferencesRequest.newBuilder()
                .setName(mongoSeries.getName())
                .setActiveRegion(mongoSeries.getActiveRegion())
                .addAllCustomReferences(List.of(customReference))
                .build();

    SeriesOuterClass.GetSeriesByNameAndActiveRegionAndCustomReferenceResponse response =
        seriesClient.getSeriesByNameAndActiveRegionAndCustomReferences(request);

    Series mappedSeries = seriesUtils.mapGrpcObjectToPojo(response.getDocument(), Series.class);

    Assert.assertNotNull(mongoSeries);
    Assert.assertNotNull(mappedSeries);
    Assert.assertEquals(mongoSeries, mappedSeries);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "NOT_FOUND: No Series document found for provided series name, active region, and custom reference")
  @Description(
      "GetSeriesByNameAndActiveRegionAndCustomReferences - Request with a non-existent name.")
  @TmsLink("28501405")
  public void C28501405_TEST() {
    Series mongoSeries = seriesUtils.getRandomSeriesWithNameActiveRegionAndCustomReferences();

    String nonExistentName = "Peñarol & Shakhtar Donetsk";

    SeriesOuterClass.CustomReference customReference =
        SeriesOuterClass.Series.newBuilder()
            .addCustomReferencesBuilder()
            .setId(mongoSeries.getCustomReferences().get(0).getId())
            .setValue(mongoSeries.getCustomReferences().get(0).getValue())
            .setType(mongoSeries.getCustomReferences().get(0).getType())
            .build();

    tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesByNameAndActiveRegionAndCustomReferencesRequest
        request =
            tv.pluto.ip.dal.series.SeriesOuterClass
                .GetSeriesByNameAndActiveRegionAndCustomReferencesRequest.newBuilder()
                .setName(nonExistentName)
                .setActiveRegion(mongoSeries.getActiveRegion())
                .addAllCustomReferences(List.of(customReference))
                .build();

    seriesClient.getSeriesByNameAndActiveRegionAndCustomReferences(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "NOT_FOUND: No Series document found for provided series name, active region, and custom reference")
  @Description(
      "GetSeriesByNameAndActiveRegionAndCustomReferences - Request with a non-existent activeRegion.")
  @TmsLink("28501406")
  public void C28501406_TEST() {
    Series mongoSeries = seriesUtils.getRandomSeriesWithNameActiveRegionAndCustomReferences();

    String nonExistentActiveRegion = "nonExistentActiveRegion";

    SeriesOuterClass.CustomReference customReference =
        SeriesOuterClass.Series.newBuilder()
            .addCustomReferencesBuilder()
            .setId(mongoSeries.getCustomReferences().get(0).getId())
            .setValue(mongoSeries.getCustomReferences().get(0).getValue())
            .setType(mongoSeries.getCustomReferences().get(0).getType())
            .build();

    tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesByNameAndActiveRegionAndCustomReferencesRequest
        request =
            tv.pluto.ip.dal.series.SeriesOuterClass
                .GetSeriesByNameAndActiveRegionAndCustomReferencesRequest.newBuilder()
                .setName(mongoSeries.getName())
                .setActiveRegion(nonExistentActiveRegion)
                .addAllCustomReferences(List.of(customReference))
                .build();

    seriesClient.getSeriesByNameAndActiveRegionAndCustomReferences(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "NOT_FOUND: No Series document found for provided series name, active region, and custom reference")
  @Description(
      "GetSeriesByNameAndActiveRegionAndCustomReferences - Request with a non-existent customReference.")
  @TmsLink("28501407")
  public void C28501407_TEST() {
    Series mongoSeries = seriesUtils.getRandomSeriesWithNameActiveRegionAndCustomReferences();

    String nonExistentObjectId = "1a2b3c4d5e6f7a8b9c10d11e";
    String nonExistentType = "Peñarol & Shakhtar Donetsk";

    SeriesOuterClass.CustomReference customReference =
        SeriesOuterClass.Series.newBuilder()
            .addCustomReferencesBuilder()
            .setId(nonExistentObjectId)
            .setValue(nonExistentObjectId)
            .setType(nonExistentType)
            .build();

    tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesByNameAndActiveRegionAndCustomReferencesRequest
        request =
            tv.pluto.ip.dal.series.SeriesOuterClass
                .GetSeriesByNameAndActiveRegionAndCustomReferencesRequest.newBuilder()
                .setName(mongoSeries.getName())
                .setActiveRegion(mongoSeries.getActiveRegion())
                .addAllCustomReferences(List.of(customReference))
                .build();

    seriesClient.getSeriesByNameAndActiveRegionAndCustomReferences(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INVALID_ARGUMENT: name, activeRegion, and customReferences are all required parameters")
  @Description("GetSeriesByNameAndActiveRegionAndCustomReferences - Request without name.")
  @TmsLink("28501408")
  public void C28501408_TEST() {
    Series mongoSeries = seriesUtils.getRandomSeriesWithNameActiveRegionAndCustomReferences();

    SeriesOuterClass.CustomReference customReference =
        SeriesOuterClass.Series.newBuilder()
            .addCustomReferencesBuilder()
            .setId(mongoSeries.getCustomReferences().get(0).getId())
            .setValue(mongoSeries.getCustomReferences().get(0).getValue())
            .setType(mongoSeries.getCustomReferences().get(0).getType())
            .build();

    tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesByNameAndActiveRegionAndCustomReferencesRequest
        request =
            tv.pluto.ip.dal.series.SeriesOuterClass
                .GetSeriesByNameAndActiveRegionAndCustomReferencesRequest.newBuilder()
                .setActiveRegion(mongoSeries.getActiveRegion())
                .addAllCustomReferences(List.of(customReference))
                .build();

    seriesClient.getSeriesByNameAndActiveRegionAndCustomReferences(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INVALID_ARGUMENT: name, activeRegion, and customReferences are all required parameters")
  @Description("GetSeriesByNameAndActiveRegionAndCustomReferences - Request without activeRegion.")
  @TmsLink("28501409")
  public void C28501409_TEST() {
    Series mongoSeries = seriesUtils.getRandomSeriesWithNameActiveRegionAndCustomReferences();

    SeriesOuterClass.CustomReference customReference =
        SeriesOuterClass.Series.newBuilder()
            .addCustomReferencesBuilder()
            .setId(mongoSeries.getCustomReferences().get(0).getId())
            .setValue(mongoSeries.getCustomReferences().get(0).getValue())
            .setType(mongoSeries.getCustomReferences().get(0).getType())
            .build();

    tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesByNameAndActiveRegionAndCustomReferencesRequest
        request =
            tv.pluto.ip.dal.series.SeriesOuterClass
                .GetSeriesByNameAndActiveRegionAndCustomReferencesRequest.newBuilder()
                .setName(mongoSeries.getName())
                .addAllCustomReferences(List.of(customReference))
                .build();

    seriesClient.getSeriesByNameAndActiveRegionAndCustomReferences(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INVALID_ARGUMENT: name, activeRegion, and customReferences are all required parameters")
  @Description(
      "GetSeriesByNameAndActiveRegionAndCustomReferences - Request without customReferences.")
  @TmsLink("28501410")
  public void C28501410_TEST() {
    Series mongoSeries = seriesUtils.getRandomSeriesWithNameActiveRegionAndCustomReferences();

    tv.pluto.ip.dal.series.SeriesOuterClass.GetSeriesByNameAndActiveRegionAndCustomReferencesRequest
        request =
            tv.pluto.ip.dal.series.SeriesOuterClass
                .GetSeriesByNameAndActiveRegionAndCustomReferencesRequest.newBuilder()
                .setName(mongoSeries.getName())
                .setActiveRegion(mongoSeries.getActiveRegion())
                .build();

    seriesClient.getSeriesByNameAndActiveRegionAndCustomReferences(request);
  }

  @Test
  @Description("GetSeriesByChannelId - Happy Path")
  @TmsLink("28501411")
  public void C28501411_TEST() {
    Series series = seriesUtils.getExistsAndSizeNotZero("channels");

    ObjectId channelObjectId = series.getChannels().get(0);
    CommonUtils.printTestCaseNameWithId("C28501411", channelObjectId.toString());

    SeriesOuterClass.GetSeriesByChannelIdResponse grpcResponse =
        seriesClient.getSeriesByChannelIdResponse(channelObjectId.toString());

    List<Series> mongoSeries = seriesUtils.getSeriesByChannelId(channelObjectId);

    mongoSeries.sort(Comparator.comparing(Series::getId));

    List<SeriesOuterClass.SeriesDetails> grpcSeriesList =
        new ArrayList<>(grpcResponse.getDocumentsList());

    grpcSeriesList.sort(Comparator.comparing(SeriesOuterClass.SeriesDetails::getId));

    Assert.assertEquals(mongoSeries.size(), grpcSeriesList.size());

    for (int i = 0; i < mongoSeries.size(); i++) {
      Series mongoSeriesItem = mongoSeries.get(i);
      SeriesOuterClass.SeriesDetails grpcSeriesItem = grpcSeriesList.get(i);

      Assert.assertEquals(mongoSeriesItem.getId(), grpcSeriesItem.getId());
      Assert.assertEquals(mongoSeriesItem.getName(), grpcSeriesItem.getName());

      Instant mongoUpdatedAt = mongoSeriesItem.getUpdatedAt().atOffset(ZoneOffset.UTC).toInstant();
      Instant grpcUpdatedAt = Instant.parse(grpcSeriesItem.getUpdatedAt());
      Assert.assertEquals(mongoUpdatedAt, grpcUpdatedAt);
    }
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "FAILED_PRECONDITION: Request param id must be a valid id")
  @Description("GetSeriesByChannelId - Invalid ChannelId")
  @TmsLink("28501412")
  public void C28501412_TEST() {
    String channelId = "552813bd688ba6740ee13aa1a";
    seriesClient.getSeriesByChannelIdResponse(channelId);
  }

  @Test
  @Description("GetSeriesByIds - Ascending Sorting Order validation")
  @TmsLink("28501413")
  public void C28501413_TEST() {
    int sortOrder = 1;
    List<String> randomSeriesIds = seriesUtils.getRandomSeriesIds();

    LogUtils.logInfoMessage("Series IDs: " + randomSeriesIds);

    List<Series> mongoSeriesList = seriesUtils.getDocumentsByIds(randomSeriesIds, sortOrder);

    GetAllPagedSort idSort =
        GetAllPagedSort.newBuilder().setField("_id").setValue(sortOrder).build();

    List<GetAllPagedSort> grpcSort = List.of(idSort);

    GetByIdsRequest getByIdsRequest =
        GetByIdsRequest.newBuilder().addAllIds(randomSeriesIds).addAllSort(grpcSort).build();

    List<?> mappedSeriesList =
        seriesUtils.mapGrpcObjectsToPojo(
            seriesClient.getSeriesByIds(getByIdsRequest).getDocumentsList(), Series.class);

    Assert.assertEquals(mongoSeriesList, mappedSeriesList);
  }

  @Test
  @Description("GetSeriesByIds - Descending Sorting Order validation")
  @TmsLink("28501414")
  public void C28501414_TEST() {
    int sortOrder = -1;
    List<String> randomSeriesIds = seriesUtils.getRandomSeriesIds();

    LogUtils.logInfoMessage("Series IDs: " + randomSeriesIds);

    List<Series> mongoSeriesList = seriesUtils.getDocumentsByIds(randomSeriesIds, sortOrder);

    GetAllPagedSort idSort =
        GetAllPagedSort.newBuilder().setField("_id").setValue(sortOrder).build();

    List<GetAllPagedSort> grpcSort = List.of(idSort);

    GetByIdsRequest getByIdsRequest =
        GetByIdsRequest.newBuilder().addAllIds(randomSeriesIds).addAllSort(grpcSort).build();

    List<?> mappedSeriesList =
        seriesUtils.mapGrpcObjectsToPojo(
            seriesClient.getSeriesByIds(getByIdsRequest).getDocumentsList(), Series.class);

    Assert.assertEquals(mongoSeriesList, mappedSeriesList);
  }

  @Test
  @Description("GetSeriesByIds - Projection validation")
  @TmsLink("28501415")
  public void C28501415_TEST() {
    List<String> randomSeriesIds = seriesUtils.getRandomSeriesIds();

    LogUtils.logInfoMessage("Series IDs: " + randomSeriesIds);

    String[] projectionFieldsArray = new String[] {"_id", "name", "slug"};
    List<String> projectionFields = List.of("_id", "name", "slug");

    List<Series> mongoSeriesList =
        seriesUtils.getDocumentsByIds(randomSeriesIds, projectionFieldsArray);

    GetByIdsRequest getByIdsRequest =
        GetByIdsRequest.newBuilder()
            .addAllIds(randomSeriesIds)
            .addAllProjection(projectionFields)
            .build();

    List<?> mappedSeriesList =
        seriesUtils.mapGrpcObjectsToPojo(
            seriesClient.getSeriesByIds(getByIdsRequest).getDocumentsList(), Series.class);

    Assert.assertEquals(mongoSeriesList, mappedSeriesList);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INTERNAL: One or more provided sort values no not match 1 or -1.")
  @Issue("IP-5930")
  @Description("GetSeriesByIds - Invalid sort order")
  @TmsLink("28501416")
  public void C28501416_TEST() {
    int invalidSortOrder = 10;
    List<String> randomSeriesIds = seriesUtils.getRandomSeriesIds();

    LogUtils.logInfoMessage("Series IDs: " + randomSeriesIds);

    GetAllPagedSort idSort =
        GetAllPagedSort.newBuilder().setField("_id").setValue(invalidSortOrder).build();

    List<GetAllPagedSort> grpcSort = List.of(idSort);

    GetByIdsRequest getByIdsRequest =
        GetByIdsRequest.newBuilder().addAllIds(randomSeriesIds).addAllSort(grpcSort).build();

    seriesClient.getSeriesByIds(getByIdsRequest);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INTERNAL: One or more provided sort attributes are not contained within the table/collection. .*")
  @Issue("IP-5930")
  @Description("GetSeriesByIds - Invalid sort field")
  @TmsLink("28501417")
  public void C28501417_TEST() {
    int sortOrder = 1;
    String invalidSortField = "Peñarol & Shakhtar Donetsk";

    List<String> randomSeriesIds = seriesUtils.getRandomSeriesIds();

    LogUtils.logInfoMessage("Series IDs: " + randomSeriesIds);

    GetAllPagedSort idSort =
        GetAllPagedSort.newBuilder().setField(invalidSortField).setValue(sortOrder).build();

    List<GetAllPagedSort> grpcSort = List.of(idSort);

    GetByIdsRequest getByIdsRequest =
        GetByIdsRequest.newBuilder().addAllIds(randomSeriesIds).addAllSort(grpcSort).build();

    seriesClient.getSeriesByIds(getByIdsRequest);
  }

  @Test
  @Description("GetSeriesFutureTimelines - Happy Path")
  @TmsLink("28501402")
  public void C28501402_TEST() {
    /* Time to avoid flakiness. ~Time it is ~ execution of the query. If the timeline start is too
    close to now
    and then you sent to grpc with the time after now, it will not find it.*/
    Document mongoTimelineDoc =
        new DocumentUtils("timelines").getTimelinesWithStartAfterCurrentDate(2000);

    String mongoEpisodeId = ((Document) mongoTimelineDoc.get("episode")).get("_id").toString();
    String mongoTimelineId = mongoTimelineDoc.get("_id").toString();
    String seriesId =
        ((Document) mongoTimelineDoc.get("series", List.class).get(0)).get("_id").toString();
    String channelId = mongoTimelineDoc.get("channel").toString();

    Instant mongoStart = mongoTimelineDoc.get("start", Date.class).toInstant();
    Instant mongoStop = mongoTimelineDoc.get("stop", Date.class).toInstant();
    LogUtils.logInfoMessage(
        "TimelineId: " + mongoTimelineId + ", Start: " + mongoStart + " Stop: " + mongoStop);
    LogUtils.logInfoMessage("Now: " + Instant.now());
    LogUtils.logInfoMessage(
        "SeriesId: " + seriesId + ", ChannelId: " + channelId + ", EpisodeId: " + mongoEpisodeId);

    SeriesOuterClass.GetSeriesFutureTimelineResponse grpcResponse =
        seriesClient.getSeriesFutureTimelineResponse(seriesId, channelId);

    List<SeriesOuterClass.SeriesFutureTimeline> grpcSeriesList =
        new ArrayList<>(grpcResponse.getDocumentsList());

    SeriesOuterClass.SeriesFutureTimeline grpcTimeline =
        grpcSeriesList.stream()
            .filter(t -> t.getTimelineId().equals(mongoTimelineId))
            .findFirst()
            .orElseThrow(
                () ->
                    new AssertionError(
                        "No matching gRPC timeline found for ID: " + mongoTimelineId));

    Assert.assertEquals(
        grpcTimeline.getEpisodeId(),
        mongoEpisodeId,
        "Episode ID mismatch between MongoDB and gRPC");

    Instant grpcStart = Instant.parse(grpcTimeline.getTimelineStart());
    Instant grpcStop = Instant.parse(grpcTimeline.getTimelineStop());

    Assert.assertTrue(grpcStart.isBefore(grpcStop), "gRPC timeline start is not before stop.");
    Assert.assertTrue(mongoStart.isBefore(mongoStop), "MongoDB timeline start is not before stop.");

    Assert.assertEquals(
        grpcStart,
        mongoStart,
        "Timeline start time mismatch between MongoDB and gRPC for episode: " + mongoEpisodeId);
    Assert.assertEquals(
        grpcStop,
        mongoStop,
        "Timeline stop time mismatch between MongoDB and gRPC for episode: " + mongoEpisodeId);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "FAILED_PRECONDITION: Series_id must be a valid id")
  @Description("GetSeriesFutureTimeline - Invalid SeriesId")
  @TmsLink("28501403")
  public void C28501403_TEST() {
    String invalidSeriesId = "6320334bb2e75600132474771";
    String channelId = "622f40c901d4b70007ad7609";
    seriesClient.getSeriesFutureTimelineResponse(invalidSeriesId, channelId);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "FAILED_PRECONDITION: Channel_id must be a valid id")
  @Description("GetSeriesFutureTimeline - Invalid ChannelID")
  @TmsLink("28501418")
  public void C28501418_TEST() {

    List<String> randomSeriesIds = seriesUtils.getRandomSeriesIds();
    Assert.assertFalse(randomSeriesIds.isEmpty(), "No series IDs found.");
    String validSeriesId = randomSeriesIds.get(0);
    String invalidChannelId = "622f40c901d4b70007ad76091";
    seriesClient.getSeriesFutureTimelineResponse(validSeriesId, invalidChannelId);
  }

  @Test
  @Description("UpdateRatings - update ratings for series and assert DB and gRPC values")
  @TmsLink("28876766")
  public void C28876766_TEST() {
    Series mongoSeries = seriesUtils.getPublishedSeriesWithRatingAndActiveRegion();
    Assert.assertNotNull(mongoSeries, "No valid Series found for updateRatings test.");

    String seriesId = mongoSeries.getId();
    CommonUtils.printTestCaseNameWithId("SERIES_RATINGS_POSITIVE", seriesId);
    String targetRating = "TV-PG";
    boolean replace = true;

    List<Episodes> episodes = episodeUtils.getEpisodesBySeriesIdWithProjection(seriesId);
    Assert.assertFalse(episodes.isEmpty(), "No episodes found for series:  " + seriesId);

    List<String> clipIds =
        episodes.stream()
            .filter(ep -> ep.getSources() != null)
            .flatMap(ep -> ep.getSources().stream())
            .map(Source::getClip)
            .filter(java.util.Objects::nonNull)
            .distinct()
            .collect(Collectors.toList());

    Assert.assertFalse(clipIds.isEmpty(), "No clips found for series: " + seriesId);

    SeriesOuterClass.UpdateRatingsResponse response =
        seriesClient.updateRatings(seriesId, targetRating, replace);

    Assert.assertNotNull(response, "No response from updateRatings.");
    Assert.assertEquals(response.getDocument().getId(), seriesId);
    Assert.assertEquals(response.getDocument().getRating(), targetRating);

    Series updatedMongoSeries = seriesUtils.getDocumentById(seriesId);
    Assert.assertEquals(
        updatedMongoSeries.getRating(), targetRating, "Series rating not updated in MongoDB.");

    List<Episodes> updatedEpisodes = episodeUtils.getEpisodesBySeriesIdWithProjection(seriesId);
    for (Episodes ep : updatedEpisodes) {
      Assert.assertEquals(
          ep.getRating(), targetRating, "Episode rating not updated for episode: " + ep.getId());
    }

    List<Clips> updatedClips = clipsUtils.getClipsByIdsWithStandardProjection(clipIds);
    for (Clips clip : updatedClips) {
      // Only assert for non-promotional clips
      if (clip.getPromotional() == null || !clip.getPromotional()) {
        Assert.assertEquals(
            clip.getRating(), targetRating, "Clip rating not updated for clip: " + clip.getId());
      }
    }
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "INVALID_ARGUMENT: rating is required")
  @Description("UpdateRatings - error for empty rating")
  @TmsLink("28876765")
  public void C28876765_TEST() {
    Series mongoSeries = seriesUtils.getPublishedSeriesWithRatingAndActiveRegion();
    Assert.assertNotNull(mongoSeries, "No valid Series found for updateRatings test.");
    String seriesId = mongoSeries.getId();
    CommonUtils.printTestCaseNameWithId("SERIES_RATINGS_EMPTY", seriesId);
    String emptyRating = "";
    boolean replace = false;
    seriesClient.updateRatings(seriesId, emptyRating, replace);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INVALID_ARGUMENT: Rating \"invalid\" is not valid for active region \"us\"")
  @Description("UpdateRatings - error for invalid rating value")
  @TmsLink("28876767")
  public void C28876767_TEST() {
    Series mongoSeries = seriesUtils.getPublishedSeriesWithRatingAndActiveRegion();
    Assert.assertNotNull(mongoSeries, "No valid Series found for updateRatings test.");
    String seriesId = mongoSeries.getId();
    CommonUtils.printTestCaseNameWithId("SERIES_RATINGS_INVALID", seriesId);
    String invalidRating = "invalid";
    boolean replace = true;
    seriesClient.updateRatings(seriesId, invalidRating, replace);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "INVALID_ARGUMENT: series_id must be a valid ObjectID")
  @Description("UpdateRatings - error for invalid series id")
  @TmsLink("28876768")
  public void C28876768_TEST() {
    String invalidSeriesId = "invalid";
    String validRating = "TV-PG";
    boolean replace = true;
    seriesClient.updateRatings(invalidSeriesId, validRating, replace);
  }

  @Test
  @Description("UpdateRatings - should update with restrictive clip ratings when replace = true")
  @TmsLink("28876769")
  public void C28876769_TEST() {
    Series mongoSeries = seriesUtils.getPublishedSeriesWithRatingAndActiveRegion();
    Assert.assertNotNull(mongoSeries, "No valid Series found for updateRatings test.");
    String seriesId = mongoSeries.getId();
    CommonUtils.printTestCaseNameWithId("SERIES_RATINGS_REPLACE_TRUE", seriesId);
    String lowerRating = "PG";
    boolean replace = true;
    SeriesOuterClass.UpdateRatingsResponse response =
        seriesClient.updateRatings(seriesId, lowerRating, replace);

    Assert.assertNotNull(response, "No response from updateRatings.");
    Assert.assertEquals(response.getDocument().getId(), seriesId);
    Assert.assertEquals(response.getDocument().getRating(), lowerRating);

    Series updatedMongoSeries = seriesUtils.getDocumentById(seriesId);
    Assert.assertEquals(
        updatedMongoSeries.getRating(), lowerRating, "Series rating not updated in MongoDB.");
  }

  @Test
  @Description("UpdateRatings - should update when rating is most restrictive")
  @TmsLink("28876770")
  public void C28876770_TEST() {
    Series mongoSeries = seriesUtils.getPublishedSeriesWithRatingAndActiveRegion();
    Assert.assertNotNull(mongoSeries, "No valid Series found for updateRatings test.");
    String seriesId = mongoSeries.getId();
    CommonUtils.printTestCaseNameWithId("SERIES_RATINGS_MOST_RESTRICTIVE", seriesId);
    String mostRestrictiveRating = "NC-17"; // Highest for "US", should always update
    boolean replace = false;

    SeriesOuterClass.UpdateRatingsResponse response =
        seriesClient.updateRatings(seriesId, mostRestrictiveRating, replace);
    Assert.assertNotNull(response, "No response from updateRatings.");
    Assert.assertEquals(response.getDocument().getId(), seriesId);
    Assert.assertEquals(response.getDocument().getRating(), mostRestrictiveRating);

    Series updatedMongoSeries = seriesUtils.getDocumentById(seriesId);
    Assert.assertEquals(
        updatedMongoSeries.getRating(),
        mostRestrictiveRating,
        "Series rating not updated in MongoDB.");
  }

  @Test
  @Description("UpdateRatings - should update to 'Not Rated' in any case")
  @TmsLink("28876771")
  public void C28876771_TEST() {
    Series mongoSeries = seriesUtils.getPublishedSeriesWithRatingAndActiveRegion();
    Assert.assertNotNull(mongoSeries, "No valid Series found for updateRatings test.");
    String seriesId = mongoSeries.getId();
    CommonUtils.printTestCaseNameWithId("SERIES_RATINGS_NOT_RATED", seriesId);
    String rating = "Not Rated";
    boolean replace = false;

    SeriesOuterClass.UpdateRatingsResponse response =
        seriesClient.updateRatings(seriesId, rating, replace);

    Assert.assertNotNull(response, "No response from updateRatings.");
    Assert.assertEquals(response.getDocument().getId(), seriesId);
    Assert.assertEquals(response.getDocument().getRating(), rating);

    Series updatedMongoSeries = seriesUtils.getDocumentById(seriesId);
    Assert.assertEquals(
        updatedMongoSeries.getRating(), rating, "Series rating not updated in MongoDB.");
  }

  @Test
  @Description("UpdateRatings - verify replace=true overrides weight comparison")
  @TmsLink("28884093")
  public void C28884093_TEST() {
    Series mongoSeries = seriesUtils.getPublishedSeriesWithRatingAndActiveRegion();
    Assert.assertNotNull(mongoSeries, "No valid Series found for updateRatings test.");

    String seriesId = mongoSeries.getId();
    CommonUtils.printTestCaseNameWithId("SERIES_RATINGS_REPLACE_OVERRIDE", seriesId);

    // Use low weight rating with replace=true - should succeed regardless of clip weights
    String lowRating = "TV-Y";
    boolean replace = true;

    SeriesOuterClass.UpdateRatingsResponse response =
        seriesClient.updateRatings(seriesId, lowRating, replace);

    Assert.assertNotNull(response, "Response should not be null");
    Assert.assertEquals(response.getDocument().getRating(), lowRating);
    Assert.assertTrue(
        response.getClipsWithMoreRestrictiveRatingsList().isEmpty(),
        "Should have no restrictive clips error when replace=true");

    Series updatedSeries = seriesUtils.getDocumentById(seriesId);
    Assert.assertEquals(updatedSeries.getRating(), lowRating, "Series rating should be updated");

    List<Episodes> episodes = episodeUtils.getEpisodesBySeriesIdWithProjection(seriesId);
    for (Episodes ep : episodes) {
      Assert.assertEquals(ep.getRating(), lowRating, "Episode should be updated with replace=true");
    }
  }

  @Test
  @Description("UpdateRatings - clip with 'Not Rated' ratings get updated properly")
  @TmsLink("29253616")
  public void C29253616_TEST() {
    Series mongoSeries = seriesUtils.getPublishedSeriesWithNotRatedAndActiveRegion();

    String seriesId = mongoSeries.getId();
    CommonUtils.printTestCaseNameWithId("SERIES_NOT_RATED", seriesId);

    List<Episodes> episodes = episodeUtils.getEpisodesBySeriesIdWithProjection(seriesId);
    Assert.assertFalse(episodes.isEmpty(), "No episodes found for series:  " + seriesId);

    List<String> clipIds =
        episodes.stream()
            .filter(ep -> ep.getSources() != null)
            .flatMap(ep -> ep.getSources().stream())
            .map(Source::getClip)
            .filter(java.util.Objects::nonNull)
            .distinct()
            .collect(Collectors.toList());

    Assert.assertFalse(clipIds.isEmpty(), "No clips found for series:  " + seriesId);

    // Update series from "Not Rated" to "TV-PG"
    String targetRating = "TV-PG";
    boolean replace = true;

    SeriesOuterClass.UpdateRatingsResponse response =
        seriesClient.updateRatings(seriesId, targetRating, replace);

    Assert.assertNotNull(response, "No response from updateRatings.");
    Assert.assertEquals(response.getDocument().getId(), seriesId);
    Assert.assertEquals(response.getDocument().getRating(), targetRating);

    Series updatedMongoSeries = seriesUtils.getDocumentById(seriesId);
    Assert.assertEquals(
        updatedMongoSeries.getRating(), targetRating, "Series rating not updated in MongoDB.");

    List<Episodes> updatedEpisodes = episodeUtils.getEpisodesBySeriesIdWithProjection(seriesId);
    for (Episodes ep : updatedEpisodes) {
      Assert.assertEquals(
          ep.getRating(), targetRating, "Episode rating not updated for episode: " + ep.getId());
    }

    List<Clips> updatedClips = clipsUtils.getClipsByIdsWithStandardProjection(clipIds);
    for (Clips clip : updatedClips) {
      if (clip.getPromotional() == null || !clip.getPromotional()) {
        Assert.assertEquals(
            clip.getRating(), targetRating, "Clip rating not updated for clip: " + clip.getId());
      }
    }
  }

  @Test
  @Description("UpdateRatings - clip blocks lower rating when replace=false")
  @TmsLink("29253617")
  public void C29253617_TEST() {
    Series mongoSeries = seriesUtils.getPublishedSeriesWithRestrictiveClipsAndActiveRegion();

    String seriesId = mongoSeries.getId();
    String originalRating = mongoSeries.getRating();
    CommonUtils.printTestCaseNameWithId("WEIGHT_BLOCKS", seriesId);

    List<Episodes> episodes = episodeUtils.getEpisodesBySeriesIdWithProjection(seriesId);
    Assert.assertFalse(episodes.isEmpty(), "No episodes found for series:  " + seriesId);

    List<String> clipIds =
        episodes.stream()
            .filter(ep -> ep.getSources() != null)
            .flatMap(ep -> ep.getSources().stream())
            .map(Source::getClip)
            .filter(java.util.Objects::nonNull)
            .distinct()
            .collect(Collectors.toList());

    Assert.assertFalse(clipIds.isEmpty(), "No clips found for series: " + seriesId);

    // Try lower rating WITHOUT forcing
    String lowerRating = "TV-PG";
    boolean replace = false;

    SeriesOuterClass.UpdateRatingsResponse response =
        seriesClient.updateRatings(seriesId, lowerRating, replace);

    Assert.assertNotNull(response, "No response from updateRatings.");

    // Should block update and return restrictive clips
    Assert.assertTrue(
        response.getClipsWithMoreRestrictiveRatingsCount() > 0,
        "Should have clips with more restrictive ratings");

    // Verify restrictive clip is in the list
    Assert.assertTrue(
        response.getClipsWithMoreRestrictiveRatingsList().size() > 0,
        "Restrictive clips list should not be empty");

    // Verify series was NOT updated
    Series unchangedSeries = seriesUtils.getDocumentById(seriesId);
    Assert.assertEquals(
        unchangedSeries.getRating(),
        originalRating,
        "Series rating should NOT change when clips are more restrictive");

    // Verify episodes were NOT updated
    List<Episodes> unchangedEpisodes = episodeUtils.getEpisodesBySeriesIdWithProjection(seriesId);
    for (Episodes ep : unchangedEpisodes) {
      Assert.assertNotEquals(
          ep.getRating(), lowerRating, "Episode rating should NOT be updated when blocked");
    }
  }
}
