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

import static com.mongodb.client.model.Projections.include;
import static com.mongodb.client.model.Sorts.ascending;
import static com.mongodb.client.model.Sorts.orderBy;
import static tv.pluto.ip.dal.vod.Vod.VodCategory;
import static tv.pluto.mongo.utils.MongoUtils.INVALID_ID_VALUE;

import com.plutotv.test.lib.LogUtils;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.qameta.allure.Description;
import io.qameta.allure.TmsLink;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.bson.conversions.Bson;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import tv.pluto.core.BaseTest;
import tv.pluto.grpc.vod.VodClient;
import tv.pluto.ip.dal.vod.Vod;
import tv.pluto.ip.dal.vodcategory.VodCategoryOuterClass;
import tv.pluto.mongo.dto.ContentFeedQuery;
import tv.pluto.mongo.entities.devices.Devices;
import tv.pluto.mongo.entities.vod.VodCategories;
import tv.pluto.mongo.utils.ActiveRegionsUtils;
import tv.pluto.mongo.utils.DevicesUtils;
import tv.pluto.mongo.utils.MongoUtils;
import tv.pluto.mongo.utils.VodCategoryEntriesUtils;
import tv.pluto.mongo.utils.VodUtils;
import tv.pluto.utils.CommonUtils;

public class VodTestSuite extends BaseTest {
  private VodClient vodClient;
  private VodUtils vodUtils;
  private DevicesUtils devicesUtils;
  private VodCategoryEntriesUtils vodCategoryEntriesUtils;
  private ActiveRegionsUtils activeRegionsUtils;

  @BeforeClass(alwaysRun = true)
  private void setUp() {
    vodClient = new VodClient(grpcChannelManager.getChannel());
    vodUtils = new VodUtils();
    devicesUtils = new DevicesUtils();
    vodCategoryEntriesUtils = new VodCategoryEntriesUtils();
    activeRegionsUtils = new ActiveRegionsUtils();
  }

  @Test
  @Description("Valid request - GetActiveVodCategoryIds")
  @TmsLink("28501580")
  public void C28501580_TEST() {
    String randomActiveRegion = activeRegionsUtils.getRandomRegion().getCode().toLowerCase();
    List<String> grpcActiveVodCategoryIds =
        new ArrayList<>(vodClient.getActiveVodCategoryIds(randomActiveRegion).getIdsList());
    List<String> mongoActiveVodCategoryIds = vodUtils.getActiveVodCategoryIds(randomActiveRegion);
    Collections.sort(grpcActiveVodCategoryIds);
    Collections.sort(mongoActiveVodCategoryIds);
    Assert.assertEquals(grpcActiveVodCategoryIds, mongoActiveVodCategoryIds);
  }

  @Test
  @Description("Invalid request - GetActiveVodCategoryIds")
  @TmsLink("28501581")
  public void C28501581_TEST() {
    Assert.assertTrue(
        vodClient.getActiveVodCategoryIds(MongoUtils.INVALID_ID_VALUE).getIdsList().isEmpty());
  }

  @Test
  @Description("Valid request - GetActiveEpisodeIdsForCategoryId")
  @TmsLink("28501582")
  public void C28501582_TEST() {
    String randomId = vodCategoryEntriesUtils.getVodCategoryEntry().getVodCategory();
    List<String> grpcActiveEpisodeIdsForCategoryId =
        new ArrayList<>(vodClient.getActiveEpisodeIdsForCategoryId(randomId).getIdsList());
    List<String> mongoActiveEpisodeIdsForCategoryId =
        vodUtils.getActiveEpisodeIdsForCategoryId(randomId);
    Collections.sort(grpcActiveEpisodeIdsForCategoryId);
    Collections.sort(mongoActiveEpisodeIdsForCategoryId);
    Assert.assertEquals(grpcActiveEpisodeIdsForCategoryId, mongoActiveEpisodeIdsForCategoryId);
  }

  @Test
  @Description("Invalid request - GetActiveEpisodeIdsForCategoryId")
  @TmsLink("28501583")
  public void C28501583_TEST() {
    Assert.assertTrue(
        vodClient
            .getActiveEpisodeIdsForCategoryId(MongoUtils.INVALID_ID_VALUE)
            .getIdsList()
            .isEmpty());
  }

  @Test
  @Description("GetDocumentsUpdatedBetweenDates - Time period for which there is a vod category")
  @TmsLink("28501596")
  public void C28501596_TEST() {

    ContentFeedQuery contentFeedQuery =
        ContentFeedQuery.builder()
            .updatedAtMin(vodUtils.updatedAtMin)
            .updatedAtMax(vodUtils.updatedAtMax)
            .limit(0)
            .projections(include("id"))
            .build();

    List<VodCategories> mongoVodCategories = vodUtils.getDocumentsForContentFeeds(contentFeedQuery);
    List<VodCategories> grpcVodCategories = new ArrayList<>();

    Iterator<VodCategory> vodCategories =
        vodClient.getDocumentsUpdatedBetweenDates(
            vodUtils.updatedAtMinString, vodUtils.updatedAtMaxString);

    while (vodCategories.hasNext()) {
      grpcVodCategories.add(
          vodUtils.mapGrpcObjectToPojo(vodCategories.next(), VodCategories.class));
    }

    mongoVodCategories.sort(Comparator.comparing(VodCategories::getId));
    grpcVodCategories.sort(Comparator.comparing(VodCategories::getId));

    List<String> mongoIds = mongoVodCategories.stream().map(VodCategories::getId).toList();

    List<String> grpcIds = grpcVodCategories.stream().map(VodCategories::getId).toList();

    Assert.assertEquals(
        mongoIds, grpcIds, "The IDs do not match between Mongo and gRPC vod categories.");
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_MESSAGE)
  @Description("GetDocumentsUpdatedBetweenDates - Time period without vod category")
  @TmsLink("28501597")
  public void C28501597_TEST() {
    vodClient.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("28501598")
  public void C28501598_TEST(String startDateTime, String endDateTime) {
    vodClient.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("28501599")
  public void C28501599_TEST(String startDateTime, String endDateTime) {
    vodClient.getDocumentsUpdatedBetweenDates(startDateTime, endDateTime).next();
  }

  @Test
  @Description("GetByIdRequest - Existing id")
  @TmsLink("28501608")
  public void C28501608_TEST() {
    String id = vodUtils.getRandomVodCategory().getId();

    LogUtils.logInfoMessage("Id for C28501608_TEST " + id);

    VodCategories mongoCategory = vodUtils.getDocumentById(id);
    VodCategories grpcCategory =
        vodUtils.mapGrpcObjectToPojo(vodClient.getById(id).getDocument(), VodCategories.class);

    Assert.assertEquals(mongoCategory, grpcCategory);
  }

  @Test
  @Description("GetByIdRequest - Non existing id")
  @TmsLink("28501609")
  public void C28501609_TEST() {
    try {
      vodClient.getById(INVALID_ID_VALUE);
    } catch (StatusRuntimeException e) {
      Assert.assertEquals(e.getStatus().getCode(), Status.NOT_FOUND.getCode());
      Assert.assertEquals(
          e.getMessage(), "NOT_FOUND: VodCategory document for requested ID not found");
    }
  }

  @Test
  @Description("GetByIds - Single existing id")
  @TmsLink("28501610")
  public void C28501610_TEST() {
    List<String> ids = Collections.singletonList(vodUtils.getRandomVodCategory().getId());

    LogUtils.logInfoMessage("C9029493_TEST category ids = " + ids);

    Bson sort = orderBy(ascending("_id"));

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

    List<VodCategories> mongoCategories = vodUtils.getByIds(ids, sort);

    List<VodCategories> grpcCategories =
        vodUtils.mapGrpcObjectsToPojo(
            vodClient.getByIds(ids, grpcSort).getDocumentsList(), VodCategories.class);

    Assert.assertEquals(mongoCategories, grpcCategories);
  }

  @Test
  @Description("GetByIds - Multiple existing ids")
  @TmsLink("28501611")
  public void C28501611_TEST() {
    List<String> ids = vodUtils.getRandomVodCategoriesIds();

    LogUtils.logInfoMessage("C9029494_TEST categories ids = " + ids);

    Bson sort = orderBy(ascending("_id"));

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

    List<VodCategories> mongoCategories = vodUtils.getByIds(ids, sort);

    List<VodCategories> grpcCategories =
        vodUtils.mapGrpcObjectsToPojo(
            vodClient.getByIds(ids, grpcSort).getDocumentsList(), VodCategories.class);

    Assert.assertEquals(mongoCategories, grpcCategories);
  }

  @Test
  @Description("GetByIds - Multiple existing ids with one non existing")
  @TmsLink("28501612")
  public void C28501612_TEST() {
    List<String> ids = vodUtils.getRandomVodCategoriesIds();
    ids.add(INVALID_ID_VALUE);

    LogUtils.logInfoMessage("C9029495_TEST categories ids = " + ids);

    Bson sort = orderBy(ascending("_id"));

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

    List<VodCategories> mongoCategories = vodUtils.getByIds(ids, sort);

    List<VodCategories> grpcCategories =
        vodUtils.mapGrpcObjectsToPojo(
            vodClient.getByIds(ids, grpcSort).getDocumentsList(), VodCategories.class);

    Assert.assertEquals(mongoCategories, grpcCategories);
  }

  @Test
  @Description("GetByIds - Single non existing id")
  @TmsLink("28501613")
  public void C28501613_TEST() {
    List<VodCategoryOuterClass.GetAllPagedSort> grpcSort =
        Collections.singletonList(
            VodCategoryOuterClass.GetAllPagedSort.newBuilder().setField("_id").setValue(1).build());

    try {
      vodClient.getByIds(Collections.singletonList(INVALID_ID_VALUE), grpcSort);
    } catch (StatusRuntimeException e) {
      Assert.assertEquals(e.getStatus().getCode(), Status.NOT_FOUND.getCode());
      Assert.assertEquals(
          e.getMessage(), "NOT_FOUND: VodCategory documents for requested IDs not found");
    }
  }

  @Test
  @Description("CFeedsSyncAllIds - stream returns CFEEDS stream of Vod ids")
  @TmsLink("28501614")
  public void C28501614_TEST() {
    Iterator<?> cFeedsSyncAllIds =
        vodClient.getCFeedsResponse(null, null, null, "cFeedsSyncAllIds");
    List<String> grpcListIds = new ArrayList<>();
    int total = 0;
    while (cFeedsSyncAllIds.hasNext() && total < 100) {
      String id = ((Vod.Id) cFeedsSyncAllIds.next()).getId();
      grpcListIds.add(id);
      total++;
    }

    int mongoIds = vodUtils.getDocumentsByIds(grpcListIds).size();

    Assert.assertTrue(mongoIds > 0);
    Assert.assertEquals(grpcListIds.size(), mongoIds);
  }

  @Test
  @Description("cFeedsSyncDocuments - last_run_at")
  @TmsLink("28501615")
  public void C28501615_TEST() {
    LocalDateTime ranAt = LocalDateTime.now(ZoneOffset.UTC).minusWeeks(9);
    Iterator<?> cFeedsSyncAllIds =
        vodClient.getCFeedsResponse(null, ranAt + "Z", null, "cFeedsSyncDocuments");

    while (cFeedsSyncAllIds.hasNext()) {
      VodCategories grpcVodCategory =
          vodUtils.mapGrpcObjectToPojo(cFeedsSyncAllIds.next(), VodCategories.class);
      LocalDateTime grpcTime = grpcVodCategory.getUpdatedAt();
      boolean isTrue = ranAt.isBefore(grpcTime) || ranAt.equals(grpcTime);
      Assert.assertTrue(isTrue);
    }
  }

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

  @Test
  @Description("GetDocumentsUpdatedBetweenDates has a kids mode field")
  @TmsLink("28501617")
  public void C28501617_TEST() {
    VodCategories vod =
        vodUtils.mapGrpcObjectToPojo(
            vodClient
                .getDocumentsUpdatedBetweenDates(
                    vodUtils.updatedAtMinString, vodUtils.updatedAtMaxString)
                .next(),
            VodCategories.class);

    Assert.assertNotNull(vod.getKidsMode());
  }

  @Test
  @Description("cFeedsSyncDocuments - last_run_at has a kids mode field")
  @TmsLink("28501618")
  public void C28501618_TEST() {
    LocalDateTime ranAt = LocalDateTime.now(ZoneOffset.UTC).minusWeeks(9);
    Iterator<?> cFeedsSyncAllIds =
        vodClient.getCFeedsResponse(null, ranAt + "Z", null, "cFeedsSyncDocuments");
    VodCategories grpcVodCategory =
        vodUtils.mapGrpcObjectToPojo(cFeedsSyncAllIds.next(), VodCategories.class);
    Boolean kidsMode = grpcVodCategory.getKidsMode();
    Assert.assertNotNull(kidsMode);
  }

  @Test
  @Description("BulkDeviceUpdate - delete platform include")
  @TmsLink("28501620")
  public void C28501620_TEST() {
    VodCategories vodCategories = vodUtils.getExistsAndSizeNotZero("distribution.include");
    List<String> includeList = vodCategories.getDistribution().getInclude();
    String include = includeList.get(0);

    List<Devices> listDevicesDb = devicesUtils.getDocumentsByKeyValue("platform", include);
    Devices devices = listDevicesDb.get(0);
    List<String> listDevices = List.of(devices.getId());
    List<String> listVodCategories = List.of(vodCategories.getId());

    vodClient.bulkdDeviceUpdate(listDevices, null, null, listVodCategories);
    VodCategories vodCategoriesAfterUpdate = vodUtils.getDocumentById(vodCategories.getId());
    List<String> includeAfterList = vodCategoriesAfterUpdate.getDistribution().getInclude();

    includeList.remove(0);
    Assert.assertEquals(includeList, includeAfterList);
  }

  @Test
  @Description("BulkDeviceUpdate - platform exclude")
  @TmsLink("28501621")
  public void C28501621_TEST() {
    VodCategories vodCategories = vodUtils.getExistsAndSizeNotZero("distribution.exclude");
    List<String> excludeList = vodCategories.getDistribution().getExclude();
    String exclude = excludeList.get(0);

    List<Devices> listDevicesDb = devicesUtils.getDocumentsByKeyValue("platform", exclude);
    Devices devices = listDevicesDb.get(0);
    List<String> listDevices = List.of(devices.getId());
    List<String> listVodCategories = List.of(vodCategories.getId());

    Vod.BulkDeviceUpdateResponse bulkDeviceUpdateResponse =
        vodClient.bulkdDeviceUpdate(listDevices, null, listVodCategories, null);
    Assert.assertEquals("", bulkDeviceUpdateResponse.toString());
  }

  @Test
  @Description("BulkDeviceUpdate - platform remove")
  @TmsLink("28501622")
  public void C28501622_TEST() {
    VodCategories vodCategories = vodUtils.getExistsAndSizeNotZero("distribution.include");
    List<String> listIncludeDevices = vodCategories.getDistribution().getInclude();
    String platform = listIncludeDevices.get(0);

    List<Devices> listDevicesDb = devicesUtils.getDocumentsByKeyValue("platform", platform);
    Devices devices = listDevicesDb.get(0);
    List<String> listDevices = List.of(devices.getId());
    List<String> listVodCategories = List.of(vodCategories.getId());

    vodClient.bulkdDeviceUpdate(listDevices, null, null, listVodCategories);
    VodCategories vodCategoriesAfterUpdate = vodUtils.getDocumentById(vodCategories.getId());
    List<String> excludeAfterList = vodCategoriesAfterUpdate.getDistribution().getExclude();

    boolean isInclude =
        vodCategoriesAfterUpdate.getDistribution().getInclude().stream()
            .noneMatch(device -> device.equals(platform));
    boolean isExclude = excludeAfterList.stream().noneMatch(device -> device.equals(platform));
    Assert.assertTrue(isInclude);
    Assert.assertTrue(isExclude);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INVALID_ARGUMENT: includeGroup and excludeGroup cannot be empty.")
  @Description("BulkDeviceUpdate - only devices in the request")
  @TmsLink("28501623")
  public void C28501623_TEST() {
    VodCategories vodCategories = vodUtils.getExistsAndSizeNotZero("distribution.include");
    List<String> listIncludeDevices = vodCategories.getDistribution().getInclude();
    String platform = listIncludeDevices.get(0);

    List<Devices> listDevicesDb = devicesUtils.getDocumentsByKeyValue("platform", platform);
    Devices devices = listDevicesDb.get(0);
    List<String> listDevices = List.of(devices.getId());

    vodClient.bulkdDeviceUpdate(listDevices, null, null, null);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "INVALID_ARGUMENT: Devices cannot be empty.")
  @Description("BulkDeviceUpdate - delete only with remove_group field")
  @TmsLink("28501624")
  public void C28501624_TEST() {
    VodCategories vodCategories = vodUtils.getExistsAndSizeNotZero("distribution.include");
    List<String> listVodCategories = List.of(vodCategories.getId());

    vodClient.bulkdDeviceUpdate(null, null, null, listVodCategories);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "INVALID_ARGUMENT: Devices cannot be empty.")
  @Description("BulkDeviceUpdate - delete only with exclude_group")
  @TmsLink("28501625")
  public void C28501625_TEST() {
    VodCategories vodCategories = vodUtils.getExistsAndSizeNotZero("distribution.exclude");
    List<String> listVodCategories = List.of(vodCategories.getId());

    vodClient.bulkdDeviceUpdate(null, null, listVodCategories, null);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "INVALID_ARGUMENT: Devices cannot be empty.")
  @Description("BulkDeviceUpdate - delete only with exclude_group")
  @TmsLink("28501626")
  public void C28501626_TEST() {
    VodCategories vodCategories = vodUtils.getExistsAndSizeNotZero("distribution.include");
    List<String> listVodCategories = List.of(vodCategories.getId());

    vodClient.bulkdDeviceUpdate(null, listVodCategories, null, null);
  }
}
