/*
 * (c) 2021 Pluto Inc.
 */
package tv.pluto.mongo.utils.labpartner;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.GsonBuilder;
import com.plutotv.test.lib.LogUtils;
import tv.pluto.mongo.entities.episodes.Episodes;
import tv.pluto.mongo.entities.labpartner.AgeRanges;
import tv.pluto.mongo.entities.labpartner.Clip;
import tv.pluto.mongo.entities.labpartner.Episode;
import tv.pluto.mongo.entities.labpartner.GetLabPartnerObjectByEpisodeIdsResponse;
import tv.pluto.mongo.entities.labpartner.Series;
import tv.pluto.mongo.entities.ratingdescriptors.RatingDescriptors;
import tv.pluto.mongo.utils.MongoUtils;
import tv.pluto.mongo.utils.RatingDescriptorsUtils;
import tv.pluto.mongo.utils.episodes.EpisodeUtils;
import tv.pluto.mongo.utils.json.SkipDeserialisation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class LabPartnerUtils extends MongoUtils {
  private final LabPartnerEpisodeUtils labPartnerEpisodeUtils = new LabPartnerEpisodeUtils();
  private final LabPartnerClipUtils labPartnerClipUtils = new LabPartnerClipUtils();
  private final LabPartnerSeriesUtils labPartnerSeriesUtils = new LabPartnerSeriesUtils();
  private final AgeRangesUtils ageRangesUtils = new AgeRangesUtils();
  private final EpisodeUtils episodeUtils = new EpisodeUtils();
  private final RatingDescriptorsUtils ratingDescriptorsUtils = new RatingDescriptorsUtils();

  public LabPartnerUtils() {
    this.gson =
        new GsonBuilder()
            .addDeserializationExclusionStrategy(
                new ExclusionStrategy() {
                  @Override
                  public boolean shouldSkipField(FieldAttributes f) {
                    return f.getAnnotation(SkipDeserialisation.class) != null;
                  }

                  @Override
                  public boolean shouldSkipClass(Class<?> clazz) {
                    return false;
                  }
                })
            .setPrettyPrinting()
            .create();
  }

  public <T> List<T> getFromMap(List<String> targetList, Map<String, T> inputMap) {
    Set<T> populatedList = new HashSet<>();
    if (targetList != null && !targetList.isEmpty()) {
      targetList.forEach(
          tg -> {
            if (inputMap.containsKey(tg)) {
              populatedList.add(inputMap.get(tg));
            }
          });
    }

    return new ArrayList<>(populatedList);
  }

  public Series populateRatingDescriptors(
      Series series, Map<String, RatingDescriptors> ratingDescriptorsMap) {
    if (ratingDescriptorsMap.size() > 0) {
      if (series.getRatingDescriptors() != null) {
        List<RatingDescriptors> seriesRatingDescriptorsPopulated =
            getFromMap(series.getRatingDescriptors(), ratingDescriptorsMap);
        if (seriesRatingDescriptorsPopulated != null
            && !seriesRatingDescriptorsPopulated.isEmpty()) {
          series.setRatingDescriptorss(seriesRatingDescriptorsPopulated);
          series.setRatingDescriptors(Collections.emptyList());
        }
      }

      if (series.getEpisodes() != null && !series.getEpisodes().isEmpty()) {
        series
            .getEpisodes()
            .forEach(
                episode -> {
                  if (episode.getRatingDescriptors() != null) {
                    List<RatingDescriptors> episodeRatingDescriptorsPopulated =
                        getFromMap(episode.getRatingDescriptors(), ratingDescriptorsMap);
                    if (episodeRatingDescriptorsPopulated != null
                        && !episodeRatingDescriptorsPopulated.isEmpty()) {
                      episode.setRatingDescriptorss(episodeRatingDescriptorsPopulated); // TODO
                      episode.setRatingDescriptors(Collections.emptyList());
                    }
                  }

                  if (episode.getClips() != null && !episode.getClips().isEmpty()) {
                    episode
                        .getClips()
                        .forEach(
                            clip -> {
                              if (clip.getRatingDescriptors() != null) {
                                List<RatingDescriptors> clipRatingDescriptorsPopulated =
                                    getFromMap(clip.getRatingDescriptors(), ratingDescriptorsMap);
                                if (clipRatingDescriptorsPopulated != null
                                    && !clipRatingDescriptorsPopulated.isEmpty()) {
                                  clip.setRatingDescriptorss(clipRatingDescriptorsPopulated);
                                  clip.setRatingDescriptors(Collections.emptyList());
                                }
                              }
                            });
                  }
                });
      }
    }

    return series;
  }

  public GetLabPartnerObjectByEpisodeIdsResponse getLabPartnerObjectByEpisodeIds(
      List<String> episodeIds) {
    GetLabPartnerObjectByEpisodeIdsResponse returnObject =
        new GetLabPartnerObjectByEpisodeIdsResponse();

    List<Episode> episodes = labPartnerEpisodeUtils.getDocumentsByIds(episodeIds);
    Map<String, Clip> clipsMap = new HashMap<>();
    Map<String, AgeRanges> ageRangeMap = new HashMap<>();
    Map<String, RatingDescriptors> ratingDescriptorsMap = new HashMap<>();
    List<String> ratingDescriptorSlugList = new ArrayList<>();
    List<String> ageRangeIds = new ArrayList<>();
    List<String> uniqueClipIds = new ArrayList<>();

    if (episodes.isEmpty()) {
      return returnObject;
    }

    if (!episodeIds.isEmpty()) {
      episodes.forEach(
          episode -> {
            episode
                .getSources()
                .forEach(
                    source -> {
                      uniqueClipIds.add(source.getClip());
                    });
            if (episode.getRatingDescriptors() != null
                && !episode.getRatingDescriptors().isEmpty()) {
              ratingDescriptorSlugList.addAll(episode.getRatingDescriptors());
            }
            if (episode.getAgeRange() != null && episode.getAgeRange().isEmpty()) {
              episode.getAgeRange().forEach(range -> ageRangeIds.add(range.getId()));
            }
          });
    }

    if (!uniqueClipIds.isEmpty()) {
      List<Clip> clips = labPartnerClipUtils.getDocumentsByIds(uniqueClipIds);

      if (clips != null && !clips.isEmpty()) {
        clips.forEach(
            clip -> {
              clipsMap.put(clip.getId(), clip);
            });
        clips.forEach(
            clip -> {
              if (clip.getRatingDescriptors() != null && !clip.getRatingDescriptors().isEmpty()) {
                ratingDescriptorSlugList.addAll(clip.getRatingDescriptors());
              }
            });
      }
    }

    episodes.forEach(
        episode -> {
          Episodes ep = episodeUtils.getDocumentById(episode.getId());
          ep.getAgeRange()
              .forEach(
                  ageRange -> {
                    ageRangeIds.add(ageRange.toString());
                  });
        });

    List<String> uniqueAgeRangeIds = ageRangeIds.stream().distinct().collect(Collectors.toList());

    if (!uniqueAgeRangeIds.isEmpty()) {
      List<AgeRanges> ageRanges = ageRangesUtils.getDocumentsByIds(uniqueAgeRangeIds);

      if (!ageRanges.isEmpty()) {
        ageRanges.forEach(e -> ageRangeMap.put(e.getId(), e));
      }
    }

    episodes.forEach(
        episode -> {
          if (episode != null && episode.getSources() != null && !episode.getSources().isEmpty()) {
            episode
                .getSources()
                .forEach(
                    source -> {
                      if (clipsMap.containsKey(source.getClip())) {
                        if (episode.getClips() == null) {
                          episode.setClips(new ArrayList<>());
                        } else if (!(episode.getClips() instanceof ArrayList)) {
                          episode.setClips(new ArrayList<>(episode.getClips()));
                        }
                        Clip clip = clipsMap.get(source.getClip());
                        episode.getClips().add(clip);
                      }
                    });
          }

          Episodes ep = episodeUtils.getDocumentById(episode.getId());
          if (ep.getAgeRange() != null && !ep.getAgeRange().isEmpty()) {
            List<AgeRanges> ageRangesPopulated = new ArrayList<>();
            ep.getAgeRange()
                .forEach(
                    ageRange -> {
                      if (ageRangeMap.containsKey(ageRange.toString())) {
                        ageRangesPopulated.add(ageRangeMap.get(ageRange.toString()));
                      }
                    });

            // Set age range objects
            episode.setAgeRange(ageRangesPopulated);
          }
        });

    List<String> seriesIds =
        episodes.stream()
            .map(episode -> episode.getSeries().toString())
            .distinct()
            .collect(Collectors.toList());

    if (seriesIds.size() > 0) {
      if (seriesIds.size() > 1) {
        // eslint-disable-next-line max-len
        LogUtils.logSevereMessageThenFail(
            "EpisodeIds are associated with more than one series. Only include episodes associated with one series.");
      }
    }

    Series series =
        labPartnerSeriesUtils.getDocumentById(seriesIds.get(0)); // should be a list of 1 item.

    if (series == null) {
      LogUtils.logSevereMessageThenFail("Series matching episode fk seriesId not found.");
    }

    if (series.getRatingDescriptors() != null && !series.getRatingDescriptors().isEmpty()) {
      ratingDescriptorSlugList.addAll(series.getRatingDescriptors());
    }

    if (!ratingDescriptorSlugList.isEmpty()) {
      List<RatingDescriptors> ratingDescriptorsList =
          ratingDescriptorsUtils.getRatingDescriptorsBySlugs(ratingDescriptorSlugList);

      if (!ratingDescriptorsList.isEmpty()) {
        ratingDescriptorsMap =
            ratingDescriptorsList.stream()
                .collect(Collectors.toMap(RatingDescriptors::getSlug, item -> item));
      }
    }

    series.setEpisodes(episodes);

    returnObject.setSeries(populateRatingDescriptors(series, ratingDescriptorsMap));
    returnObject.setType(series.getType());

    return returnObject;
  }

  public void sortInnerLists(Series series) {
    if (series != null) {
      // Sort Rating Descriptors in series
      series.getRatingDescriptorss().sort(Comparator.comparing(RatingDescriptors::getId));

      if (series.getEpisodes() != null && !series.getEpisodes().isEmpty()) {
        series
            .getEpisodes()
            .forEach(
                episode -> {
                  // Sort Rating Descriptors in episode
                  episode
                      .getRatingDescriptorss()
                      .sort(Comparator.comparing(RatingDescriptors::getId));
                  // Sort Age ranges in episode
                  episode.getAgeRange().sort(Comparator.comparing(AgeRanges::getId));

                  if (episode.getClips() != null && !episode.getClips().isEmpty()) {
                    // Sort Rating Descriptors in clip
                    episode
                        .getClips()
                        .forEach(
                            clip -> {
                              clip.getRatingDescriptorss()
                                  .sort(Comparator.comparing(RatingDescriptors::getId));
                            });
                  }
                });
      }
    }
  }
}
