package tv.pluto.mongo.utils.populated_timeline_drafts;

import com.mongodb.BasicDBObject;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Projections;
import com.mongodb.client.model.Sorts;
import com.plutotv.test.lib.LogUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import tv.pluto.mongo.entities.clips.Clips;
import tv.pluto.mongo.entities.common.AvailabilityWindows;
import tv.pluto.mongo.entities.episodes.Episodes;
import tv.pluto.mongo.entities.episodes.Source;
import tv.pluto.mongo.entities.timelinedrafts.populated_timeline_drafts.TimelineDraftPopulated;
import tv.pluto.mongo.utils.ClipsUtils;
import tv.pluto.mongo.utils.MongoUtils;
import tv.pluto.mongo.utils.episodes.EpisodeUtils;

public class TimelineDraftsByChannelIdUtils extends MongoUtils {
  EpisodeUtils episodeUtils = new EpisodeUtils();
  ClipsUtils clipsUtils = new ClipsUtils();

  public TimelineDraftsByChannelIdUtils() {
    super("timelinedrafts", new TimelineDraftPopulated());
  }

  public List<TimelineDraftPopulated> getPopulatedTimelineDraftsByChannelId(
      String channelId, long start, long stop) {
    List<TimelineDraftPopulated> result = new ArrayList<>();

    Bson matchStage = this.createMatchStage(channelId, start, stop);

    List<Bson> pipeline =
        Arrays.asList(
            matchStage,
            Aggregates.lookup(
                "episodes", // Target collection name
                "episode", // Local field to match
                "_id", // Foreign field from the "orderDetails" collection
                "episode" // Output array field name
                ),
            Aggregates.unwind("$episode"),
            Aggregates.lookup(
                "series", // Target collection name
                "episode.series", // Local field to match
                "_id", // Foreign field from the "orderDetails" collection
                "series" // Output array field name
                ),
            Aggregates.unwind("$series"),
            Aggregates.lookup(
                "clips", // Target collection name
                "episode.sources.clip", // Local field to match
                "_id", // Foreign field from the "orderDetails" collection
                "clips" // Output array field name
                ),
            this.createProjectionStage(),
            Aggregates.sort(Sorts.ascending("start")));

    genericCollection.aggregate(pipeline).forEach(e -> result.add((TimelineDraftPopulated) e));

    result.forEach(
        draft -> {
          Episodes episode = episodeUtils.getDocumentById(draft.getEpisode().getId());
          draft.setAllClipsReady(this.allClipsReady(episode));
          draft.setIsAvailableNow(this.isAvailableNow(episode, new Date()));
          draft.setHasRegionAvailability(this.hasRegionAvailability(episode));
          draft.setProviderIsJwOnly(this.hasUniqueProvider(episode, "jwplatform"));
          draft.setProviderIsJwMixed(this.hasMixedProviderWith(episode, "jwplatform"));
          draft.setHasEmptySources(this.hasClipWithEmptySources(episode));
          draft.setHasLinearAvailabilityWindows(this.hasLinearAvailabilityWindows(episode));
          draft.setHasLinearAvailabilityWindowForDate(
              this.isAvailableNow(episode, new Date(draft.getStart().getTime())));
          draft.setHasAdPods(episode.getAdPodsDuration() > 0);
          draft.setFulfilled(Objects.equals(episode.getAllotment(), episode.getDuration()));
          draft.setAuthor(this.calculateAuthorValue(episode));
        });

    return result;
  }

  public Bson createMatchStage(String channelId, long start, long stop) {
    Date startDate = new Date(start);
    Date stopDate = new Date(stop);

    Bson matchChannel = Filters.eq("channel", new ObjectId(channelId));

    Bson matchDate = new BasicDBObject();

    if (start != 0 && stop != 0) {
      Bson condition1 = Filters.gte("start", startDate);
      Bson condition2 = Filters.lt("stop", stopDate);
      Bson condition3 = Filters.gt("stop", startDate);
      Bson condition4 = Filters.lte("stop", stopDate);
      Bson condition5 = Filters.lte("start", startDate);
      Bson condition6 = Filters.gte("stop", stopDate);

      matchDate =
          Filters.or(
              Filters.and(condition1, condition2),
              Filters.and(condition3, condition4),
              Filters.and(condition5, condition6));
    } else if (start != 0) {
      matchDate = Filters.gte("start", startDate);
    }

    Bson filter = Filters.and(matchChannel, matchDate);

    return Aggregates.match(filter);
  }

  public Bson createProjectionStage() {
    return Aggregates.project(
        Projections.fields(
            Projections.include("name"),
            Projections.include("start"),
            Projections.include("stop"),
            Projections.include("manualOverrideMarker"),
            Projections.include("episode._id"),
            Projections.include("episode.name"),
            Projections.include("episode.allotment"),
            Projections.include("episode.duration"),
            Projections.include("episode.published"),
            Projections.include("episode.is_feed"),
            Projections.include("episode.status"),
            Projections.include("episode.flagged"),
            Projections.include("series"),
            Projections.computed("episode.series._id", "$series._id"),
            Projections.computed("episode.series.name", "$series.name"),
            Projections.include("episode.number"),
            Projections.include("episode.season"),
            Projections.include("episode.adPodsDuration"),
            Projections.include("episode.featured"),
            Projections.include("episode.firstAired"),
            Projections.include("episode.availabilityWindows"),
            Projections.include("episode.regionFilter"),
            Projections.include("episode.activeRegion"),
            Projections.include("episode.lastAired"),
            Projections.include("episode.nextTimeline")));
  }

  public boolean allClipsReady(Episodes episode) {
    LogUtils.logInfoMessage("Checking allClipsReady for episode with ID = " + episode.getId());
    List<Source> sources = episode.getSources();

    final boolean[] result = {true};

    if (!sources.isEmpty()) {
      sources.forEach(
          source -> {
            if (!source.getClip().isEmpty()) {
              Clips clip = clipsUtils.getDocumentById(source.getClip());

              if (!clip.getContentStatus().equals("ready")) {
                result[0] = false;
              }
            }
          });
    }

    return result[0];
  }

  public boolean isAvailableNow(Episodes episode, Date date) {
    LogUtils.logInfoMessage("Checking isAvailableNow for episode with ID = " + episode.getId());

    AvailabilityWindows availabilityWindows = episode.getAvailabilityWindows();

    if (availabilityWindows.getAvod().isEmpty() && availabilityWindows.getLinear().isEmpty()) {
      return false;
    }

    if (availabilityWindows.getLinear().isEmpty()) {
      return false;
    } else {
      return date.getTime() >= availabilityWindows.getLinear().get(0).getStartDate().getTime()
          && date.getTime() <= availabilityWindows.getLinear().get(0).getEndDate().getTime();
    }
  }

  public boolean hasRegionAvailability(Episodes episode) {
    LogUtils.logInfoMessage(
        "Checking hasRegionAvailability for episode with ID = " + episode.getId());

    if (episode.getRegionFilter().isEmpty()) {
      return false;
    } else {
      return episode.getRegionFilter().contains(episode.getActiveRegion());
    }
  }

  public boolean hasUniqueProvider(Episodes episode, String provider) {
    LogUtils.logInfoMessage("Checking hasUniqueProvider for episode with ID = " + episode.getId());

    String errorMessage =
        "Episode's "
            + episode.getId()
            + " sources.clip must be selected and populated in order to call hasUniqueProvider method.";

    if (episode.getIsFeed()) {
      return false;
    }

    if (episode.getSources().isEmpty()) {
      return false;
    }

    AtomicBoolean hasUnique = new AtomicBoolean(true);

    episode
        .getSources()
        .forEach(
            source -> {
              if (source.getClip().isEmpty()) {
                throw new RuntimeException(errorMessage);
              }

              String clipsProvider =
                  ((Clips) clipsUtils.getDocumentById(source.getClip())).getProvider();

              if (!clipsProvider.equals(provider)) {
                hasUnique.set(false);
              }
            });

    return hasUnique.get();
  }

  public boolean hasMixedProviderWith(Episodes episode, String provider) {
    LogUtils.logInfoMessage(
        "Checking hasMixedProviderWith for episode with ID = " + episode.getId());

    String errorMessage =
        "Episode's "
            + episode.getId()
            + " sources.clip must be selected and populated in order to call hasUniqueProvider method.";

    AtomicBoolean hasDifferent = new AtomicBoolean(false);
    AtomicBoolean hasProvider = new AtomicBoolean(false);

    if (episode.getSources().isEmpty()) {
      throw new RuntimeException(errorMessage);
    }

    episode
        .getSources()
        .forEach(
            source -> {
              if (source.getClip().isEmpty()) {
                throw new RuntimeException(errorMessage);
              }

              String clipsProvider =
                  ((Clips) clipsUtils.getDocumentById(source.getClip())).getProvider();

              if (clipsProvider.equals(provider)) {
                hasProvider.set(true);
              }

              if (!clipsProvider.equals(provider)) {
                hasDifferent.set(false);
              }
            });

    if (episode.getIsFeed()) {
      return false;
    }

    return hasProvider.get() && hasDifferent.get();
  }

  public boolean hasClipWithEmptySources(Episodes episode) {
    LogUtils.logInfoMessage(
        "Checking hasClipWithEmptySources for episode with ID = " + episode.getId());

    AtomicBoolean result = new AtomicBoolean(false);

    if (episode.getSources().isEmpty()) {
      return false;
    }

    episode
        .getSources()
        .forEach(
            source -> {
              if (source.getClip().isEmpty()) {
                result.set(true);
              } else {
                Clips clip = clipsUtils.getDocumentById(source.getClip());
                result.set(clip.getSources().isEmpty());
              }
            });

    return result.get();
  }

  public boolean hasLinearAvailabilityWindows(Episodes episode) {
    LogUtils.logInfoMessage(
        "Checking hasLinearAvailabilityWindows for episode with ID = " + episode.getId());

    return !episode.getAvailabilityWindows().getLinear().isEmpty();
  }

  public String calculateAuthorValue(Episodes episode) {
    LogUtils.logInfoMessage(
        "Checking calculateAuthorValue for episode with ID = " + episode.getId());

    List<String> authors = new ArrayList<>();

    episode
        .getSources()
        .forEach(
            source -> {
              if (!source.getClip().isEmpty()) {
                Clips clip = clipsUtils.getDocumentById(source.getClip());

                if (!clip.getAuthor().getName().isEmpty()) {
                  String authorName = clip.getAuthor().getName();

                  if (!authorName.equals("Pluto TV") && !authorName.equals("Pluto TV O&O")) {
                    if (!authors.contains(authorName)) {
                      authors.add(authorName);
                    }
                  }
                }
              }
            });

    if (authors.size() > 1) {
      return "Multiple";
    } else if (authors.size() == 1) {
      return authors.get(0);
    } else {
      return "N/A";
    }
  }
}
