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

import static tv.pluto.mongo.utils.MongoUtils.INVALID_ID_VALUE;

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.StatusRuntimeException;
import io.qameta.allure.Description;
import io.qameta.allure.TmsLink;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.assertj.core.util.Streams;
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.channels.ChannelsClient;
import tv.pluto.grpc.timelines.TimelinesClient;
import tv.pluto.ip.dal.timeline.Timelines.DateFilter;
import tv.pluto.ip.dal.timeline.Timelines.DeleteTimelinesInChannelByDatesResponse;
import tv.pluto.ip.dal.timeline.Timelines.Filter;
import tv.pluto.ip.dal.timeline.Timelines.GetAllByResponse;
import tv.pluto.ip.dal.timeline.Timelines.GetTimelinesLiteByChannelIdResponse;
import tv.pluto.ip.dal.timeline.Timelines.Timeline;
import tv.pluto.mongo.entities.channels.Channels;
import tv.pluto.mongo.entities.episodes.Episodes;
import tv.pluto.mongo.entities.episodes.Source;
import tv.pluto.mongo.entities.series.Series;
import tv.pluto.mongo.entities.timelines.Timelines;
import tv.pluto.mongo.entities.timelines.populated_timelines.Episode;
import tv.pluto.mongo.entities.timelines.populated_timelines.TimelineByChannelId;
import tv.pluto.mongo.utils.ChannelsUtils;
import tv.pluto.mongo.utils.MongoUtils;
import tv.pluto.mongo.utils.SeriesUtils;
import tv.pluto.mongo.utils.TimelinesUtils;
import tv.pluto.mongo.utils.episodes.EpisodeUtils;
import tv.pluto.mongo.utils.populated_timelines.TimelineByChannelIdUtils;
import tv.pluto.utils.CommonUtils;

public class TimelinesTestSuite extends BaseTest {
  private TimelinesClient timelinesClientStub;
  private TimelinesClient timelinesClient;
  private ChannelsClient channelsClient;
  private TimelinesClient timelinesClientAuth;
  private TimelinesUtils timelinesUtils;
  private EpisodeUtils episodeUtils;
  private ChannelsUtils channelsUtils;
  private SeriesUtils seriesUtils;
  private static final String TIMELINES_INVALID_ARGUMENT_MESSAGE =
      "INVALID_ARGUMENT: Invalid parameters provided. 'start' and 'stop' must be valid Date objects. Timeline";
  private static final String NOT_FOUND_MESSAGE = "NOT_FOUND: Timeline not found";
  private static final String NOT_FOUND_DOCUMENT_MESSAGE =
      "NOT_FOUND: Timeline document for requested ID not found";
  private static final String UNKNOWN_ID = "9999952b4ea0c99997f99999";
  private TimelineByChannelIdUtils timelineByChannelIdUtils;
  private Timelines testTimeline;

  @BeforeClass(alwaysRun = true)
  private void setUp() {
    timelinesClientStub = new TimelinesClient(grpcChannelManager.getChannel());
    timelinesClient = new TimelinesClient(grpcChannelManager.getChannel(), false);
    channelsClient = new ChannelsClient(grpcChannelManager.getChannel(), false);
    timelinesClientAuth = new TimelinesClient(grpcChannelManager.getChannel(), true);
    timelinesUtils = new TimelinesUtils();
    episodeUtils = new EpisodeUtils();
    channelsUtils = new ChannelsUtils();
    seriesUtils = new SeriesUtils();
    timelineByChannelIdUtils = new TimelineByChannelIdUtils();
    testTimeline = timelinesUtils.getRandomDocument();
  }

  @Test
  @Description("Valid request - GetTimelineById")
  @TmsLink("28501428")
  public void C28501428_TEST() {
    Timelines mongoTimeline = timelinesUtils.getRandomDocument();

    String randomTimelineId = mongoTimeline.getId();

    Timelines mappedTimeline =
        timelinesUtils.mapGrpcObjectToPojo(
            timelinesClient.getTimelineById(randomTimelineId).getDocument(), Timelines.class);

    Assert.assertEquals(mongoTimeline, mappedTimeline);
  }

  @Test
  @Description("Valid request - GetTimelineById with projections")
  @TmsLink("28501429")
  public void C28501429_TEST() {
    String[] projection = {"_id", "start"};
    Timelines mongoTimeline = timelinesUtils.getRandomDocument(projection);

    String randomTimelineId = mongoTimeline.getId();

    Timelines mappedTimeline =
        timelinesUtils.mapGrpcObjectToPojo(
            timelinesClient.getTimelineById(randomTimelineId, projection).getDocument(),
            Timelines.class);

    Assert.assertEquals(mongoTimeline, mappedTimeline);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_DOCUMENT_MESSAGE)
  @Description("Invalid request - GetTimelineById")
  @TmsLink("28501430")
  public void C28501430_TEST() {
    timelinesClient.getTimelineById(MongoUtils.INVALID_ID_VALUE);
  }

  @Test
  @Description("Valid request with single timeline - GetTimelinesByIds")
  @TmsLink("28501431")
  public void C28501431_TEST() {
    Timelines mongoTimeline = timelinesUtils.getRandomDocument();
    List<String> randomTimelinesIds = Collections.singletonList(mongoTimeline.getId());

    List<Timelines> mongoChannelsList = timelinesUtils.getDocumentsByIds(randomTimelinesIds);
    List<?> mappedChannelsList =
        timelinesUtils.mapGrpcObjectsToPojo(
            timelinesClient.getTimelinesByIds(randomTimelinesIds, null).getDocumentsList(),
            Timelines.class);

    Assert.assertEquals(mongoChannelsList, mappedChannelsList);
  }

  @Test(dataProvider = "start-stop-channel")
  @Description("GetTimelinesByIds - Valid request with projection")
  @TmsLink("28501432")
  public void C28501432_TEST(String projectionField) {
    List<String> projection = List.of(projectionField);
    Timelines mongoTimeline = timelinesUtils.getRandomDocument();
    List<String> randomTimelinesIds = Collections.singletonList(mongoTimeline.getId());

    List<Timelines> mongoChannelsList =
        timelinesUtils.getDocumentsByIdsProjection(randomTimelinesIds, projection);

    List<?> mappedChannelsList =
        timelinesUtils.mapGrpcObjectsToPojo(
            timelinesClient.getTimelinesByIds(randomTimelinesIds, projection).getDocumentsList(),
            Timelines.class);

    Assert.assertEquals(mongoChannelsList, mappedChannelsList);
  }

  @Test
  @Description("Valid request with multiple timelines - GetTimelinesByIds")
  @TmsLink("28501433")
  public void C28501433_TEST() {
    List<String> randomTimelinesIds = timelinesUtils.getRandomTimelinesIds();

    List<Timelines> mongoChannelsList = timelinesUtils.getDocumentsByIds(randomTimelinesIds);
    List<?> mappedChannelsList =
        timelinesUtils.mapGrpcObjectsToPojo(
            timelinesClient.getTimelinesByIds(randomTimelinesIds, null).getDocumentsList(),
            Timelines.class);

    Assert.assertEquals(mongoChannelsList, mappedChannelsList);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Timeline documents for requested IDs not found")
  @Description("Invalid request with single timeline - GetTimelinesByIds")
  @TmsLink("28501434")
  public void C28501434_TEST() {
    List<String> randomTimelinesIds = Collections.singletonList(MongoUtils.INVALID_ID_VALUE);
    timelinesClient.getTimelinesByIds(randomTimelinesIds, null);
  }

  @Test
  @Description("Valid request with invalid timelines - GetTimelinesByIds")
  @TmsLink("28501435")
  public void C28501435_TEST() {
    List<String> randomTimelinesIds = timelinesUtils.getRandomTimelinesIds();
    randomTimelinesIds.add(MongoUtils.INVALID_ID_VALUE);

    List<Timelines> mongoChannelsList = timelinesUtils.getDocumentsByIds(randomTimelinesIds);
    List<?> mappedChannelsList =
        timelinesUtils.mapGrpcObjectsToPojo(
            timelinesClient.getTimelinesByIds(randomTimelinesIds, null).getDocumentsList(),
            Timelines.class);

    Assert.assertEquals(mongoChannelsList, mappedChannelsList);
  }

  @Test
  @Description("GetEmptyTimeline request")
  @TmsLink("28501436")
  public void C28501436_TEST() {
    Assert.assertTrue(timelinesClient.getEmptyTimeline().hasTimeline());
  }

  @Test
  @Description(
      "GetDocumentsUpdatedBetweenDatesOnChannel - Time period for which there is a timeline")
  @TmsLink("28501462")
  public void C28501462_TEST() {
    List<Timelines> initTimelines = timelinesUtils.getDocuments(5);

    List<Timelines> grpcTimelines = new ArrayList<>();
    List<String> channelsIds =
        initTimelines.stream().map(Timelines::getChannel).distinct().collect(Collectors.toList());

    List<Timelines> mongoTimelines = timelinesUtils.getTimelinesByChannelsIds(channelsIds);

    Date start =
        mongoTimelines.stream()
            .min(Comparator.comparing(Timelines::getStart))
            .map(Timelines::getStart)
            .get();

    String startString =
        LocalDateTime.ofInstant(start.toInstant(), ZoneOffset.UTC).minusMinutes(1) + "Z";

    Date stop =
        mongoTimelines.stream()
            .max(Comparator.comparing(Timelines::getStop))
            .map(Timelines::getStop)
            .get();

    String stopString =
        LocalDateTime.ofInstant(stop.toInstant(), ZoneOffset.UTC).plusMinutes(1) + "Z";

    LogUtils.logInfoMessage(
        "Start: " + startString + ", Stop: " + stopString + "ChannelId: " + channelsIds);

    Iterator<Timeline> timelines =
        timelinesClient.getDocumentsUpdatedBetweenDatesOnChannel(
            startString, stopString, channelsIds);

    while (timelines.hasNext()) {
      grpcTimelines.add(timelinesUtils.mapGrpcObjectToPojo(timelines.next(), Timelines.class));
    }

    grpcTimelines.forEach(a -> a.setUpdatedAt(null));
    mongoTimelines.forEach(a -> a.setUpdatedAt(null));

    mongoTimelines.sort(Comparator.comparing(Timelines::getId));
    grpcTimelines.sort(Comparator.comparing(Timelines::getId));

    Assert.assertEquals(mongoTimelines, grpcTimelines);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Timeline not found")
  @Description("GetDocumentsUpdatedBetweenDatesOnChannel - Time period without timeline")
  @TmsLink("28501463")
  public void C28501463_TEST() {
    timelinesClient
        .getDocumentsUpdatedBetweenDatesOnChannel(
            failStartDateTime, failEndDateTime, Collections.emptyList())
        .next();
  }

  @Test(
      dataProvider = "fail-content-feeds-test-data",
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = TIMELINES_INVALID_ARGUMENT_MESSAGE)
  @Description(
      "GetDocumentsUpdatedBetweenDatesOnChannel - Invalid values for start_date_time and end_date_time")
  @TmsLink("28501464")
  public void C28501464_TEST(String startDateTime, String endDateTime) {
    timelinesClient
        .getDocumentsUpdatedBetweenDatesOnChannel(
            startDateTime, endDateTime, Collections.emptyList())
        .next();
  }

  @Test(
      dataProvider = "less-or-equal-fail-content-feeds-test-data",
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_MESSAGE)
  @Description(
      "GetDocumentsUpdatedBetweenDatesOnChannel - end_date_time is less than or equal to start_date_time")
  @TmsLink("28501465")
  public void C28501465_TEST(String startDateTime, String endDateTime) {
    timelinesClient
        .getDocumentsUpdatedBetweenDatesOnChannel(
            startDateTime, endDateTime, Collections.emptyList())
        .next();
  }

  @Test
  @Description("GetAll - filter by existing region")
  @TmsLink("28501474")
  public void C28501474_TEST() {
    String randomActiveRegion =
        episodeUtils.getUniqueActiveRegionFromEpisodes().get(0).toLowerCase();
    int limit = 10;

    Iterator<Timeline> timelineIterator = timelinesClient.getAllResponse(randomActiveRegion);
    List<Timeline> listTimeLines =
        Streams.stream(timelineIterator).limit(limit).collect(Collectors.toList());
    Assert.assertEquals(limit, listTimeLines.size());

    List<Timelines> listTimelines = new ArrayList<>();
    List<Timelines> listMongoTimelines = new ArrayList<>();
    int i = 0;

    while (timelineIterator.hasNext() && i < limit) {
      Timelines timelines =
          timelinesUtils.mapGrpcObjectToPojo(timelineIterator.next(), Timelines.class);
      listTimelines.add(timelines);
      i++;
    }

    listTimelines.forEach(
        timelines -> {
          BasicDBObject query = new BasicDBObject();
          query.put("_id", new ObjectId(timelines.getId()));
          Timelines mongoTimelines = timelinesUtils.collectionTimelines.find(query).first();
          listMongoTimelines.add(mongoTimelines);

          Episodes episode = episodeUtils.getDocumentById(timelines.getEpisode());
          tv.pluto.mongo.entities.channels.Channels mongoChannel =
              channelsUtils.getDocumentById(timelines.getChannel());
          Assert.assertEquals(randomActiveRegion, episode.getActiveRegion());
          Assert.assertEquals(randomActiveRegion, mongoChannel.getActiveRegion());
        });

    listMongoTimelines.sort(Comparator.comparing(Timelines::getId));
    listTimelines.sort(Comparator.comparing(Timelines::getId));

    Assert.assertEquals(listTimelines, listMongoTimelines);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_MESSAGE)
  @Description("GetAll - filter by not existing region")
  @TmsLink("28501475")
  public void C28501475_TEST() {
    timelinesClient.getAllResponse("testRandom").next();
  }

  @Test
  @Description("GetAll - filter by channel id with Timeline")
  @TmsLink("28501479")
  public void C28501479_TEST() {
    Timelines timeline = (Timelines) timelinesUtils.getTimelinesWithChannelId().next();
    List<String> channelId = List.of(timeline.getChannel());
    Iterator<Timeline> grpcTimeLines = timelinesClient.getAllResponseChannelIds(channelId);
    List<Timelines> grpcListTimelines = new ArrayList<>();

    while (grpcTimeLines.hasNext()) {
      Timelines grpcTimeline =
          timelinesUtils.mapGrpcObjectToPojo(grpcTimeLines.next(), Timelines.class);
      grpcListTimelines.add(grpcTimeline);
    }

    List<Timelines> mongoListTimelines = timelinesUtils.getTimelinesForSelectedChannelId(channelId);
    mongoListTimelines.sort(Comparator.comparing(Timelines::getId));
    grpcListTimelines.sort(Comparator.comparing(Timelines::getId));
    Assert.assertTrue(mongoListTimelines.size() > 0);
    Assert.assertEquals(mongoListTimelines, grpcListTimelines);
  }

  @Test
  @Description("GetAll - filter by list of channel ids with Timeline ")
  @TmsLink("28501481")
  public void C28501481_TEST() {
    Iterator timelinesIterator = timelinesUtils.getTimelinesWithChannelId();
    Set<String> setTimelines = new HashSet<>();
    while (timelinesIterator.hasNext()) {
      String channel = ((Timelines) timelinesIterator.next()).getChannel();
      setTimelines.add(channel);
      if (setTimelines.size() > 1) break;
    }

    List<String> listChannelIds = setTimelines.stream().limit(2).collect(Collectors.toList());
    Iterator<Timeline> grpcTimeLines = timelinesClient.getAllResponseChannelIds(listChannelIds);
    List<Timelines> grpcListTimelines = new ArrayList<>();

    while (grpcTimeLines.hasNext()) {
      Timelines grpcTimeline =
          timelinesUtils.mapGrpcObjectToPojo(grpcTimeLines.next(), Timelines.class);
      grpcListTimelines.add(grpcTimeline);
    }

    List<Timelines> mongoListTimelines =
        timelinesUtils.getTimelinesForSelectedChannelId(listChannelIds);
    mongoListTimelines.sort(Comparator.comparing(Timelines::getId));
    grpcListTimelines.sort(Comparator.comparing(Timelines::getId));
    Assert.assertTrue(mongoListTimelines.size() > 0);
    Assert.assertEquals(mongoListTimelines, grpcListTimelines);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_MESSAGE)
  @Description("GetAll - filter by invalid channelIds")
  @TmsLink("28501480")
  public void C28501480_TEST() {
    timelinesClient.getAllResponseChannelIds(List.of("99999999999999a1cd00002f")).next();
  }

  @Test
  @Description("CFeedsSyncAllIds - stream returns CFEEDS stream of Timelines ids")
  @TmsLink("28501476")
  public void C28501476_TEST() {
    String time = LocalDateTime.now(ZoneOffset.UTC).minusHours(5) + "Z";
    Iterator<?> timeLineIterator = timelinesClient.getTimelinesIdsWithLimiterDate(time);
    List<String> grpcTimeLine = new ArrayList<>();

    for (int i = 0; i < 100; i++) {
      String id = ((tv.pluto.ip.dal.timeline.Timelines.Id) timeLineIterator.next()).getId();
      grpcTimeLine.add(id);
    }

    List<String> mongoIds =
        timelinesUtils.getDocumentsByIds(grpcTimeLine).stream()
            .map(o -> ((Timelines) o).getId())
            .sorted()
            .collect(Collectors.toList());

    Assert.assertTrue(mongoIds.size() > 0);
    Assert.assertTrue(CommonUtils.isTwoListsEqual(grpcTimeLine, mongoIds));
  }

  @Test
  @Description("cFeedsSyncDocuments - last_run_at")
  @TmsLink("28501477")
  public void C28501477_TEST() {
    LocalDateTime ranAt = CommonUtils.getNowDateTimeUtc().minusMonths(3);
    LocalDateTime until = CommonUtils.getNowDateTimeUtc();
    Iterator<?> cFeedsSyncAllIds =
        timelinesClient.getTimelinesWithLimiterDate(ranAt + "Z", until + "Z");
    int total = 0;

    while (cFeedsSyncAllIds.hasNext() && total < 50) {
      Timelines grpcTimeline =
          timelinesUtils.mapGrpcObjectToPojo(cFeedsSyncAllIds.next(), Timelines.class);
      LocalDateTime grpcTime = grpcTimeline.getUpdatedAt();
      boolean isTrue =
          grpcTime.isBefore(until)
              || grpcTime.equals(until) && grpcTime.isAfter(ranAt)
              || grpcTime.equals(until);
      Assert.assertTrue(isTrue);
      total++;
    }
  }

  @Test
  @Description("cFeedsSyncDocuments - last_run_at future time")
  @TmsLink("28501478")
  public void C28501478_TEST() {
    try {
      String futureTime = CommonUtils.localDateTimeStringPlusDays(10);
      String futureTimePlus = CommonUtils.localDateTimeStringPlusDays(51);
      Timeline timeline =
          timelinesClient.getTimelinesWithLimiterDate(futureTime, futureTimePlus).next();
      Assert.assertNotNull(timeline);
    } catch (RuntimeException e) {
      // if no data
      Assert.assertEquals(e.getMessage(), "NOT_FOUND: Not found");
    }
  }

  @Test
  @Description("GetOne - returns only 1 sorted timeline")
  @TmsLink("28501485")
  public void C28501485_TEST() {
    List<String> listFields = List.of("start", "stop", "episode", "channel");
    String testDbValue = CommonUtils.getRandomValueFromList(listFields);
    LogUtils.logInfoMessage("testDbValue: " + testDbValue);

    tv.pluto.ip.dal.timeline.Timelines.GetOneResponse grpcOne =
        timelinesClient.getOne(testDbValue, 1);
    Timelines grpcTimelines =
        timelinesUtils.mapGrpcObjectToPojo(grpcOne.getDocument(), Timelines.class);
    Timelines timelinesMangoDb = timelinesUtils.getSortedDocumentByField(testDbValue, 1);

    Assert.assertFalse(grpcTimelines.getId().isEmpty());

    if (testDbValue.equals("start"))
      Assert.assertEquals(grpcTimelines.getStart(), timelinesMangoDb.getStart());
    else Assert.assertEquals(grpcTimelines, timelinesMangoDb);
  }

  @Test(dataProvider = "sort-values", expectedExceptions = StatusRuntimeException.class)
  @Description("GetOne - returns only 1 sorted timeline - error handling")
  @TmsLink("28501486")
  public void C28501486_TEST(int sortValue) {
    String testDbValue = "testDal";
    timelinesClient.getOne(testDbValue, sortValue);
  }

  @Test
  @Description("GetByEpisodeId - returns list of Timelines without start and stop time")
  @TmsLink("28501482")
  public void C28501482_TEST() {
    int limit = 5;
    LogUtils.logInfoMessage("Limit: " + limit);
    TimelinesClient timeClient = new TimelinesClient(grpcChannelManager.getChannel(), false);

    List<tv.pluto.ip.dal.timeline.Timelines.GetAllPagedSort> sort =
        timeClient.getListGetAllPagedSort("_id", 1);

    Timelines timelines = timelinesUtils.getRandomDocumentWithTheField("episode", true);
    tv.pluto.ip.dal.timeline.Timelines.GetByEpisodeIdResponse timeline =
        timeClient.getByEpisodeId(timelines.getEpisode(), limit, 0, sort, null, null, null);
    List<Timelines> listGrpcTimelines =
        timelinesUtils.mapGrpcObjectsToPojo(timeline.getDocumentsList(), Timelines.class);
    List<Timelines> listMangoDb =
        timelinesUtils.getDocumentsByFieldAndId("episode", timelines.getEpisode());

    listGrpcTimelines.sort(Comparator.comparing(Timelines::getId));
    listMangoDb.sort(Comparator.comparing(Timelines::getId));

    SoftAssert softAssert = new SoftAssert();
    softAssert.assertTrue(listGrpcTimelines.size() > 0);

    if (listMangoDb.size() >= limit) softAssert.assertEquals(limit, listGrpcTimelines.size());
    else softAssert.assertEquals(listGrpcTimelines, listMangoDb);

    softAssert.assertAll();
  }

  @Test
  @Description("GetByEpisodeId - returns list of Timelines with start and stop time")
  @TmsLink("28501483")
  public void C28501483_TEST() {
    int limit = CommonUtils.getRandomNumber(10);
    List<tv.pluto.ip.dal.timeline.Timelines.GetAllPagedSort> sort =
        timelinesClient.getListGetAllPagedSort("_id", 1);

    Timelines timelines = timelinesUtils.getRandomDocumentWithTheField("episode", true);
    Instant startBegin = timelines.getStart().toInstant();
    Instant stopBegin = timelines.getStop().toInstant();

    tv.pluto.ip.dal.timeline.Timelines.QueryTimeRange start =
        timelinesClient.timeRangeStartStop(startBegin.toString(), startBegin.toString());
    tv.pluto.ip.dal.timeline.Timelines.QueryTimeRange stop =
        timelinesClient.timeRangeStartStop(stopBegin.toString(), stopBegin.toString());

    tv.pluto.ip.dal.timeline.Timelines.GetByEpisodeIdResponse timeline =
        timelinesClient.getByEpisodeId(timelines.getEpisode(), limit, 0, sort, start, stop, null);

    List<Timelines> listGrpcTimelines =
        timelinesUtils.mapGrpcObjectsToPojo(timeline.getDocumentsList(), Timelines.class);
    List<Timelines> listMangoDb =
        timelinesUtils.getTimelinesBetweenStartEnd(
            timelines.getEpisode(), startBegin, stopBegin, null);
    SoftAssert softAssert = new SoftAssert();
    softAssert.assertTrue(
        !listGrpcTimelines.isEmpty() && listGrpcTimelines.size() <= limit, "limit error");

    listGrpcTimelines.sort(Comparator.comparing(Timelines::getStart));
    listMangoDb.sort(Comparator.comparing(Timelines::getStart));

    softAssert.assertEquals(listGrpcTimelines, listMangoDb);
    softAssert.assertAll();
  }

  @Test
  @Description(
      "GetByEpisodeId - returns list of Timelines with start and stop time with projections")
  @TmsLink("28501484")
  public void C28501484_TEST() {
    String[] projection = {"_id", "start", "stop", "episode"};
    int limit = CommonUtils.getRandomNumber(10);
    List<tv.pluto.ip.dal.timeline.Timelines.GetAllPagedSort> sort =
        timelinesClient.getListGetAllPagedSort("_id", 1);

    Timelines timelines = timelinesUtils.getRandomDocumentWithTheField("episode", true);
    Instant startBegin = timelines.getStart().toInstant();
    Instant stopBegin = timelines.getStop().toInstant();

    tv.pluto.ip.dal.timeline.Timelines.QueryTimeRange start =
        timelinesClient.timeRangeStartStop(startBegin.toString(), startBegin.toString());
    tv.pluto.ip.dal.timeline.Timelines.QueryTimeRange stop =
        timelinesClient.timeRangeStartStop(stopBegin.toString(), stopBegin.toString());

    tv.pluto.ip.dal.timeline.Timelines.GetByEpisodeIdResponse timeline =
        timelinesClient.getByEpisodeId(
            timelines.getEpisode(), limit, 0, sort, start, stop, projection);

    List<Timelines> listGrpcTimelines =
        timelinesUtils.mapGrpcObjectsToPojo(timeline.getDocumentsList(), Timelines.class);
    List<Timelines> listMangoDb =
        timelinesUtils.getTimelinesBetweenStartEnd(
            timelines.getEpisode(), startBegin, stopBegin, projection);
    SoftAssert softAssert = new SoftAssert();
    softAssert.assertTrue(
        !listGrpcTimelines.isEmpty() && listGrpcTimelines.size() <= limit, "limit error");

    listGrpcTimelines.sort(Comparator.comparing(Timelines::getStart));
    listMangoDb.sort(Comparator.comparing(Timelines::getStart));

    softAssert.assertEquals(listGrpcTimelines, listMangoDb);
    softAssert.assertAll();
  }

  @Test
  @Description("Count - start/stop count")
  @TmsLink("28501487")
  public void C28501487_TEST() {
    Timelines timelines = timelinesUtils.getRandomDocument();
    Instant startTime = timelines.getStart().toInstant();
    Instant stopTime = startTime.plus(1, ChronoUnit.MINUTES);

    long grpCount = timelinesClient.getCount(startTime.toString(), stopTime.toString()).getCount();
    long countDBBetweenDates = timelinesUtils.getDocumentsBetweenDates(startTime, stopTime).size();
    LogUtils.logInfoMessage("grpCount: " + grpCount);

    Assert.assertEquals(countDBBetweenDates, grpCount);
  }

  @Test
  @Description("GetTimelinesBetweenDates - between valid dates")
  @TmsLink("28501492")
  public void C28501492_TEST() {
    Timelines randomTimeline = timelinesUtils.getRandomDocument();
    Instant startTime = randomTimeline.getStart().toInstant();
    Instant stopTime = startTime.plusSeconds(1);

    Iterator<Timeline> timelinesBetweenDates =
        timelinesClient.getTimelinesBetweenDates(startTime.toString(), stopTime.toString());
    List listTimelinesGrpc = CommonUtils.iteratorToList(timelinesBetweenDates);
    List<Timelines> grpcListTimelines =
        timelinesUtils.mapGrpcObjectsToPojo(listTimelinesGrpc, Timelines.class);
    List<Timelines> mongoTimelinesBetweenDates =
        timelinesUtils.getDocumentsBetweenDates(startTime, stopTime);

    grpcListTimelines.sort(Comparator.comparing(Timelines::getId));
    mongoTimelinesBetweenDates.sort(Comparator.comparing(Timelines::getId));

    Assert.assertEquals(grpcListTimelines, mongoTimelinesBetweenDates);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Timeline not found")
  @Description("GetTimelinesBetweenDates - Timeline not found")
  @TmsLink("28501493")
  public void C28501493_TEST() {
    Timelines randomTimeline = timelinesUtils.getRandomDocument();
    Instant startTime = randomTimeline.getStart().toInstant();
    Instant stopTime = startTime.plusSeconds(1);

    timelinesClient.getTimelinesBetweenDates(stopTime.toString(), startTime.toString()).next();
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Timeline documents not found")
  @Description("Count - start/stop future time. Error handling")
  @TmsLink("28501488")
  public void C28501488_TEST() {
    Instant startTime = Instant.parse("2098-09-03T18:46:18.390988Z");
    Instant stopTime = startTime.plus(1, ChronoUnit.DAYS);
    timelinesClient.getCount(startTime.toString(), stopTime.toString());
  }

  @Test
  @Description("DeleteById - valid Id with authentication")
  @TmsLink("28501489")
  public void C28501489_TEST() {
    Timelines timelines = timelinesUtils.getRandomDocument();
    tv.pluto.ip.dal.timeline.Timelines.DeleteByIdResponse deleteByIdResponse =
        timelinesClientAuth.deleteById(timelines.getId());
    Assert.assertEquals(1, deleteByIdResponse.getDeletedCount());
    Assert.assertNull(timelinesUtils.getDocumentById(timelines.getId()));
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "UNAUTHENTICATED: DB deletes not allowed without authentication")
  @Description("DeleteById - valid Id without authentication")
  @TmsLink("28501491")
  public void C28501491_TEST() {
    Timelines timelines = timelinesUtils.getRandomDocument();
    timelinesClient.deleteById(timelines.getId());
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Timeline documents for requested IDs not found")
  @Description("DeleteById - invalid Id")
  @TmsLink("28501490")
  public void C28501490_TEST() {
    timelinesClientAuth.deleteById(UNKNOWN_ID);
  }

  @Test
  @Description("GetTimelinesForAutoSchedule - start and stop for channel id")
  @TmsLink("28501494")
  public void C28501494_TEST() {
    Timelines timelines = timelinesUtils.getRandomDocument();
    Instant start = timelines.getStart().toInstant().minus(200, ChronoUnit.DAYS);
    Instant stop = timelines.getStart().toInstant().plus(1, ChronoUnit.DAYS);

    tv.pluto.ip.dal.timeline.Timelines.GetTimelinesForAutoScheduleResponse timelineResponse =
        timelinesClient.getTimelinesForAutoSchedule(timelines.getChannel(), start, stop);
    LogUtils.logInfoMessage("Number of timelines: " + timelineResponse.getTimelinesCount());
    Assert.assertTrue(timelineResponse.getTimelinesCount() > 0, "No timelines in the response");

    timelineResponse
        .getTimelinesList()
        .forEach(
            line -> {
              Assert.assertEquals(timelines.getChannel(), line.getChannel());

              Instant responseTimelineStart = Instant.parse(line.getStart());
              Instant responseTimelineStop = Instant.parse(line.getStop());

              boolean isStart = CommonUtils.isDateBetweenDates(start, stop, responseTimelineStart);
              boolean isStop = CommonUtils.isDateBetweenDates(start, stop, responseTimelineStop);

              Assert.assertTrue(isStart);
              Assert.assertTrue(isStop);
            });
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "NOT_FOUND: Timelines not found by channel, start and stop date")
  @Description("GetTimelinesForAutoSchedule - start and stop for channel id old date")
  @TmsLink("28501495")
  public void C28501495_TEST() {
    Timelines timelines = timelinesUtils.getRandomDocument();
    Instant start = Instant.parse("1900-02-06T00:00:00.000Z");
    Instant stop = start.plus(1, ChronoUnit.DAYS);

    timelinesClient.getTimelinesForAutoSchedule(timelines.getChannel(), start, stop);
  }

  @Test
  @Description("GetTimelinesForRepeatSchedule - verify the timelines return")
  @TmsLink("28501496")
  public void C28501496_TEST() {
    Series series = seriesUtils.getDocumentById("55af18da27f18b7f8bc270dc");
    Instant timeNow = Instant.now();
    Instant timeStart = timeNow.minus(60, ChronoUnit.DAYS);
    String channelId = series.getChannels().get(0).toString();

    tv.pluto.ip.dal.timeline.Timelines.GetTimeslinesForRepeatScheduleParams.Builder paramsBuilder =
        tv.pluto.ip.dal.timeline.Timelines.GetTimeslinesForRepeatScheduleParams.newBuilder()
            .setSeries(series.getId())
            .setRepeatInterval("day")
            .setRepeatPeriod(1)
            .setEnd("date")
            .setEndDate(timeNow.toString());

    tv.pluto.ip.dal.timeline.Timelines.GetTimelinesForRepeatScheduleResponse
        getTimelinesForRepeatSchedule =
            timelinesClient.getTimelinesForRepeatSchedule(channelId, timeStart, paramsBuilder);

    Assert.assertFalse(getTimelinesForRepeatSchedule.getTimelinesList().isEmpty());

    getTimelinesForRepeatSchedule
        .getTimelinesList()
        .forEach(
            timeline -> {
              Instant start = Instant.parse(timeline.getStart());
              boolean isStart = CommonUtils.isDateBetweenDates(timeStart, timeNow, start);

              Assert.assertEquals(channelId, timeline.getChannel());
              Assert.assertTrue(isStart);
              Assert.assertNotNull(timeline.getEpisode());
              Assert.assertNotNull(timeline.getStop());
            });
  }

  @Test(expectedExceptions = StatusRuntimeException.class)
  @Description("GetTimelinesForRepeatSchedule - invalid channel id")
  @TmsLink("28501497")
  public void C28501497_TEST() {
    Series series = seriesUtils.getDocumentById("60e7fed9f836b1001374dd1e");
    Instant timeNow = Instant.now();
    Instant timeStart = timeNow.minus(60, ChronoUnit.DAYS);

    tv.pluto.ip.dal.timeline.Timelines.GetTimeslinesForRepeatScheduleParams.Builder paramsBuilder =
        tv.pluto.ip.dal.timeline.Timelines.GetTimeslinesForRepeatScheduleParams.newBuilder()
            .setSeries(series.getId())
            .setRepeatInterval("day")
            .setRepeatPeriod(1)
            .setEnd("date")
            .setEndDate(timeNow.toString());

    timelinesClient.getTimelinesForRepeatSchedule(UNKNOWN_ID, timeStart, paramsBuilder);
  }

  @Test
  @Description("UpdateTimelinesBulk - delete")
  @TmsLink("28501498")
  public void C28501498_TEST() {
    List<Timelines> listTimelines = timelinesUtils.getDocuments(2);
    List<String> listIds = List.of(listTimelines.get(0).getId(), listTimelines.get(1).getId());
    timelinesClient.updateTimelinesBulk(listIds, null);

    List<Timelines> documentsByIds = timelinesUtils.getDocumentsByIds(listIds);
    Assert.assertTrue(documentsByIds.isEmpty());
  }

  @Test
  @Description("UpdateTimelinesBulk - create new timeline")
  @TmsLink("28501499")
  public void C28501499_TEST() {
    Timelines timelines = timelinesUtils.getRandomDocument();
    String newChannel = timelines.getEpisode();
    String newEpisode = timelines.getChannel();

    Timeline timeline = timelinesClient.getTimelineById(timelines.getId()).getDocument();
    Timeline newTimeline =
        timeline.toBuilder().setChannel(newChannel).setEpisode(newEpisode).build();

    timelinesClient.updateTimelinesBulk(null, newTimeline);

    BasicDBObject query = new BasicDBObject();
    query.put("channel", new ObjectId(newChannel));
    query.put("episode", new ObjectId(newEpisode));

    List<Timelines> listTimelines = timelinesUtils.getDocuments(query, 10);
    LogUtils.logInfoMessage("Total timelines: " + listTimelines.size());
    String testTimeline = listTimelines.get(0).getId();
    tv.pluto.ip.dal.timeline.Timelines.GetByIdResponse timelineByIdBefore =
        timelinesClient.getTimelineById(testTimeline);
    timelinesClient.updateTimelinesBulk(List.of(testTimeline), null);

    try {
      timelinesClient.getTimelineById(testTimeline);
      Assert.fail("Timeline wasn't removed");
    } catch (RuntimeException e) {
      Assert.assertEquals(
          e.getMessage(), "NOT_FOUND: Timeline document for requested ID not found");
    }

    // revert back the TimelineId
    timelinesClient.updateTimelinesBulk(null, timeline);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INVALID_ARGUMENT: DeleteIds and/or CreateObjects parameters cannot be empty.")
  @Description("UpdateTimelinesBulk - empty body")
  @TmsLink("28501500")
  public void C28501500_TEST() {
    timelinesClient.updateTimelinesBulk(null, null);
  }

  @Test(enabled = false) // TODO Should be enabled for 1.26.0
  @Description("GetPopulatedTimelinesByChannelId - existing ID")
  @TmsLink("28501501")
  public void C28501501_TEST() {
    Timelines timeline = (Timelines) timelinesUtils.getTimelinesWithChannelId().next();

    String channelId = timeline.getChannel();
    long start = timeline.getStart().getTime();
    long stop = timeline.getStop().getTime();

    LogUtils.logInfoMessage("Running test case C23910948_TEST");
    LogUtils.logInfoMessage("ChannelID = " + channelId);
    LogUtils.logInfoMessage("start = " + start);
    LogUtils.logInfoMessage("stop = " + stop);

    List<TimelineByChannelId> timelinesGrpc =
        timelinesUtils.mapGrpcObjectsToPojo(
            timelinesClient
                .getPopulatedTimelinesByChannelId(channelId, start, stop, Collections.emptyList())
                .getDocumentsList(),
            TimelineByChannelId.class);

    List<TimelineByChannelId> timelinesMongo =
        timelineByChannelIdUtils.getPopulatedTimelinesByChannelId(
            channelId, start, stop, Collections.emptyList());

    Assert.assertEquals(timelinesMongo, timelinesGrpc);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "INVALID_ARGUMENT: Channel ID cannot be undefined or empty")
  @Description("GetPopulatedTimelineByChannelId - empty ID")
  @TmsLink("28501502")
  public void C28501502_TEST() {
    timelinesClient.getPopulatedTimelinesByChannelId("", 0, 0, Collections.emptyList());
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Timeline document for requested ID not found")
  @Description("GetPopulatedTimelinesByChannelId - non existing ID")
  @TmsLink("28501503")
  public void C28501503_TEST() {
    timelinesClient.getPopulatedTimelinesByChannelId(
        INVALID_ID_VALUE, 0, 0, Collections.emptyList());
  }

  @Test
  @Description("GetPopulatedTimelinesByChannelId - existing ID and filter by timelinesToExclude")
  @TmsLink("28501504")
  public void C28501504_TEST() {
    Timelines timeline = (Timelines) timelinesUtils.getTimelinesWithChannelId().next();

    String channelId = timeline.getChannel();
    long start = timeline.getStart().getTime();
    long stop = timeline.getStop().getTime();

    LogUtils.logInfoMessage("Running test case C23910948_TEST");
    LogUtils.logInfoMessage("ChannelID = " + channelId);
    LogUtils.logInfoMessage("start = " + start);
    LogUtils.logInfoMessage("stop = " + stop);
    LogUtils.logInfoMessage("Timelines id: " + timeline.getId());

    List<TimelineByChannelId> timelinesGrpc =
        timelinesUtils.mapGrpcObjectsToPojo(
            timelinesClient
                .getPopulatedTimelinesByChannelId(channelId, start, stop, Collections.emptyList())
                .getDocumentsList(),
            TimelineByChannelId.class);

    List<TimelineByChannelId> timelinesMongo =
        timelineByChannelIdUtils.getPopulatedTimelinesByChannelId(
            channelId, start, stop, Collections.emptyList());

    // To do. Need to check some test data if remove Not Rated line
    timelinesMongo.forEach(d -> d.getEpisode().setRating("Not Rated"));
    timelinesGrpc.forEach(d -> d.getEpisode().setRating("Not Rated"));

    Assert.assertEquals(timelinesMongo, timelinesGrpc);
  }

  @Test
  @Description("GetPopulatedTimelinesByChannelId - episode.rating exist")
  @TmsLink("28501505")
  public void C28501505_TEST() {
    Timelines timeline = timelinesUtils.getRandomDocument();
    Channels channel = channelsUtils.getDocumentById(timeline.getChannel());

    if (!channel.getPublished()) {
      tv.pluto.ip.dal.channels.Channels.Channel chan =
          tv.pluto.ip.dal.channels.Channels.Channel.newBuilder().setPublished(true).build();
      tv.pluto.ip.dal.channels.Channels.UpdateFieldsResponse response =
          channelsClient.getUpdateFieldsResponse(channel.getId(), chan, List.of("published"));
      Assert.assertTrue(response.getDocument().getPublished());
    }

    String channelId = timeline.getChannel();
    long start = timeline.getStart().getTime();
    long stop = timeline.getStop().getTime();

    List<TimelineByChannelId> timelinesGrpc =
        timelinesUtils.mapGrpcObjectsToPojo(
            timelinesClient
                .getPopulatedTimelinesByChannelId(channelId, start, stop, Collections.emptyList())
                .getDocumentsList(),
            TimelineByChannelId.class);

    Episode episode = timelinesGrpc.get(0).getEpisode();
    Episodes episodesDb = episodeUtils.getDocumentById(episode.getId());
    Assert.assertEquals(episodesDb.getRating(), episode.getRating());
    Assert.assertNotNull(episodesDb.getRating());
  }

  @Test
  @Description("DeleteTimelinesInChannelUsingDates - happy path")
  @TmsLink("28501506")
  public void C28501506_TEST() {
    Timelines timeline = timelinesUtils.getRandomDocument();
    Instant stop = timeline.getStop().toInstant().plusMillis(1);

    BasicDBObject query = new BasicDBObject();
    query.put("channel", new ObjectId(timeline.getChannel()));
    query.put("start", new BasicDBObject("$gte", timeline.getStart()));
    query.put("stop", new BasicDBObject("$lt", stop));

    List<Timelines> listTimelines = timelinesUtils.getDocuments(query, 500);

    Assert.assertTrue(listTimelines.size() > 0);

    DeleteTimelinesInChannelByDatesResponse response =
        timelinesClient.deleteTimelinesInChannelByDatesRequest(
            timeline.getChannel(), timeline.getStart().toInstant().toString(), stop.toString());

    Assert.assertEquals(response.getDeletedCount(), listTimelines.size());
    Assert.assertNull(timelinesUtils.getDocumentById(timeline.getId()));
  }

  @Test
  @Description("DeleteTimelinesInChannelUsingDates - invalid channel")
  @TmsLink("28501507")
  public void C28501507_TEST() {
    Timelines timeline = timelinesUtils.getRandomDocument();
    Instant stop = timeline.getStop().toInstant().plusMillis(1);

    DeleteTimelinesInChannelByDatesResponse response =
        timelinesClient.deleteTimelinesInChannelByDatesRequest(
            "00000000dbdaaa0000000000",
            timeline.getStart().toInstant().toString(),
            stop.toString());
    Assert.assertEquals(0, response.getDeletedCount());
  }

  @Test
  @Description("DeleteTimelinesInChannelUsingDates - start date and stop dates are the same")
  @TmsLink("28501508")
  public void C28501508_TEST() {
    Timelines timeline = timelinesUtils.getRandomDocument();

    BasicDBObject query = new BasicDBObject();
    query.put("channel", new ObjectId(timeline.getChannel()));
    query.put("start", new BasicDBObject("$gte", timeline.getStart()));
    query.put("stop", new BasicDBObject("$lt", timeline.getStart()));

    List<Timelines> listTimelines = timelinesUtils.getDocuments(query, 500);

    Assert.assertEquals(listTimelines.size(), 0);
    String start = timeline.getStart().toInstant().toString();

    DeleteTimelinesInChannelByDatesResponse response =
        timelinesClient.deleteTimelinesInChannelByDatesRequest(timeline.getChannel(), start, start);

    Assert.assertEquals(response.getDeletedCount(), 0);
  }

  @Test
  @Description("Create - a new Timeline with authentication token")
  @TmsLink("28501509")
  public void C28501509_TEST() {
    Timeline timelineBefore = timelinesClient.getOne("start", 1).getDocument();
    Timeline timeline = timelineBefore.toBuilder().clearId().clearCreatedAt().build();
    Timeline createdTimeline =
        new TimelinesClient(grpcChannelManager.getChannel(), true)
            .createTimeline(timeline)
            .getDocument();

    Assert.assertFalse(createdTimeline.getId().isEmpty());
    Assert.assertEquals(timeline.getChannel(), createdTimeline.getChannel());
    Assert.assertEquals(timeline.getEpisode(), createdTimeline.getEpisode());
    Assert.assertEquals(timeline.getFeatured(), createdTimeline.getFeatured());
    Assert.assertEquals(
        timeline.getManualOverrideMarker(), createdTimeline.getManualOverrideMarker());
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "UNAUTHENTICATED: DB writes not allowed without authentication")
  @Description("Create - a new Timeline without the authentication token")
  @TmsLink("28501510")
  public void C28501510_TEST() {
    Timeline timeline = timelinesClient.getOne("start", 1).getDocument();
    timelinesClient.createTimeline(timeline).getDocument();
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "INTERNAL.*")
  @Description("Create - a new Timeline with authentication token no episode")
  @TmsLink("28501511")
  public void C28501511_TEST() {
    Timeline timelineBefore = timelinesClient.getOne("start", 1).getDocument();
    Timeline timeline =
        timelineBefore.toBuilder().clearId().clearCreatedAt().clearEpisode().build();
    timelinesClientAuth.createTimeline(timeline);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "INTERNAL.*")
  @Description("Create - a new Timeline with authentication token no channel")
  @TmsLink("28501512")
  public void C28501512_TEST() {
    Timeline timelineBefore = timelinesClient.getOne("start", 1).getDocument();
    Timeline timeline =
        timelineBefore.toBuilder().clearId().clearCreatedAt().clearChannel().build();
    timelinesClientAuth.createTimeline(timeline);
  }

  @Test
  @Description("GetTimelinesByChannelId - happy path")
  @TmsLink("28501513")
  public void C28501513_TEST() {
    Timelines timelineDraft = timelinesUtils.getRandomDocument();
    Instant stop = timelineDraft.getStop().toInstant().plusSeconds(5);

    BasicDBObject query = new BasicDBObject();
    query.put("channel", new ObjectId(timelineDraft.getChannel()));
    query.put("start", new BasicDBObject("$gte", timelineDraft.getStart()));
    query.put("stop", new BasicDBObject("$lt", stop));

    List<Timelines> listTimelines = timelinesUtils.getDocuments(query, 500);

    Assert.assertTrue(listTimelines.size() > 0);

    tv.pluto.ip.dal.timeline.Timelines.GetTimelinesByChannelIdResponse timelinesByChannelId =
        timelinesClient.getTimelinesByChannelId(
            timelineDraft.getChannel(),
            timelineDraft.getStart().toInstant().toString(),
            stop.toString());

    List<Timelines> timelineDraftsList =
        timelinesUtils.mapGrpcObjectsToPojo(
            timelinesByChannelId.getDocumentsList(), Timelines.class);

    Assert.assertFalse(timelineDraftsList.isEmpty());

    listTimelines.sort(Comparator.comparing(Timelines::getId));
    timelineDraftsList.sort(Comparator.comparing(Timelines::getId));

    Assert.assertEquals(timelineDraftsList.size(), listTimelines.size());
    Assert.assertEquals(timelineDraftsList, listTimelines);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_DOCUMENT_MESSAGE)
  @Description("GetTimelinesByChannelId - invalid channelId")
  @TmsLink("28501514")
  public void C28501514_TEST() {
    Timelines timeline = timelinesUtils.getRandomDocument();
    Instant stop = timeline.getStop().toInstant().plusMillis(1);

    tv.pluto.ip.dal.timeline.Timelines.GetTimelinesByChannelIdResponse response =
        timelinesClient.getTimelinesByChannelId(
            "00000000dbdaaa0000000000",
            timeline.getStart().toInstant().toString(),
            stop.toString());
    Assert.assertEquals(0, response.getDocumentsList().size());
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_DOCUMENT_MESSAGE)
  @Description("GetTimelinesByChannelId - start date and stop dates are the same")
  @TmsLink("28501515")
  public void C28501515_TEST() {
    Timelines timelineDraft = timelinesUtils.getRandomDocument();

    BasicDBObject query = new BasicDBObject();
    query.put("channel", new ObjectId(timelineDraft.getChannel()));
    query.put("start", new BasicDBObject("$gte", timelineDraft.getStart()));
    query.put("stop", new BasicDBObject("$lt", timelineDraft.getStart()));

    List<Timelines> listTimelines = timelinesUtils.getDocuments(query, 500);

    Assert.assertEquals(listTimelines.size(), 0);
    String start = timelineDraft.getStart().toInstant().toString();

    timelinesClient.getTimelinesByChannelId(timelineDraft.getChannel(), start, start);
  }

  @Test
  @Description("GetTimelinesByChannelId - happy path no stop")
  @TmsLink("28501516")
  public void C28501516_TEST() {
    Timelines timelineDraft = timelinesUtils.getSortedDocumentByField("start", -1);
    BasicDBObject query = new BasicDBObject();
    query.put("channel", new ObjectId(timelineDraft.getChannel()));
    query.put("start", new BasicDBObject("$gte", timelineDraft.getStart()));

    List<Timelines> listTimelines = timelinesUtils.getDocuments(query, 99000);

    Assert.assertTrue(listTimelines.size() > 0);

    tv.pluto.ip.dal.timeline.Timelines.GetTimelinesByChannelIdResponse timelinesByChannelId =
        timelinesClient.getTimelinesByChannelId(
            timelineDraft.getChannel(), timelineDraft.getStart().toInstant().toString(), "");

    List<Timelines> timelineDraftsList =
        timelinesUtils.mapGrpcObjectsToPojo(
            timelinesByChannelId.getDocumentsList(), Timelines.class);

    Assert.assertFalse(timelineDraftsList.isEmpty());

    listTimelines.sort(Comparator.comparing(Timelines::getId));
    timelineDraftsList.sort(Comparator.comparing(Timelines::getId));

    Assert.assertEquals(timelineDraftsList.size(), listTimelines.size());
    Assert.assertEquals(timelineDraftsList, listTimelines);
  }

  @Test
  @Description("GetTimelineIdsClipId - get all timelines with the clipId - published")
  @TmsLink("28501517")
  public void C28501517_TEST() {
    Channels channels = channelsUtils.aggregatePublishedChannelsEpisodesTimelines(true);
    List<Episodes> listEpisodes =
        episodeUtils.getDocumentsByFieldAndId("channels", channels.getId());
    LogUtils.logInfoMessage("listEpisodes size: " + listEpisodes.size());

    List<String> episodeIds =
        listEpisodes.stream().map(Episodes::getId).limit(99999).collect(Collectors.toList());
    List<Timelines> listTimelines = timelinesUtils.getDocumentsByIds("episode", episodeIds);
    LogUtils.logInfoMessage("list Timelines size: " + listTimelines.size());

    Timelines testTimeline = listTimelines.get(0);
    LogUtils.logInfoMessage("Timeline id: " + testTimeline.getId());

    List<Episodes> listEpisodeTimeline =
        listEpisodes.stream()
            .filter(e -> e.getId().equals(testTimeline.getEpisode()))
            .collect(Collectors.toList());

    List<String> listClips =
        listEpisodeTimeline.get(0).getSources().stream()
            .map(Source::getClip)
            .collect(Collectors.toList());
    Assert.assertFalse(listClips.isEmpty());

    Instant testTime = testTimeline.getStart().toInstant().minusSeconds(1);
    String clipId = listClips.get(0);
    LogUtils.logInfoMessage("ClipId: " + clipId);

    tv.pluto.ip.dal.timeline.Timelines.GetTimelineIdsByClipIdResponse timelineByClipId =
        timelinesClient.getTimelineByClipId(clipId, testTime.toString());
    Assert.assertTrue(timelineByClipId.getTimelineIdsCount() > 0);

    List<Timelines> listTimelinesFromResponse =
        timelinesUtils.getDocumentsByIds(timelineByClipId.getTimelineIdsList());
    List<Timelines> filteredTimelines =
        listTimelinesFromResponse.stream()
            .filter(t -> t.getStart().toInstant().isBefore(testTime))
            .collect(Collectors.toList());
    Assert.assertTrue(filteredTimelines.isEmpty());
  }

  @Test(expectedExceptions = RuntimeException.class)
  @Description("GetTimelineIdsClipId - future time")
  @TmsLink("28501518")
  public void C28501518_TEST() {
    String episodeId = episodeUtils.getIdOfEpisodeWithSources();
    Episodes episodeWithSource = episodeUtils.getDocumentById(episodeId);
    String clipId = episodeWithSource.getSources().get(0).getClip();
    LogUtils.logInfoMessage("C24646429_TEST. ClipId: " + clipId);

    Instant start = Instant.parse("2050-01-06T00:00:00.000Z");
    new TimelinesClient(grpcChannelManager.getChannel(), false)
        .getTimelineByClipId(clipId, start.toString());
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: No episodes found for provided clip ID")
  @Description("GetTimelineIdsClipId - NOT_FOUND: No episodes found for provided clip ID")
  @TmsLink("28500174")
  public void C28500174_TEST() {
    String inValidClipId = "65e8cf2393dd4e001397000d";
    Instant start = Instant.parse("2022-01-06T00:00:00.000Z");
    timelinesClient.getTimelineByClipId(inValidClipId, start.toString());
  }

  @Test
  @Description("GetTimelinesByChannelId - channel, start, stop")
  @TmsLink("28501520")
  public void C28501520_TEST() {
    Timelines timelines = timelinesUtils.getRandomDocument();
    Instant start = timelines.getStart().toInstant();
    Instant stop = timelines.getStop().toInstant();
    LogUtils.logInfoMessage(
        "ChannelId: "
            + timelines.getChannel()
            + ", start: "
            + start.toEpochMilli()
            + ", stop: "
            + stop.toEpochMilli());

    GetTimelinesLiteByChannelIdResponse request =
        timelinesClient.getTimelinesLiteByChannelIdRequest(
            timelines.getChannel(), start.toEpochMilli(), stop.toEpochMilli());
    tv.pluto.ip.dal.timeline.Timelines.TimelineLite timelineLite =
        request.getDocumentsList().get(0);
    Assert.assertEquals(timelines.getEpisode(), timelineLite.getEpisode().getId());

    long startResponse = Instant.parse(timelineLite.getStart()).toEpochMilli();
    long stopResponse = Instant.parse(timelineLite.getStop()).toEpochMilli();

    Assert.assertTrue(start.toEpochMilli() <= startResponse);
    Assert.assertTrue(stop.toEpochMilli() <= stopResponse);
  }

  @Test
  @Description("GetTimelinesByChannelId - channel, start")
  @TmsLink("28501519")
  public void C28501519_TEST() {
    Timelines timelines = timelinesUtils.getRandomDocument();
    Instant start = timelines.getStart().toInstant();
    LogUtils.logInfoMessage(
        "ChannelId: " + timelines.getChannel() + ", start: " + start.toEpochMilli());

    GetTimelinesLiteByChannelIdResponse request =
        timelinesClient.getTimelinesLiteByChannelIdRequest(
            timelines.getChannel(), start.toEpochMilli(), null);

    request
        .getDocumentsList()
        .forEach(
            timelineLite -> {
              long startResponse = Instant.parse(timelineLite.getStart()).toEpochMilli();

              Assert.assertTrue(start.toEpochMilli() <= startResponse);
            });
  }

  @Test
  @Description("GetTimelinesByChannelId - channel")
  @TmsLink("28501521")
  public void C28501521_TEST() {
    Timelines timelines = timelinesUtils.getRandomDocument();
    LogUtils.logInfoMessage("ChannelId: " + timelines.getChannel());

    GetTimelinesLiteByChannelIdResponse request =
        timelinesClient.getTimelinesLiteByChannelIdRequest(timelines.getChannel(), null, null);
    Set<String> setGrpcIds =
        request.getDocumentsList().stream().map(t -> t.getId()).collect(Collectors.toSet());

    List<Timelines> timelinesList =
        timelinesUtils.getDocumentsByFieldAndId("channel", timelines.getChannel());
    Set<String> setIds = timelinesList.stream().map(t -> t.getId()).collect(Collectors.toSet());

    Assert.assertEquals(setIds, setGrpcIds);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "INVALID_ARGUMENT: Channel ID cannot be undefined or empty")
  @Description("GetTimelinesByChannelId - no channel")
  @TmsLink("28501522")
  public void C28501522_TEST() {
    timelinesClient.getTimelinesLiteByChannelIdRequest(null, null, null);
  }

  @Test
  @Description("GetDocumentsBetweenStartDates - verify start time")
  @TmsLink("28501523")
  public void C28501523_TEST() {
    Timelines mongoTimeline = timelinesUtils.getRandomDocument();
    String channelId = mongoTimeline.getChannel();
    LogUtils.logInfoMessage("Test channel id: " + channelId);

    Instant start = mongoTimeline.getStart().toInstant();
    String startString = start.toString();
    Instant end = start.plusSeconds(3600);
    String endString = end.toString();

    Iterator<Timeline> docs =
        timelinesClient.getDocumentsBetweenStartDates(startString, endString, channelId);
    Assert.assertTrue(docs.hasNext());

    while (docs.hasNext()) {
      Timeline timelines = docs.next();
      Instant startTimeline = Instant.parse(timelines.getStart());

      boolean isWithinTimeline = start.equals(startTimeline) || startTimeline.isAfter(start);
      boolean isWithinTimelineEnd = start.equals(end) || end.isAfter(start);

      Assert.assertEquals(channelId, timelines.getChannel());
      Assert.assertTrue(isWithinTimeline);
      Assert.assertTrue(isWithinTimelineEnd);
    }
  }

  @Test
  @Description("GetDocumentsBetweenStartDates - cancel stream")
  @TmsLink("28501527")
  public void C28501527_TEST() {
    Timelines mongoTimeline = timelinesUtils.getRandomDocument();
    String channelId = mongoTimeline.getChannel();
    LogUtils.logInfoMessage("Test channel id: " + channelId);

    Instant start = mongoTimeline.getStart().toInstant();
    String startString = start.toString();
    Instant end = start.plusSeconds(3600);
    String endString = end.toString();

    Iterator<Timeline> docs =
        timelinesClientStub.getDocumentsBetweenStartDatesCancel(startString, endString, channelId);
    Assert.assertFalse(docs.hasNext());
  }

  @Test(expectedExceptions = RuntimeException.class)
  @Description("GetDocumentsBetweenStartDates - no channel")
  @TmsLink("28501524")
  public void C28501524_TEST() {
    String startString = "2024-05-04T19:53:00Z";
    String endString = "2024-05-04T19:53:00Z";
    timelinesClient.getDocumentsBetweenStartDates(startString, endString, "").next();
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INVALID_ARGUMENT: Invalid parameters provided. 'start_begin' and 'start_end' must be valid Date objects. Timeline")
  @Description("GetDocumentsBetweenStartDates - no start_begin")
  @TmsLink("28501525")
  public void C28501525_TEST() {
    timelinesClient
        .getDocumentsBetweenStartDates("", Instant.now().toString(), testTimeline.getId())
        .next();
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INVALID_ARGUMENT: Invalid parameters provided. 'start_begin' and 'start_end' must be valid Date objects. Timeline")
  @Description("GetDocumentsBetweenStartDates - no start_end")
  @TmsLink("28501526")
  public void C28501526_TEST() {
    timelinesClient
        .getDocumentsBetweenStartDates("2000-05-04T19:53:00Z", "", testTimeline.getId())
        .next();
  }

  @Test(groups = {"ttfb"})
  @Description("Check TTFB 90 percentile for the TimelineManager GetById operation.")
  @TmsLink("28501528")
  public void C28501528_TEST_TTFB() {
    Timelines mongoTimeline = timelinesUtils.getRandomDocument();

    String randomTimelineId = mongoTimeline.getId();

    GrpcCall grpcCall =
        new GrpcCall(
            "TimelineManager.GetById", () -> timelinesClient.getTimelineById(randomTimelineId));

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

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

  @Test
  @Description("AggregateByEpisodesRequest -no episodeids")
  @TmsLink("28501529")
  public void C28501529() {
    Timelines timeline = timelinesUtils.getExistsAndNotEmpty("channel");
    String channelId = timeline.getChannel();
    String start = timeline.getStart().toInstant().toString();

    List<tv.pluto.ip.dal.timeline.Timelines.AggregateTimeline> timelinesList =
        timelinesClient.aggregateByEpisodesRequest(null, channelId, start).getTimelinesList();

    Assert.assertFalse(timelinesList.isEmpty());

    Instant startTime = timeline.getStart().toInstant();

    List<Timelines> listTimelines2 =
        timelinesUtils.getDocumentsForFeedsSyncStartTime(startTime, "$gte", channelId).stream()
            .map(t -> (Timelines) t)
            .filter(t -> t.getChannel().equals(channelId))
            .sorted(Comparator.comparing(Timelines::getStart))
            .toList();

    List<Timelines> listTimeline = new ArrayList<>(listTimelines2);

    listTimeline.sort(Comparator.comparing(Timelines::getStart));

    Assert.assertFalse(listTimeline.isEmpty());
    boolean allMatchChannelId =
        timelinesList.stream().allMatch(t -> t.getChannel().equals(channelId));
    Assert.assertTrue(allMatchChannelId);
  }

  @Test
  @Description("AggregateByEpisodesRequest -with episodeids")
  @TmsLink("28501530")
  public void C28501530() {

    Timelines timeline = timelinesUtils.getExistsAndNotEmpty("channel");
    String channelId = timeline.getChannel();
    String start = timeline.getStart().toInstant().toString();
    String episodeId = timeline.getEpisode(); // Assuming this gets the episode ID

    List<tv.pluto.ip.dal.timeline.Timelines.AggregateTimeline> timelinesList =
        timelinesClient
            .aggregateByEpisodesRequest(Collections.singletonList(episodeId), channelId, start)
            .getTimelinesList();

    Assert.assertFalse(timelinesList.isEmpty());

    Instant startTime = timeline.getStart().toInstant();

    List<Timelines> listTimelines2 =
        timelinesUtils.getDocumentsForFeedsSyncStartTime(startTime, "$gte", channelId).stream()
            .map(t -> (Timelines) t)
            .filter(t -> t.getChannel().equals(channelId))
            .sorted(Comparator.comparing(Timelines::getStart))
            .toList();

    List<Timelines> listTimeline = new ArrayList<>(listTimelines2);

    listTimeline.sort(Comparator.comparing(Timelines::getStart));

    Assert.assertFalse(listTimeline.isEmpty());

    boolean allMatchChannelId =
        timelinesList.stream().allMatch(t -> t.getChannel().equals(channelId));
    Assert.assertTrue(allMatchChannelId);
  }

  @Test
  @Description(
      "AggregateByEpisodesRequest-min_start from request date is smaller or equal to start from  response date")
  @TmsLink("28501531")
  public void C28501531() {

    Timelines timeline = timelinesUtils.getExistsAndNotEmpty("channel");
    String channelId = timeline.getChannel();
    String start = timeline.getStart().toInstant().toString();
    String episodeId = timeline.getEpisode();

    List<tv.pluto.ip.dal.timeline.Timelines.AggregateTimeline> timelinesList =
        timelinesClient
            .aggregateByEpisodesRequest(Collections.singletonList(episodeId), channelId, start)
            .getTimelinesList();

    Assert.assertFalse(timelinesList.isEmpty());

    Instant startTime = Instant.parse(start);

    String startDate = startTime.toString().substring(0, 10);
    boolean minStartIsSmallerOrEqual =
        timelinesList.stream()
            .allMatch(
                t -> {
                  try {

                    Instant minStart = Instant.parse(t.getStart());
                    String minStartDate = minStart.toString().substring(0, 10);
                    return minStartDate.compareTo(startDate) >= 0;
                  } catch (Exception e) {
                    return false;
                  }
                });
    Assert.assertTrue(minStartIsSmallerOrEqual);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "FAILED_PRECONDITION: Request param episodeIds must contain valid ids")
  @TmsLink("28501532")
  @Description("AggregateByEpisodesRequest - Test with invalid episodeIDs")
  public void C28501532() {
    Timelines timeline = timelinesUtils.getExistsAndNotEmpty("channel");
    String invalidEpisodeId = "invalid-episode-id";
    String channelId = timeline.getChannel();
    String start = timeline.getStart().toInstant().toString();

    timelinesClient
        .aggregateByEpisodesRequest(Collections.singletonList(invalidEpisodeId), channelId, start)
        .getTimelinesList();
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "FAILED_PRECONDITION: Request param channel_id must be a valid id")
  @TmsLink("28501533")
  @Description("AggregateByEpisodesRequest - Test with invalid channelID")
  public void C28501533() {
    Timelines timeline = timelinesUtils.getExistsAndNotEmpty("channel");
    String invalidEpisodeId = timeline.getEpisode();
    String channelId = "Invalid ChannelID";
    String start = timeline.getStart().toInstant().toString();

    timelinesClient
        .aggregateByEpisodesRequest(Collections.singletonList(invalidEpisodeId), channelId, start)
        .getTimelinesList();
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "FAILED_PRECONDITION: Request param min_start cannot be empty")
  @TmsLink("28501534")
  @Description("AggregateByEpisodesRequest - Test with empty min_start, channelId, episodeId")
  public void C28501534() {
    String channelId = "";
    String episodeId = "";
    String minStart = "";

    timelinesClient
        .aggregateByEpisodesRequest(Collections.singletonList(episodeId), channelId, minStart)
        .getTimelinesList();
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "FAILED_PRECONDITION: Request param min_start must be a valid date")
  @TmsLink("28501536")
  public void C28501536() {
    Timelines timeline = timelinesUtils.getExistsAndNotEmpty("channel");
    String invalidEpisodeId = timeline.getEpisode();
    String channelId = timeline.getChannel();
    String start = "abc";

    timelinesClient
        .aggregateByEpisodesRequest(Collections.singletonList(invalidEpisodeId), channelId, start)
        .getTimelinesList();
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "FAILED_PRECONDITION: Request param min_start must be a valid date")
  @TmsLink("28501535")
  @Description("AggregateByEpisodesRequest - Test with invalid min_start, channelId, episodeId")
  public void C28501535() {

    String invalidValue = "invalid";

    timelinesClient
        .aggregateByEpisodesRequest(
            Collections.singletonList(invalidValue), invalidValue, invalidValue)
        .getTimelinesList();
  }

  @Test
  @Description("GetAllBy – Retrieve distinct episodes that aired in the last 3 hours.")
  @TmsLink("28501537")
  public void C28501537() {
    Instant now = Instant.now();
    Instant threeHoursAgo = now.minusSeconds(3 * 3600);

    DateFilter stopLte =
        DateFilter.newBuilder().setDate(now.toString()).setOperator("$lte").build();

    DateFilter stopGt =
        DateFilter.newBuilder().setDate(threeHoursAgo.toString()).setOperator("$gt").build();

    List<DateFilter> stopFilters = List.of(stopLte, stopGt);

    GetAllByResponse grpcResponse =
        timelinesClient.getAllByRequest(null, null, stopFilters, null, "episode");

    List<String> grpcEpisodeIds = new ArrayList<>(grpcResponse.getDocumentsList());

    List<String> mongoEpisodeIds = timelinesUtils.getEpisodeIdsByStopBetween(threeHoursAgo, now);

    grpcEpisodeIds.sort(Comparator.naturalOrder());
    mongoEpisodeIds.sort(Comparator.naturalOrder());

    Assert.assertEquals(grpcEpisodeIds, mongoEpisodeIds);
  }

  @Test
  @Description(
      "GetAllBy - Distinct timeline IDs with start >= now + 15 minutes for a given channel")
  @TmsLink("28501538")
  public void C28501538() {

    Timelines sampleTimeline = timelinesUtils.getRandomDocument();
    String channelId = sampleTimeline.getChannel();

    Instant startTime = Instant.now().plusSeconds(15 * 60);

    DateFilter startFilter =
        DateFilter.newBuilder().setDate(startTime.toString()).setOperator("$gte").build();

    GetAllByResponse grpcResponse =
        timelinesClient.getAllByRequest(
            channelId, Collections.singletonList(startFilter), null, null, "_id");

    List<String> grpcIds = new ArrayList<>(grpcResponse.getDocumentsList());

    List<Timelines> mongoTimelines =
        timelinesUtils.getTimelinesByStartGteAndChannel(startTime, channelId);
    List<String> mongoIds =
        mongoTimelines.stream().map(Timelines::getId).collect(Collectors.toList());

    grpcIds.sort(Comparator.naturalOrder());
    mongoIds.sort(Comparator.naturalOrder());

    Assert.assertEquals(mongoIds, grpcIds);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "FAILED_PRECONDITION: Either start or stop must be provided")
  @TmsLink("28501540")
  @Description("GetAllBy - Time period without start and stop timeline.")
  public void C28501540() {
    Timelines sampleTimeline = timelinesUtils.getRandomDocument();
    String channelId = sampleTimeline.getChannel();
    timelinesClient.getAllByRequest(channelId, null, null, null, "_id");
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INTERNAL: Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer")
  @TmsLink("28501539")
  @Description("GetAllBy - Invalid ChannelID")
  public void C28501539() {

    String incorrectChannelId = "66338ce79e68c20008b7e00z";

    Instant startTime = Instant.now().plusSeconds(15 * 60);
    DateFilter startFilter =
        DateFilter.newBuilder().setDate(startTime.toString()).setOperator("$gte").build();

    timelinesClient.getAllByRequest(
        incorrectChannelId, java.util.Collections.singletonList(startFilter), null, null, "_id");
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "NOT_FOUND: Timeline documents for requested params not found")
  @TmsLink("28501541")
  @Description("GetAllBy - Valid request but no timeline found")
  public void C28501541() {

    Timelines sampleTimeline = timelinesUtils.getRandomDocument();
    String channelId = sampleTimeline.getChannel();

    Instant futureStart = Instant.now().plusSeconds(365 * 24 * 3600);
    DateFilter startFilter =
        DateFilter.newBuilder().setDate(futureStart.toString()).setOperator("$gte").build();

    timelinesClient.getAllByRequest(
        channelId, java.util.Collections.singletonList(startFilter), null, null, "id");
  }

  @Test
  @Description("GetAllBy - Distinct documents using $nin on start")
  @TmsLink("28501542")
  public void C28501542() {

    Timelines sampleTimeline = timelinesUtils.getRandomDocument();
    String channelId = sampleTimeline.getChannel();

    Instant startTime = Instant.now().plusSeconds(15 * 60);

    DateFilter startFilter =
        DateFilter.newBuilder().setDate(startTime.toString()).setOperator("$nin").build();

    GetAllByResponse grpcResponse =
        timelinesClient.getAllByRequest(
            channelId, Collections.singletonList(startFilter), null, null, "_id");

    List<String> grpcIds = new ArrayList<>(grpcResponse.getDocumentsList());

    List<Timelines> mongoTimelines = timelinesUtils.getTimelinesByStartNIN(startTime, channelId);
    List<String> mongoIds =
        mongoTimelines.stream().map(Timelines::getId).collect(Collectors.toList());

    grpcIds.sort(Comparator.naturalOrder());
    mongoIds.sort(Comparator.naturalOrder());

    Assert.assertEquals(mongoIds, grpcIds);
  }

  @Test
  @Description(
      "GetAllBy - Distinct documents using $ne on start with featured filter and OR/AND operator.")
  @TmsLink("28501543")
  public void C28501543() {

    Timelines sampleTimeline = timelinesUtils.getRandomDocument();
    String channelId = sampleTimeline.getChannel();

    Instant startTime = Instant.now().plusSeconds(15 * 60);

    DateFilter startFilter =
        DateFilter.newBuilder().setDate(startTime.toString()).setOperator("$ne").build();

    Filter featuredOrFilter = timelinesClient.timesFilter("featured", "false", "bool", "OR");

    Filter featuredAndFilter = timelinesClient.timesFilter("featured", "false", "bool", "AND");

    GetAllByResponse grpcResponse =
        timelinesClient.getAllByRequest(
            channelId,
            Collections.singletonList(startFilter),
            null,
            List.of(featuredOrFilter, featuredAndFilter),
            "_id");

    List<String> grpcIds = new ArrayList<>(grpcResponse.getDocumentsList());

    List<Timelines> mongoTimelines = timelinesUtils.getTimelinesByStartNIN(startTime, channelId);
    List<String> mongoIds =
        mongoTimelines.stream().map(Timelines::getId).collect(Collectors.toList());

    grpcIds.sort(Comparator.naturalOrder());
    mongoIds.sort(Comparator.naturalOrder());

    Assert.assertEquals(mongoIds, grpcIds);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INTERNAL: Operator property should be only AND, OR, or NOR.")
  @TmsLink("28501544")
  @Description("GetAllBy - Invalid filter operator")
  public void C28501544() {
    Timelines sampleTimeline = timelinesUtils.getRandomDocument();
    String channelId = sampleTimeline.getChannel();
    Instant startTime = Instant.now().plusSeconds(15 * 60);
    DateFilter startFilter =
        DateFilter.newBuilder().setDate(startTime.toString()).setOperator("$gte").build();
    Filter invalidFilter = timelinesClient.timesFilter("featured", "false", "bool", "INVALID");

    timelinesClient.getAllByRequest(
        channelId, List.of(startFilter), null, List.of(invalidFilter), "_id");
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "FAILED_PRECONDITION: Request param start.operator is required and must be one of the allowed operators")
  @TmsLink("28501545")
  @Description("GetAllBy - Invalid start operator")
  public void C28501545() {
    Timelines sampleTimeline = timelinesUtils.getRandomDocument();
    String channelId = sampleTimeline.getChannel();
    Instant startTime = Instant.now().plusSeconds(15 * 60);
    DateFilter startFilter =
        DateFilter.newBuilder().setDate(startTime.toString()).setOperator("$invalid").build();

    timelinesClient.getAllByRequest(channelId, List.of(startFilter), null, null, "_id");
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "FAILED_PRECONDITION: Request param stop.operator must be one of the allowed operators")
  @Description("GetAllBy - Invalid stop operator")
  @TmsLink("28501546")
  public void C28501546() {
    Timelines sampleTimeline = timelinesUtils.getRandomDocument();
    String channelId = sampleTimeline.getChannel();
    Instant stopTime = Instant.now().plusSeconds(15 * 60);
    DateFilter stopFilter =
        DateFilter.newBuilder().setDate(stopTime.toString()).setOperator("$invalid").build();

    timelinesClient.getAllByRequest(channelId, null, List.of(stopFilter), null, "_id");
  }

  @Test
  @Description(
      "GetAllBy - Distinct timeline IDs with start greater than and stop less than specified values.")
  @TmsLink("28501547")
  public void C28501547() {

    Timelines sampleTimeline = timelinesUtils.getRandomDocument();
    String channelId = sampleTimeline.getChannel();

    Instant startTime = Instant.now().minus(Duration.ofDays(10));
    Instant stopTime = Instant.now();

    DateFilter startFilter =
        DateFilter.newBuilder().setDate(startTime.toString()).setOperator("$gt").build();

    DateFilter stopFilter =
        DateFilter.newBuilder().setDate(stopTime.toString()).setOperator("$lt").build();

    GetAllByResponse grpcResponse =
        timelinesClient.getAllByRequest(
            channelId,
            Collections.singletonList(startFilter),
            Collections.singletonList(stopFilter),
            null,
            "_id");

    List<String> grpcIds = new ArrayList<>(grpcResponse.getDocumentsList());

    List<Timelines> mongoTimelines =
        timelinesUtils.getTimelinesByStartGTAndStopLT(startTime, stopTime, channelId);

    List<String> mongoIds =
        mongoTimelines.stream().map(Timelines::getId).collect(Collectors.toList());

    grpcIds.sort(Comparator.naturalOrder());
    mongoIds.sort(Comparator.naturalOrder());

    Assert.assertEquals(mongoIds, grpcIds);
  }

  @Test
  @Description(
      "GetAllBy – Distinct timeline IDs with start greater than or equal to and stop less than or equal to specified values")
  @TmsLink("28501548")
  public void C28501548() {

    Timelines sampleTimeline = timelinesUtils.getRandomDocument();
    String channelId = sampleTimeline.getChannel();

    Instant startTime = Instant.now().minus(Duration.ofDays(10));
    Instant stopTime = Instant.now();

    DateFilter startFilter =
        DateFilter.newBuilder().setDate(startTime.toString()).setOperator("$gte").build();

    DateFilter stopFilter =
        DateFilter.newBuilder().setDate(stopTime.toString()).setOperator("$lte").build();

    GetAllByResponse grpcResponse =
        timelinesClient.getAllByRequest(
            channelId,
            Collections.singletonList(startFilter),
            Collections.singletonList(stopFilter),
            null,
            "_id");

    List<String> grpcIds = new ArrayList<>(grpcResponse.getDocumentsList());

    List<Timelines> mongoTimelines =
        timelinesUtils.getTimelinesByStartGTEAndStopLTE(startTime, stopTime, channelId);

    List<String> mongoIds =
        mongoTimelines.stream().map(Timelines::getId).collect(Collectors.toList());

    grpcIds.sort(Comparator.naturalOrder());
    mongoIds.sort(Comparator.naturalOrder());

    Assert.assertEquals(mongoIds, grpcIds);
  }

  @Test
  @Description("GetPopulatedTimelinesByChannelId - Validate has_live_broadcast is present")
  @TmsLink("28501549")
  public void C28501549_TEST() {
    Timelines timeline = (Timelines) timelinesUtils.getTimelinesWithChannelId().next();
    Assert.assertNotNull(timeline, "Timeline is null");

    String channelId = timeline.getChannel();
    long start = timeline.getStart().getTime();
    long stop = timeline.getStop().getTime();

    LogUtils.logInfoMessage("Running test case C23910949_TEST");
    LogUtils.logInfoMessage("ChannelID = " + channelId);
    LogUtils.logInfoMessage("start = " + start);
    LogUtils.logInfoMessage("stop = " + stop);

    tv.pluto.ip.dal.timeline.Timelines.GetPopulatedTimelinesByChannelIdResponse grpcResponse =
        timelinesClient.getPopulatedTimelinesByChannelId(
            channelId, start, stop, Collections.emptyList());

    Assert.assertNotNull(grpcResponse, "gRPC response is null");

    for (tv.pluto.ip.dal.timeline.Timelines.TimelineByChannelId grpcTimeline :
        grpcResponse.getDocumentsList()) {
      Assert.assertNotNull(grpcTimeline, "Timeline is null");

      Assert.assertNotNull(
          grpcTimeline.getHasLiveBroadcast(),
          "has_live_broadcast field is not accessible for timeline draft ID: ");
    }
  }
}
