package tv.pluto.mongo.utils.populated_episodes;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.GsonBuilder;
import com.mongodb.BasicDBObject;
import com.plutotv.test.lib.LogUtils;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import tv.pluto.mongo.entities.episodes.AgeRange;
import tv.pluto.mongo.entities.episodes.Channel;
import tv.pluto.mongo.entities.episodes.Clip;
import tv.pluto.mongo.entities.episodes.ContentPopulated;
import tv.pluto.mongo.entities.episodes.EpisodePopulated;
import tv.pluto.mongo.entities.episodes.Episodes;
import tv.pluto.mongo.entities.episodes.Series;
import tv.pluto.mongo.entities.episodes.VodCategory;
import tv.pluto.mongo.entities.episodes.VodCategoryEntry;
import tv.pluto.mongo.entities.vodcategoryentries.VodCategoryEntries;
import tv.pluto.mongo.utils.MongoUtils;
import tv.pluto.mongo.utils.episodes.AgeRangeUtils;
import tv.pluto.mongo.utils.episodes.EpisodeUtils;
import tv.pluto.mongo.utils.json.SkipDeserialisation;

public class PopulatedEpisodesUtils extends MongoUtils {

  class ClipUtils extends MongoUtils {
    public ClipUtils() {
      super("clips", new Clip());
    }
  }

  class SeriesUtils extends MongoUtils {
    public SeriesUtils() {
      super("series", new Series());
    }
  }

  class ChannelUtils extends MongoUtils {
    public ChannelUtils() {
      super("channels", new Channel());
    }
  }

  class VodCategoryEntriesUtils extends MongoUtils {
    public VodCategoryEntriesUtils() {
      super("vodcategoryentries", new VodCategoryEntry());
    }
  }

  class VodCategoryUtils extends MongoUtils {
    public VodCategoryUtils() {
      super("vodcategories", new VodCategory());
    }
  }

  EpisodeUtils episodeUtils = new EpisodeUtils();
  ClipUtils clipUtils = new ClipUtils();
  SeriesUtils seriesUtils = new SeriesUtils();

  ChannelUtils channelUtils = new ChannelUtils();

  VodCategoryEntriesUtils vodCategoryEntriesUtils = new VodCategoryEntriesUtils();
  VodCategoryUtils vodCategoryUtils = new VodCategoryUtils();

  AgeRangeUtils ageRangeUtils = new AgeRangeUtils();

  tv.pluto.mongo.utils.VodCategoryEntriesUtils vodCategoryEntries =
      new tv.pluto.mongo.utils.VodCategoryEntriesUtils();

  public PopulatedEpisodesUtils() {
    super("episodes", new EpisodePopulated());

    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 List<EpisodePopulated> getPopulatedEpisodesByClipId(
      String clipId, int limit, int offset, Bson sort) {
    List<EpisodePopulated> result = new ArrayList<>();

    BasicDBObject elemMatch =
        new BasicDBObject("$elemMatch", new BasicDBObject("clip", new ObjectId(clipId)));
    BasicDBObject query = new BasicDBObject("sources", elemMatch);
    LogUtils.logInfoMessage("Query: " + query.toJson());

    genericCollection
        .find(query)
        .limit(limit)
        .skip(offset)
        .sort(sort)
        .forEach(e -> result.add((EpisodePopulated) e));

    if (!result.isEmpty()) {
      List<String> clipIds = new ArrayList<>();
      result.forEach(
          episodePopulated -> {
            if (episodePopulated.getContent() != null) {
              episodePopulated.getContent().forEach(e -> clipIds.add(e.getClipId()));
            }
            if (episodePopulated.getSources() != null) {
              episodePopulated.getSources().forEach(e -> clipIds.add(e.getClipId()));
            }
            if (episodePopulated.getBackups() != null) {
              episodePopulated.getBackups().forEach(e -> clipIds.add(e.getClipId()));
            }
          });

      Map<String, Clip> clipsMap =
          clipUtils.getDocumentsByIds(clipIds).stream()
              .map(clip -> (Clip) clip)
              .collect(Collectors.toMap(Clip::getId, clip -> clip));

      result.forEach(
          episodePopulated -> {
            Episodes episode = episodeUtils.getDocumentById(episodePopulated.getId());

            // Populate series
            if (episode.getSeries() != null && !episode.getSeries().isEmpty()) {
              Series series = seriesUtils.getDocumentById(episode.getSeries());
              episodePopulated.setSeries(series);
            }

            // Populate parent
            if (episode.getParent() != null && !episode.getParent().isEmpty()) {
              Episodes parent = episodeUtils.getDocumentById(episode.getParent());
              episodePopulated.setParent(parent);
            }

            // Populate content
            if (episodePopulated.getContent() != null) {
              episodePopulated
                  .getContent()
                  .forEach(
                      e -> {
                        e.setClip(clipsMap.get(e.getClipId()));
                        e.setClipId(null);
                      });
            }

            // Populate sources
            if (episodePopulated.getSources() != null) {
              episodePopulated
                  .getSources()
                  .forEach(
                      e -> {
                        e.setClip(clipsMap.get(e.getClipId()));
                        e.setClipId(null);
                      });
            }

            // Populate backups
            if (episodePopulated.getBackups() != null && !episodePopulated.getBackups().isEmpty()) {
              List<String> backupClipIds =
                  episodePopulated.getBackups().stream()
                      .map(backup -> backup.getClipId())
                      .filter(Objects::nonNull)
                      .collect(Collectors.toList());

              Map<String, Clip> backupClipsMap =
                  clipUtils.getDocumentsByIds(backupClipIds).stream()
                      .map(clip -> (Clip) clip)
                      .collect(Collectors.toMap(Clip::getId, clip -> clip));

              episodePopulated
                  .getBackups()
                  .forEach(
                      backup -> {
                        backup.setClip(backupClipsMap.get(backup.getClipId()));
                        backup.setClipId(null);
                      });
            }

            // Populate channels
            if (episode.getChannels() != null && !episode.getChannels().isEmpty()) {
              List<String> channelIds =
                  episode.getChannels().stream().map(Object::toString).collect(Collectors.toList());

              Map<String, Channel> channelsMap =
                  channelUtils.getDocumentsByIds(channelIds).stream()
                      .map(channel -> (Channel) channel) // Ensure casting if needed
                      .collect(
                          Collectors.toMap(
                              channel -> channel.getId().toString(), // Ensure the key is a String
                              channel -> channel // Ensure the value is of type Channel
                              ));

              List<Channel> channelsPopulated =
                  episode.getChannels().stream()
                      .map(channelId -> channelsMap.get(channelId.toString()))
                      .filter(Objects::nonNull)
                      .collect(Collectors.toList());

              episodePopulated.setChannels(channelsPopulated);
            }

            // Populate vodCategoryEntries
            if (episode.getVodCategoryEntries() != null
                && !episode.getVodCategoryEntries().isEmpty()) {
              // Collect all vodCategoryEntry IDs
              List<String> vodCategoryEntryIds =
                  episode.getVodCategoryEntries().stream()
                      .map(Object::toString)
                      .collect(Collectors.toList());

              Map<String, VodCategoryEntries> vodCategoryEntriesMap =
                  vodCategoryEntries.getDocumentsByIds(vodCategoryEntryIds).stream()
                      .map(entry -> (VodCategoryEntries) entry)
                      .collect(Collectors.toMap(entry -> entry.getId().toString(), entry -> entry));

              // Collect all vodCategory IDs
              List<String> vodCategoryIds =
                  vodCategoryEntriesMap.values().stream()
                      .map(VodCategoryEntries::getVodCategory)
                      .filter(Objects::nonNull)
                      .collect(Collectors.toList());

              // Fetch all vodCategories in a single query
              Map<String, VodCategory> vodCategoriesMap =
                  vodCategoryUtils.getDocumentsByIds(vodCategoryIds).stream()
                      .map(category -> (VodCategory) category)
                      .collect(
                          Collectors.toMap(
                              category -> category.getId().toString(), category -> category));

              // Populate vodCategoryEntriesList
              List<VodCategoryEntry> vodCategoryEntriesList =
                  episode.getVodCategoryEntries().stream()
                      .map(
                          categoryEntryId -> {
                            VodCategoryEntry vodCategoryEntryPopulated =
                                vodCategoryEntriesUtils.getDocumentById(categoryEntryId.toString());
                            VodCategoryEntries vodCategoryEntry =
                                vodCategoryEntriesMap.get(categoryEntryId.toString());

                            if (vodCategoryEntry != null
                                && vodCategoryEntry.getVodCategory() != null) {
                              vodCategoryEntryPopulated.setVodCategory(
                                  vodCategoriesMap.get(vodCategoryEntry.getVodCategory()));
                            }

                            return vodCategoryEntryPopulated;
                          })
                      .filter(Objects::nonNull)
                      .collect(Collectors.toList());

              episodePopulated.setVodCategoryEntries(vodCategoryEntriesList);
            }

            // Populate Age ranges
            if (episode.getAgeRange() != null && !episode.getAgeRange().isEmpty()) {
              List<String> ageRangeIds =
                  episode.getAgeRange().stream().map(Object::toString).collect(Collectors.toList());

              Map<String, AgeRange> ageRangeMap =
                  ageRangeUtils.getDocumentsByIds(ageRangeIds).stream()
                      .map(ageRange -> (AgeRange) ageRange)
                      .collect(
                          Collectors.toMap(
                              ageRange -> ageRange.getId().toString(), ageRange -> ageRange));

              // Populate the ageRanges list
              List<AgeRange> ageRanges =
                  episode.getAgeRange().stream()
                      .map(rangeId -> ageRangeMap.get(rangeId.toString()))
                      .filter(Objects::nonNull)
                      .collect(Collectors.toList());

              episodePopulated.setAgeRange(ageRanges);
            }
          });
    }

    return result;
  }

  public void sortEpisodes(List<EpisodePopulated> episodes) {
    if (episodes != null && !episodes.isEmpty()) {
      episodes.sort(Comparator.comparing(EpisodePopulated::getId));

      episodes.forEach(
          episode -> {
            if (episode.getContent() != null && !episode.getContent().isEmpty()) {
              episode.getContent().sort(Comparator.comparing(ContentPopulated::getId));
            }

            if (episode.getSources() != null && !episode.getSources().isEmpty()) {
              episode.getSources().sort(Comparator.comparing(source -> source.getClip().getId()));
            }

            if (episode.getBackups() != null && !episode.getBackups().isEmpty()) {
              episode.getBackups().sort(Comparator.comparing(backup -> backup.getClip().getId()));
            }

            if (episode.getChannels() != null && !episode.getChannels().isEmpty()) {
              episode.getChannels().sort(Comparator.comparing(Channel::getId));
            }

            if (episode.getVodCategoryEntries() != null
                && !episode.getVodCategoryEntries().isEmpty()) {
              episode.getVodCategoryEntries().sort(Comparator.comparing(VodCategoryEntry::getId));
            }

            if (episode.getAgeRange() != null && !episode.getAgeRange().isEmpty()) {
              episode.getAgeRange().sort(Comparator.comparing(AgeRange::getId));
            }
          });
    }
  }
}
