using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.ImportLists.ImportExclusions;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Movies.Collections;
using NzbDrone.Core.Movies.Commands;

namespace NzbDrone.Core.Movies
{
    public class RefreshCollectionService : IExecute<RefreshCollectionsCommand>
    {
        private readonly IProvideMovieInfo _movieInfo;
        private readonly IMovieCollectionService _collectionService;
        private readonly IMovieService _movieService;
        private readonly IMovieMetadataService _movieMetadataService;
        private readonly IAddMovieService _addMovieService;
        private readonly IImportExclusionsService _importExclusionService;

        private readonly Logger _logger;

        public RefreshCollectionService(IProvideMovieInfo movieInfo,
                                        IMovieCollectionService collectionService,
                                        IMovieService movieService,
                                        IMovieMetadataService movieMetadataService,
                                        IAddMovieService addMovieService,
                                        IImportExclusionsService importExclusionsService,
                                        Logger logger)
        {
            _movieInfo = movieInfo;
            _collectionService = collectionService;
            _movieService = movieService;
            _movieMetadataService = movieMetadataService;
            _addMovieService = addMovieService;
            _importExclusionService = importExclusionsService;
            _logger = logger;
        }

        private MovieCollection RefreshCollectionInfo(int collectionId)
        {
            // Get the movie before updating, that way any changes made to the movie after the refresh started,
            // but before this movie was refreshed won't be lost.
            var collection = _collectionService.GetCollection(collectionId);

            _logger.ProgressInfo("Updating info for {0}", collection.Title);

            MovieCollection collectionInfo;
            List<MovieMetadata> movies;

            try
            {
                collectionInfo = _movieInfo.GetCollectionInfo(collection.TmdbId);
            }
            catch (MovieNotFoundException)
            {
                _collectionService.RemoveCollection(collection);
                _logger.Debug("Removing collection not present on TMDb for {0}", collection.Title);

                throw;
            }

            collection.Title = collectionInfo.Title;
            collection.Overview = collectionInfo.Overview;
            collection.CleanTitle = collectionInfo.CleanTitle;
            collection.SortTitle = collectionInfo.SortTitle;
            collection.LastInfoSync = DateTime.UtcNow;
            collection.Images = collectionInfo.Images;

            movies = collectionInfo.Movies;
            movies.ForEach(x => x.CollectionTmdbId = collection.TmdbId);

            var existingMetaForCollection = _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId);

            var updateList = new List<MovieMetadata>();

            foreach (var remoteMovie in movies)
            {
                var existing = existingMetaForCollection.FirstOrDefault(e => e.TmdbId == remoteMovie.TmdbId);

                if (existingMetaForCollection.Any(x => x.TmdbId == remoteMovie.TmdbId))
                {
                    existingMetaForCollection.Remove(existing);
                }

                updateList.Add(remoteMovie);
            }

            _movieMetadataService.UpsertMany(updateList);
            _movieMetadataService.DeleteMany(existingMetaForCollection);

            _logger.Debug("Finished collection refresh for {0}", collection.Title);

            _collectionService.UpdateCollection(collection);

            return collection;
        }

        public bool ShouldRefresh(MovieCollection collection)
        {
            if (collection.LastInfoSync == null || collection.LastInfoSync < DateTime.UtcNow.AddDays(-15))
            {
                _logger.Trace("Collection {0} last updated more than 15 days ago, should refresh.", collection.Title);
                return true;
            }

            if (collection.LastInfoSync >= DateTime.UtcNow.AddHours(-6))
            {
                _logger.Trace("Collection {0} last updated less than 6 hours ago, should not be refreshed.", collection.Title);
                return false;
            }

            return false;
        }

        private void SyncCollectionMovies(MovieCollection collection)
        {
            if (collection.Monitored)
            {
                var existingMovies = _movieService.AllMovieTmdbIds();
                var collectionMovies = _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId);
                var excludedMovies = _importExclusionService.GetAllExclusions().Select(e => e.TmdbId);
                var moviesToAdd = collectionMovies.Where(m => !existingMovies.Contains(m.TmdbId)).Where(m => !excludedMovies.Contains(m.TmdbId));

                if (moviesToAdd.Any())
                {
                    _addMovieService.AddMovies(moviesToAdd.Select(m => new Movie
                    {
                        TmdbId = m.TmdbId,
                        Title = m.Title,
                        ProfileId = collection.QualityProfileId,
                        RootFolderPath = collection.RootFolderPath,
                        MinimumAvailability = collection.MinimumAvailability,
                        AddOptions = new AddMovieOptions
                        {
                            SearchForMovie = collection.SearchOnAdd,
                            AddMethod = AddMovieMethod.Collection
                        },
                        Monitored = true
                    }).ToList(), true);
                }
            }
        }

        public void Execute(RefreshCollectionsCommand message)
        {
            if (message.CollectionIds.Any())
            {
                foreach (var collectionId in message.CollectionIds)
                {
                    var newCollection = RefreshCollectionInfo(collectionId);
                    SyncCollectionMovies(newCollection);
                }
            }
            else
            {
                var allCollections = _collectionService.GetAllCollections().OrderBy(c => c.SortTitle).ToList();

                foreach (var collection in allCollections)
                {
                    try
                    {
                        var newCollection = collection;

                        if (ShouldRefresh(collection) || message.Trigger == CommandTrigger.Manual)
                        {
                            newCollection = RefreshCollectionInfo(collection.Id);
                        }

                        SyncCollectionMovies(newCollection);
                    }
                    catch (MovieNotFoundException)
                    {
                        _logger.Error("Collection '{0}' (TMDb {1}) was not found, it may have been removed from The Movie Database.", collection.Title, collection.TmdbId);
                    }
                }
            }
        }
    }
}
