public async Task Test_TraktSyncModule_AddWatchedHistoryItems()
        {
            string postJson = await TestUtility.SerializeObject(AddHistoryPost);

            postJson.Should().NotBeNullOrEmpty();

            TraktClient client = TestUtility.GetOAuthMockClient(ADD_WATCHED_HISTORY_ITEMS_URI, postJson, HISTORY_POST_RESPONSE_JSON);
            TraktResponse <ITraktSyncHistoryPostResponse> response = await client.Sync.AddWatchedHistoryItemsAsync(AddHistoryPost);

            response.Should().NotBeNull();
            response.IsSuccess.Should().BeTrue();
            response.HasValue.Should().BeTrue();
            response.Value.Should().NotBeNull();

            ITraktSyncHistoryPostResponse responseValue = response.Value;

            responseValue.Added.Should().NotBeNull();
            responseValue.Added.Movies.Should().Be(2);
            responseValue.Added.Episodes.Should().Be(72);
            responseValue.Added.Shows.Should().NotHaveValue();
            responseValue.Added.Seasons.Should().NotHaveValue();

            responseValue.NotFound.Should().NotBeNull();
            responseValue.NotFound.Movies.Should().NotBeNull().And.HaveCount(1);

            ITraktPostResponseNotFoundMovie[] movies = responseValue.NotFound.Movies.ToArray();

            movies[0].Ids.Should().NotBeNull();
            movies[0].Ids.Trakt.Should().Be(0);
            movies[0].Ids.Slug.Should().BeNullOrEmpty();
            movies[0].Ids.Imdb.Should().Be("tt0000111");
            movies[0].Ids.Tmdb.Should().BeNull();

            responseValue.NotFound.Shows.Should().NotBeNull().And.BeEmpty();
            responseValue.NotFound.Seasons.Should().NotBeNull().And.BeEmpty();
            responseValue.NotFound.Episodes.Should().NotBeNull().And.BeEmpty();
        }
        public TraktSyncMoviesResult SyncMovies()
        {
            _mediaPortalServices.GetLogger().Info("Trakt: start sync movies");

            ValidateAuthorization();

            TraktSyncMoviesResult  syncMoviesResult     = new TraktSyncMoviesResult();
            TraktMovies            traktMovies          = _traktCache.RefreshMoviesCache();
            IList <Movie>          traktUnWatchedMovies = traktMovies.UnWatched;
            IList <MovieWatched>   traktWatchedMovies   = traktMovies.Watched;
            IList <MovieCollected> traktCollectedMovies = traktMovies.Collected;

            Guid[] types =
            {
                MediaAspect.ASPECT_ID,              MovieAspect.ASPECT_ID,            VideoAspect.ASPECT_ID,       ImporterAspect.ASPECT_ID,
                ExternalIdentifierAspect.ASPECT_ID, ProviderResourceAspect.ASPECT_ID, VideoStreamAspect.ASPECT_ID,
                VideoAudioStreamAspect.ASPECT_ID
            };

            IContentDirectory contentDirectory = _mediaPortalServices.GetServerConnectionManager().ContentDirectory;

            if (contentDirectory == null)
            {
                throw new MediaLibraryNotConnectedException("ML not connected");
            }

            Guid?           userProfile = null;
            IUserManagement userProfileDataManagement = _mediaPortalServices.GetUserManagement();

            if (userProfileDataManagement != null && userProfileDataManagement.IsValidUser)
            {
                userProfile = userProfileDataManagement.CurrentUser.ProfileId;
            }

            #region Get local database info

            IList <MediaItem> collectedMovies = contentDirectory.SearchAsync(new MediaItemQuery(types, null, null), true, userProfile, false).Result;

            if (collectedMovies.Any())
            {
                syncMoviesResult.CollectedInLibrary = collectedMovies.Count;
                _mediaPortalServices.GetLogger().Info("Trakt: found {0} collected movies available to sync in media library", collectedMovies.Count);
            }

            List <MediaItem> watchedMovies = collectedMovies.Where(MediaItemAspectsUtl.IsWatched).ToList();

            if (watchedMovies.Any())
            {
                syncMoviesResult.WatchedInLibrary = watchedMovies.Count;
                _mediaPortalServices.GetLogger().Info("Trakt: found {0} watched movies available to sync in media library", watchedMovies.Count);
            }

            #endregion

            #region Mark movies as unwatched in local database

            _mediaPortalServices.GetLogger().Info("Trakt: start marking movies as unwatched in media library");
            if (traktUnWatchedMovies.Any())
            {
                foreach (var movie in traktUnWatchedMovies)
                {
                    var localMovie = watchedMovies.FirstOrDefault(m => MovieMatch(m, movie));
                    if (localMovie == null)
                    {
                        continue;
                    }

                    _mediaPortalServices.GetLogger().Info(
                        "Marking movie as unwatched in library, movie is not watched on trakt. Title = '{0}', Year = '{1}', IMDb ID = '{2}', TMDb ID = '{3}'",
                        movie.Title, movie.Year.HasValue ? movie.Year.ToString() : "<empty>", movie.Imdb ?? "<empty>",
                        movie.Tmdb.HasValue ? movie.Tmdb.ToString() : "<empty>");

                    if (_mediaPortalServices.MarkAsUnWatched(localMovie).Result)
                    {
                        syncMoviesResult.MarkedAsUnWatchedInLibrary++;
                    }
                }

                // update watched set
                watchedMovies = collectedMovies.Where(MediaItemAspectsUtl.IsWatched).ToList();
            }

            #endregion

            #region Mark movies as watched in local database

            _mediaPortalServices.GetLogger().Info("Trakt: start marking movies as watched in media library");
            if (traktWatchedMovies.Any())
            {
                foreach (var twm in traktWatchedMovies)
                {
                    var localMovie = collectedMovies.FirstOrDefault(m => MovieMatch(m, twm));
                    if (localMovie == null)
                    {
                        continue;
                    }

                    _mediaPortalServices.GetLogger().Info(
                        "Marking movie as watched in library, movie is watched on trakt. Plays = '{0}', Title = '{1}', Year = '{2}', IMDb ID = '{3}', TMDb ID = '{4}'",
                        twm.Plays, twm.Title, twm.Year.HasValue ? twm.Year.ToString() : "<empty>",
                        twm.Imdb ?? "<empty>", twm.Tmdb.HasValue ? twm.Tmdb.ToString() : "<empty>");

                    if (_mediaPortalServices.MarkAsWatched(localMovie).Result)
                    {
                        syncMoviesResult.MarkedAsWatchedInLibrary++;
                    }
                }
            }

            #endregion

            #region Add movies to watched history at trakt.tv

            _mediaPortalServices.GetLogger().Info("Trakt: finding movies to add to watched history");

            List <TraktSyncHistoryPostMovie> syncWatchedMovies = (from movie in watchedMovies
                                                                  where !traktWatchedMovies.ToList().Exists(c => MovieMatch(movie, c))
                                                                  select new TraktSyncHistoryPostMovie
            {
                Ids = new TraktMovieIds
                {
                    Imdb = MediaItemAspectsUtl.GetMovieImdbId(movie),
                    Tmdb = MediaItemAspectsUtl.GetMovieTmdbId(movie)
                },
                Title = MediaItemAspectsUtl.GetMovieTitle(movie),
                Year = MediaItemAspectsUtl.GetMovieYear(movie),
                WatchedAt = MediaItemAspectsUtl.GetLastPlayedDate(movie),
            }).ToList();

            if (syncWatchedMovies.Any())
            {
                _mediaPortalServices.GetLogger().Info("Trakt: trying to add {0} watched movies to trakt watched history", syncWatchedMovies.Count);

                ITraktSyncHistoryPostResponse watchedResponse = _traktClient.AddWatchedHistoryItems(new TraktSyncHistoryPost {
                    Movies = syncWatchedMovies
                });
                syncMoviesResult.AddedToTraktWatchedHistory = watchedResponse.Added?.Movies;
                _traktCache.ClearLastActivity(FileName.WatchedMovies.Value);

                if (watchedResponse.Added?.Movies != null)
                {
                    _mediaPortalServices.GetLogger().Info("Trakt: successfully added {0} watched movies to trakt watched history", watchedResponse.Added.Movies.Value);
                }
            }

            #endregion

            #region Add movies to collection at trakt.tv

            _mediaPortalServices.GetLogger().Info("Trakt: finding movies to add to collection");

            List <TraktSyncCollectionPostMovie> syncCollectedMovies = (from movie in collectedMovies
                                                                       where !traktCollectedMovies.ToList().Exists(c => MovieMatch(movie, c))
                                                                       select new TraktSyncCollectionPostMovie
            {
                MediaType = MediaItemAspectsUtl.GetVideoMediaType(movie),
                MediaResolution = MediaItemAspectsUtl.GetVideoResolution(movie),
                Audio = MediaItemAspectsUtl.GetVideoAudioCodec(movie),
                AudioChannels = MediaItemAspectsUtl.GetVideoAudioChannel(movie),
                ThreeDimensional = false,

                Ids = new TraktMovieIds
                {
                    Imdb = MediaItemAspectsUtl.GetMovieImdbId(movie),
                    Tmdb = MediaItemAspectsUtl.GetMovieTmdbId(movie)
                },
                Title = MediaItemAspectsUtl.GetMovieTitle(movie),
                Year = MediaItemAspectsUtl.GetMovieYear(movie),
                CollectedAt = MediaItemAspectsUtl.GetDateAddedToDb(movie)
            }).ToList();

            if (syncCollectedMovies.Any())
            {
                _mediaPortalServices.GetLogger().Info("Trakt: trying to add {0} collected movies to trakt collection", syncCollectedMovies.Count);

                foreach (var traktSyncCollectionPostMovie in syncCollectedMovies)
                {
                    string audio     = traktSyncCollectionPostMovie.Audio?.DisplayName;
                    string channel   = traktSyncCollectionPostMovie.AudioChannels?.DisplayName;
                    string res       = traktSyncCollectionPostMovie.MediaResolution?.DisplayName;
                    string mediatype = traktSyncCollectionPostMovie.MediaType?.DisplayName;
                    string name      = traktSyncCollectionPostMovie.Title;
                    _mediaPortalServices.GetLogger().Info("Trakt: {0}, {1}, {2}, {3}, {4}", audio, channel, res, mediatype, name);
                }
                ITraktSyncCollectionPostResponse collectionResponse = _traktClient.AddCollectionItems(new TraktSyncCollectionPost {
                    Movies = syncCollectedMovies
                });
                syncMoviesResult.AddedToTraktCollection = collectionResponse.Added?.Movies;
                _traktCache.ClearLastActivity(FileName.CollectedMovies.Value);

                if (collectionResponse.Added?.Movies != null)
                {
                    _mediaPortalServices.GetLogger().Info("Trakt: successfully added {0} collected movies to trakt collection", collectionResponse.Added.Movies.Value);
                }
            }
            #endregion

            return(syncMoviesResult);
        }
        public TraktSyncEpisodesResult SyncSeries()
        {
            _mediaPortalServices.GetLogger().Info("Trakt: start sync series");

            ValidateAuthorization();

            TraktSyncEpisodesResult  syncEpisodesResult     = new TraktSyncEpisodesResult();
            TraktEpisodes            traktEpisodes          = _traktCache.RefreshSeriesCache();
            IList <Episode>          traktUnWatchedEpisodes = traktEpisodes.UnWatched;
            IList <EpisodeWatched>   traktWatchedEpisodes   = traktEpisodes.Watched;
            IList <EpisodeCollected> traktCollectedEpisodes = traktEpisodes.Collected;

            Guid[] types =
            {
                MediaAspect.ASPECT_ID,            EpisodeAspect.ASPECT_ID, VideoAspect.ASPECT_ID, ImporterAspect.ASPECT_ID,
                ProviderResourceAspect.ASPECT_ID, ExternalIdentifierAspect.ASPECT_ID
            };
            var contentDirectory = _mediaPortalServices.GetServerConnectionManager().ContentDirectory;

            if (contentDirectory == null)
            {
                throw new MediaLibraryNotConnectedException("ML not connected");
            }

            Guid?           userProfile = null;
            IUserManagement userProfileDataManagement = _mediaPortalServices.GetUserManagement();

            if (userProfileDataManagement != null && userProfileDataManagement.IsValidUser)
            {
                userProfile = userProfileDataManagement.CurrentUser.ProfileId;
            }

            #region Get data from local database

            IList <MediaItem> localEpisodes = contentDirectory.SearchAsync(new MediaItemQuery(types, null, null), true, userProfile, false).Result;

            if (localEpisodes.Any())
            {
                syncEpisodesResult.CollectedInLibrary = localEpisodes.Count;
                _mediaPortalServices.GetLogger().Info("Trakt: found {0} total episodes in library", localEpisodes.Count);
            }

            List <MediaItem> localWatchedEpisodes = localEpisodes.Where(MediaItemAspectsUtl.IsWatched).ToList();

            if (localWatchedEpisodes.Any())
            {
                syncEpisodesResult.WatchedInLibrary = localWatchedEpisodes.Count;
                _mediaPortalServices.GetLogger().Info("Trakt: found {0} episodes watched in library", localWatchedEpisodes.Count);
            }

            #endregion

            #region Mark episodes as unwatched in local database

            _mediaPortalServices.GetLogger().Info("Trakt: start marking series episodes as unwatched in media library");
            if (traktUnWatchedEpisodes.Any())
            {
                // create a unique key to lookup and search for faster
                ILookup <string, MediaItem> localLookupEpisodes = localWatchedEpisodes.ToLookup(twe => CreateLookupKey(twe), twe => twe);

                foreach (var episode in traktUnWatchedEpisodes)
                {
                    string tvdbKey = CreateLookupKey(episode);

                    var watchedEpisode = localLookupEpisodes[tvdbKey].FirstOrDefault();
                    if (watchedEpisode != null)
                    {
                        _mediaPortalServices.GetLogger().Info(
                            "Marking episode as unwatched in library, episode is not watched on trakt. Title = '{0}', Year = '{1}', Season = '{2}', Episode = '{3}', Show TVDb ID = '{4}', Show IMDb ID = '{5}'",
                            episode.ShowTitle, episode.ShowYear.HasValue ? episode.ShowYear.ToString() : "<empty>", episode.Season,
                            episode.Number, episode.ShowTvdbId.HasValue ? episode.ShowTvdbId.ToString() : "<empty>",
                            episode.ShowImdbId ?? "<empty>");

                        if (_mediaPortalServices.MarkAsUnWatched(watchedEpisode).Result)
                        {
                            syncEpisodesResult.MarkedAsUnWatchedInLibrary++;
                        }

                        // update watched episodes
                        localWatchedEpisodes.Remove(watchedEpisode);
                    }
                }
            }

            #endregion

            #region Mark episodes as watched in local database

            _mediaPortalServices.GetLogger().Info("Trakt: start marking series episodes as watched in media library");
            if (traktWatchedEpisodes.Any())
            {
                // create a unique key to lookup and search for faster
                ILookup <string, EpisodeWatched> onlineEpisodes = traktWatchedEpisodes.ToLookup(twe => CreateLookupKey(twe), twe => twe);
                List <MediaItem> localUnWatchedEpisodes         = localEpisodes.Except(localWatchedEpisodes).ToList();
                foreach (var episode in localUnWatchedEpisodes)
                {
                    string tvdbKey = CreateLookupKey(episode);

                    var traktEpisode = onlineEpisodes[tvdbKey].FirstOrDefault();
                    if (traktEpisode != null)
                    {
                        _mediaPortalServices.GetLogger().Info(
                            "Marking episode as watched in library, episode is watched on trakt. Plays = '{0}', Title = '{1}', Year = '{2}', Season = '{3}', Episode = '{4}', Show TVDb ID = '{5}', Show IMDb ID = '{6}', Last Watched = '{7}'",
                            traktEpisode.Plays, traktEpisode.ShowTitle,
                            traktEpisode.ShowYear.HasValue ? traktEpisode.ShowYear.ToString() : "<empty>", traktEpisode.Season,
                            traktEpisode.Number, traktEpisode.ShowTvdbId.HasValue ? traktEpisode.ShowTvdbId.ToString() : "<empty>",
                            traktEpisode.ShowImdbId ?? "<empty>", traktEpisode.WatchedAt);

                        if (_mediaPortalServices.MarkAsWatched(episode).Result)
                        {
                            syncEpisodesResult.MarkedAsWatchedInLibrary++;
                        }
                    }
                }
            }

            #endregion

            #region Add episodes to watched history at trakt.tv

            ITraktSyncHistoryPost syncHistoryPost = GetWatchedShowsForSync(localWatchedEpisodes, traktWatchedEpisodes);
            if (syncHistoryPost.Shows != null && syncHistoryPost.Shows.Any())
            {
                _mediaPortalServices.GetLogger().Info("Trakt: trying to add {0} watched episodes to trakt watched history", syncHistoryPost.Shows.Count());
                ITraktSyncHistoryPostResponse response = _traktClient.AddWatchedHistoryItems(syncHistoryPost);
                syncEpisodesResult.AddedToTraktWatchedHistory = response.Added?.Episodes;
                _traktCache.ClearLastActivity(FileName.WatchedEpisodes.Value);

                if (response.Added?.Episodes != null)
                {
                    _mediaPortalServices.GetLogger().Info("Trakt: successfully added {0} watched episodes to trakt watched history", response.Added.Episodes.Value);
                }
            }

            #endregion

            #region Add episodes to collection at trakt.tv

            ITraktSyncCollectionPost syncCollectionPost = GetCollectedShowsForSync(localEpisodes, traktCollectedEpisodes);
            if (syncCollectionPost.Shows != null && syncCollectionPost.Shows.Any())
            {
                _mediaPortalServices.GetLogger().Info("Trakt: trying to add {0} collected episodes to trakt collection", syncCollectionPost.Shows.Count());
                ITraktSyncCollectionPostResponse response = _traktClient.AddCollectionItems(syncCollectionPost);
                syncEpisodesResult.AddedToTraktCollection = response.Added?.Episodes;
                _traktCache.ClearLastActivity(FileName.CollectedEpisodes.Value);

                if (response.Added?.Episodes != null)
                {
                    _mediaPortalServices.GetLogger().Info("Trakt: successfully added {0} collected episodes to trakt collection", response.Added.Episodes.Value);
                }
            }

            #endregion

            return(syncEpisodesResult);
        }