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

import static tv.pluto.ip.dal.timeline.Timelines.CFeedsSyncAllIdsRequest;
import static tv.pluto.ip.dal.timeline.Timelines.CFeedsSyncDocumentsRequest;
import static tv.pluto.ip.dal.timeline.Timelines.GetAllRequest;
import static tv.pluto.ip.dal.timeline.Timelines.GetByIdRequest;
import static tv.pluto.ip.dal.timeline.Timelines.GetByIdResponse;
import static tv.pluto.ip.dal.timeline.Timelines.GetByIdsRequest;
import static tv.pluto.ip.dal.timeline.Timelines.GetByIdsResponse;
import static tv.pluto.ip.dal.timeline.Timelines.GetDocumentsUpdatedBetweenDatesOnChannelRequest;
import static tv.pluto.ip.dal.timeline.Timelines.GetEmptyTimelineRequest;
import static tv.pluto.ip.dal.timeline.Timelines.GetEmptyTimelineResponse;
import static tv.pluto.ip.dal.timeline.Timelines.GetPopulatedTimelinesByChannelIdRequest;
import static tv.pluto.ip.dal.timeline.Timelines.GetPopulatedTimelinesByChannelIdResponse;
import static tv.pluto.ip.dal.timeline.Timelines.Timeline;

import com.plutotv.test.lib.LogUtils;
import io.grpc.Channel;
import io.grpc.stub.ClientCallStreamObserver;
import io.grpc.stub.ClientResponseObserver;
import java.time.Instant;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Logger;
import tv.pluto.grpc.GenericGrpcClient;
import tv.pluto.ip.dal.timeline.TimelineManagerGrpc;
import tv.pluto.ip.dal.timeline.Timelines;

/** Class represents gRPC client for <b>timelines.proto</b> file */
public class TimelinesClient extends GenericGrpcClient {
  private static final Logger logger = Logger.getLogger(TimelinesClient.class.getName());

  private TimelineManagerGrpc.TimelineManagerBlockingStub blockingStub;
  private TimelineManagerGrpc.TimelineManagerStub stub;

  public TimelinesClient(Channel channel, boolean auth) {
    if (auth) {
      blockingStub = TimelineManagerGrpc.newBlockingStub(channel);
      blockingStub = blockingStub.withInterceptors(CLIENT_INTERCEPTOR_AUTH);
    } else {
      blockingStub = TimelineManagerGrpc.newBlockingStub(channel);
      blockingStub = blockingStub.withInterceptors(CLIENT_INTERCEPTOR);
    }
  }

  public TimelinesClient(Channel channel) {
    // stream stub
    stub = TimelineManagerGrpc.newStub(channel);
    stub = stub.withInterceptors(CLIENT_INTERCEPTOR);
  }

  /**
   * Method represents gRPC method GetTimelineById from <b>timelines.proto</b> file
   *
   * @param timelineId id for search
   * @return GetTimelineByIdResponse object
   */
  public GetByIdResponse getTimelineById(String timelineId) {
    logger.info("Getting info for Timeline with id: " + timelineId + " ...");
    GetByIdRequest request = GetByIdRequest.newBuilder().setId(timelineId).build();

    return blockingStub.getById(request);
  }

  public GetByIdResponse getTimelineById(String timelineId, String[] listProjections) {
    GetByIdRequest.Builder builder = GetByIdRequest.newBuilder();

    if (listProjections != null) {
      builder.addAllProjection(List.of(listProjections));
    }

    if (timelineId != null) {
      builder.setId(timelineId);
    }

    return blockingStub.getById(builder.build());
  }

  /**
   * Method represents gRPC method GetTimelinesByIds from <b>timelines.proto</b> file
   *
   * @param limelinesIds List of partners ids to search
   * @return GetTimelinesByIdsResponse object
   */
  public GetByIdsResponse getTimelinesByIds(List<String> limelinesIds, List<String> projection) {
    logger.info("Getting info for Timelines with ids: " + limelinesIds + " ...");

    GetByIdsRequest.Builder requestBuilder = GetByIdsRequest.newBuilder();

    if (projection != null) {
      requestBuilder.addAllProjection(projection);
    }

    if (limelinesIds != null) {
      requestBuilder.addAllIds(limelinesIds);
    }

    return blockingStub.getByIds(requestBuilder.build());
  }

  /**
   * Method represents gRPC method GetEmptyTimeline from <b>timelines.proto</b> file
   *
   * @return GetEmptyTimelineResponse object
   */
  public GetEmptyTimelineResponse getEmptyTimeline() {
    logger.info("Getting new empty Timeline ...");
    GetEmptyTimelineRequest request = GetEmptyTimelineRequest.newBuilder().build();

    return blockingStub.getEmptyTimeline(request);
  }

  public Iterator<Timeline> getDocumentsUpdatedBetweenDatesOnChannel(
      String updatedAtMin, String updatedAtMax, List<String> ids) {
    LogUtils.logInfoMessage("Getting timelines for content feeds ");
    GetDocumentsUpdatedBetweenDatesOnChannelRequest request =
        GetDocumentsUpdatedBetweenDatesOnChannelRequest.newBuilder()
            .setStart(updatedAtMin)
            .setStop(updatedAtMax)
            .addAllChannelIds(ids)
            .build();

    return blockingStub.getDocumentsUpdatedBetweenDatesOnChannel(request);
  }

  public Iterator<Timeline> getAllResponse(String region) {
    GetAllRequest getAllRequest = GetAllRequest.newBuilder().setRegion(region).build();
    return blockingStub.getAll(getAllRequest);
  }

  public Iterator<Timeline> getAllResponseChannelIds(List<String> listChannelIds) {
    GetAllRequest getAllRequest =
        GetAllRequest.newBuilder().addAllChannelIds(listChannelIds).build();
    return blockingStub.getAll(getAllRequest);
  }

  public Iterator<Timelines.Id> getTimelinesIdsWithLimiterDate(String limiterDate) {
    CFeedsSyncAllIdsRequest request =
        CFeedsSyncAllIdsRequest.newBuilder().setLimiterDate(limiterDate).build();
    return blockingStub.cFeedsSyncAllIds(request);
  }

  public Iterator<Timelines.Timeline> getTimelinesWithLimiterDate(
      String limiterRunAt, String date) {
    CFeedsSyncDocumentsRequest request =
        CFeedsSyncDocumentsRequest.newBuilder()
            .setLastRunAt(limiterRunAt)
            .setLimiterDate(date)
            .build();
    return blockingStub.cFeedsSyncDocuments(request);
  }

  public Timelines.GetOneResponse getOne(String field, int value) {
    Timelines.GetOneSort sort =
        Timelines.GetOneSort.newBuilder().setField(field).setValue(value).build();
    Timelines.GetOneRequest request = Timelines.GetOneRequest.newBuilder().setSort(sort).build();
    return blockingStub.getOne(request);
  }

  public Timelines.GetByEpisodeIdResponse getByEpisodeId(
      String id,
      int limit,
      int offset,
      List<Timelines.GetAllPagedSort> sort,
      Timelines.QueryTimeRange start,
      Timelines.QueryTimeRange end,
      String[] listProjections) {
    Timelines.GetByEpisodeIdRequest.Builder requestBuilder =
        Timelines.GetByEpisodeIdRequest.newBuilder().setId(id).setLimit(limit).setOffset(offset);

    if (sort != null) requestBuilder.addAllSort(sort);

    if (start != null) requestBuilder.setStart(start);

    if (end != null) requestBuilder.setStop(end);

    if (listProjections != null) requestBuilder.addAllProjection(List.of(listProjections));

    return blockingStub.getByEpisodeId(requestBuilder.build());
  }

  public List<tv.pluto.ip.dal.timeline.Timelines.GetAllPagedSort> getListGetAllPagedSort(
      String field, int value) {
    return Collections.singletonList(
        tv.pluto.ip.dal.timeline.Timelines.GetAllPagedSort.newBuilder()
            .setField(field)
            .setValue(value)
            .build());
  }

  public Timelines.QueryTimeRange timeRangeStartStop(String start, String stop) {
    return Timelines.QueryTimeRange.newBuilder().setBegin(start).setEnd(stop).build();
  }

  public Timelines.GetCountResponse getCount(String start, String stop) {
    Timelines.GetCountRequest request =
        Timelines.GetCountRequest.newBuilder().setStartInitial(start).setStartFinal(stop).build();
    return blockingStub.count(request);
  }

  public Timelines.DeleteByIdResponse deleteById(String id) {
    Timelines.DeleteByIdRequest request =
        Timelines.DeleteByIdRequest.newBuilder().setId(id).build();
    return blockingStub.deleteById(request);
  }

  public Iterator<Timeline> getTimelinesBetweenDates(String start, String stop) {
    Timelines.GetDocumentsBetweenDatesRequest request =
        Timelines.GetDocumentsBetweenDatesRequest.newBuilder()
            .setStart(start)
            .setStop(stop)
            .build();

    return blockingStub.getDocumentsBetweenDates(request);
  }

  public Timelines.GetTimelinesForAutoScheduleResponse getTimelinesForAutoSchedule(
      String channel, Instant start, Instant stop) {
    Timelines.Channel.Builder builder = Timelines.Channel.newBuilder().setId(channel);

    Timelines.GetTimelinesForAutoScheduleRequest request =
        Timelines.GetTimelinesForAutoScheduleRequest.newBuilder()
            .setStart(start.toString())
            .setStop(stop.toString())
            .setChannel(builder)
            .build();

    return blockingStub.getTimelinesForAutoSchedule(request);
  }

  public Timelines.GetTimelinesForRepeatScheduleResponse getTimelinesForRepeatSchedule(
      String channel,
      Instant start,
      Timelines.GetTimeslinesForRepeatScheduleParams.Builder params) {
    Timelines.Channel.Builder builderChannel = Timelines.Channel.newBuilder().setId(channel);

    Timelines.GetTimelinesForRepeatScheduleRequest request =
        Timelines.GetTimelinesForRepeatScheduleRequest.newBuilder()
            .setChannel(builderChannel)
            .setStart(start.toString())
            .setParams(params)
            .build();

    return blockingStub.getTimelinesForRepeatSchedule(request);
  }

  public void updateTimelinesBulk(List<String> listIds, Timeline timeline) {
    Timelines.UpdateTimelinesBulkRequest.Builder builder =
        Timelines.UpdateTimelinesBulkRequest.newBuilder();

    if (listIds != null) builder.addAllDeleteIds(listIds);

    if (timeline != null) builder.addCreateObjects(timeline);

    blockingStub.updateTimelinesBulk(builder.build());
  }

  public GetPopulatedTimelinesByChannelIdResponse getPopulatedTimelinesByChannelId(
      String id, long start, long stop, List<String> timelinesToExclude) {
    GetPopulatedTimelinesByChannelIdRequest.Builder builder =
        GetPopulatedTimelinesByChannelIdRequest.newBuilder();

    if (!id.isEmpty()) {
      builder.setChannelId(id);
    }

    if (start != 0) {
      builder.setStart(start);
    }

    if (stop != 0) {
      builder.setStop(stop);
    }

    if (!timelinesToExclude.isEmpty()) {
      builder.addAllTimelinesToExclude(timelinesToExclude);
    }

    return blockingStub.getPopulatedTimelinesByChannelId(builder.build());
  }

  public Timelines.DeleteTimelinesInChannelByDatesResponse deleteTimelinesInChannelByDatesRequest(
      String channel, String start, String stop) {
    Timelines.DeleteTimelinesInChannelByDatesRequest request =
        Timelines.DeleteTimelinesInChannelByDatesRequest.newBuilder()
            .setChannelId(channel)
            .setStart(start)
            .setEnd(stop)
            .build();

    return blockingStub.deleteTimelinesInChannelByDates(request);
  }

  public Timelines.CreateResponse createTimeline(Timeline timeline) {
    Timelines.CreateRequest request =
        Timelines.CreateRequest.newBuilder().setDocument(timeline).build();
    return blockingStub.create(request);
  }

  public Timelines.GetTimelinesByChannelIdResponse getTimelinesByChannelId(
      String channelId, String start, String stop) {
    Timelines.GetTimelinesByChannelIdRequest.Builder request =
        Timelines.GetTimelinesByChannelIdRequest.newBuilder()
            .setChannelId(channelId)
            .setStart(start);

    if (!stop.isEmpty()) request.setStop(stop);

    return blockingStub.getTimelinesByChannelId(request.build());
  }

  public Timelines.GetTimelineIdsByClipIdResponse getTimelineByClipId(String id, String from) {
    tv.pluto.ip.dal.timeline.Timelines.GetTimelineIdsByClipIdRequest request =
        tv.pluto.ip.dal.timeline.Timelines.GetTimelineIdsByClipIdRequest.newBuilder()
            .setClipId(id)
            .setFrom(from)
            .build();
    return blockingStub.getTimelineIdsByClipId(request);
  }

  public Timelines.GetTimelinesLiteByChannelIdResponse getTimelinesLiteByChannelIdRequest(
      String id, Long start, Long stop) {
    Timelines.GetTimelinesLiteByChannelIdRequest.Builder builder =
        Timelines.GetTimelinesLiteByChannelIdRequest.newBuilder();
    if (id != null) builder.setChannelId(id);
    if (start != null) builder.setStart(start);
    if (stop != null) builder.setStop(stop);

    return blockingStub.getTimelinesLiteByChannelId(builder.build());
  }

  public Iterator<Timeline> getDocumentsBetweenStartDates(String begin, String end, String id) {
    Timelines.GetDocumentsBetweenStartDatesRequest.Builder builder =
        Timelines.GetDocumentsBetweenStartDatesRequest.newBuilder();
    if (!id.isEmpty()) builder.setChannelId(id);

    if (!begin.isEmpty()) builder.setStartBegin(begin);

    if (!end.isEmpty()) builder.setStartEnd(end);

    return blockingStub.getDocumentsBetweenStartDates(builder.build());
  }

  public Iterator<Timeline> getDocumentsBetweenStartDatesCancel(
      String begin, String end, String id) {
    Timelines.GetDocumentsBetweenStartDatesRequest.Builder builder =
        Timelines.GetDocumentsBetweenStartDatesRequest.newBuilder();
    if (!id.isEmpty()) builder.setChannelId(id);
    if (!begin.isEmpty()) builder.setStartBegin(begin);
    if (!end.isEmpty()) builder.setStartEnd(end);

    LinkedBlockingQueue<Timeline> queue = new LinkedBlockingQueue<>();

    ClientResponseObserver<Timelines.GetDocumentsBetweenStartDatesRequest, Timeline>
        responseObserver =
            new ClientResponseObserver<>() {
              @Override
              public void beforeStart(
                  ClientCallStreamObserver<Timelines.GetDocumentsBetweenStartDatesRequest>
                      requestStream) {
                LogUtils.logInfoMessage("Starting getDocumentsBetweenStartDatesCancel");
              }

              @Override
              public void onNext(Timeline value) {
                try {
                  queue.put(value);
                } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
                }
                LogUtils.logInfoMessage("Received response: " + value);
              }

              @Override
              public void onError(Throwable t) {
                LogUtils.logInfoMessage("Error response: " + t);
              }

              @Override
              public void onCompleted() {
                LogUtils.logInfoMessage("Stream completed");
              }
            };

    stub.getDocumentsBetweenStartDates(builder.build(), responseObserver);

    try {
      // Wait for a bit before canceling
      Thread.sleep(10);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }

    // Cancel the request by invoking onError on the responseObserver
    responseObserver.onError(new RuntimeException("Request cancelled by client"));
    return queue.iterator();
  }

  public Timelines.AggregateByEpisodesResponse aggregateByEpisodesRequest(
      List<String> episodeIds, String channelId, String minStart) {
    Timelines.AggregateByEpisodesRequest.Builder builder =
        Timelines.AggregateByEpisodesRequest.newBuilder();

    if (episodeIds != null) builder.addAllEpisodeIds(episodeIds);

    if (channelId != null) builder.setChannelId(channelId);

    if (minStart != null) builder.setMinStart(minStart);

    return blockingStub.aggregateByEpisodes(builder.build());
  }

  public Timelines.Filter timesFilter(String field, String value, String type, String operator) {
    Timelines.Filter.Builder builder = Timelines.Filter.newBuilder();
    if (field != null) builder.setField(field);
    if (value != null) builder.setValue(value);
    if (type != null) builder.setType(type);
    if (operator != null) builder.setOperator(operator);

    return builder.build();
  }

  public Timelines.GetAllByResponse getAllByRequest(
      String channelId,
      List<Timelines.DateFilter> startFilters,
      List<Timelines.DateFilter> stopFilters,
      List<Timelines.Filter> listFiltersBodyInTimelines,
      String distinct) {

    Timelines.GetAllByRequest.Builder requestBuilder = Timelines.GetAllByRequest.newBuilder();

    if (channelId != null && !channelId.isEmpty()) {
      requestBuilder.setChannelId(channelId);
    }

    if (startFilters != null) {
      requestBuilder.addAllStart(startFilters);
    }

    if (stopFilters != null) {
      requestBuilder.addAllStop(stopFilters);
    }

    if (listFiltersBodyInTimelines != null && !listFiltersBodyInTimelines.isEmpty()) {
      requestBuilder.addAllFilters(listFiltersBodyInTimelines);
    }

    if (distinct != null && !distinct.isEmpty()) {
      requestBuilder.setDistinct(distinct);
    }

    return blockingStub.getAllBy(requestBuilder.build());
  }
}
