#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Querying;
using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
namespace MediaBrowser.Controller.Entities.TV
{
    /// 
    /// Class Series.
    /// 
    public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo, IMetadataContainer
    {
        public Series()
        {
            RemoteTrailers = Array.Empty();
            LocalTrailerIds = Array.Empty();
            RemoteTrailerIds = Array.Empty();
            AirDays = Array.Empty();
        }
        public DayOfWeek[] AirDays { get; set; }
        public string AirTime { get; set; }
        [JsonIgnore]
        public override bool SupportsAddingToPlaylist => true;
        [JsonIgnore]
        public override bool IsPreSorted => true;
        [JsonIgnore]
        public override bool SupportsDateLastMediaAdded => true;
        [JsonIgnore]
        public override bool SupportsInheritedParentImages => false;
        [JsonIgnore]
        public override bool SupportsPeople => true;
        /// 
        public IReadOnlyList LocalTrailerIds { get; set; }
        /// 
        public IReadOnlyList RemoteTrailerIds { get; set; }
        /// 
        /// airdate, dvd or absolute.
        /// 
        public string DisplayOrder { get; set; }
        /// 
        /// Gets or sets the status.
        /// 
        /// The status.
        public SeriesStatus? Status { get; set; }
        public override double GetDefaultPrimaryImageAspectRatio()
        {
            double value = 2;
            value /= 3;
            return value;
        }
        public override string CreatePresentationUniqueKey()
        {
            if (LibraryManager.GetLibraryOptions(this).EnableAutomaticSeriesGrouping)
            {
                var userdatakeys = GetUserDataKeys();
                if (userdatakeys.Count > 1)
                {
                    return AddLibrariesToPresentationUniqueKey(userdatakeys[0]);
                }
            }
            return base.CreatePresentationUniqueKey();
        }
        private string AddLibrariesToPresentationUniqueKey(string key)
        {
            var lang = GetPreferredMetadataLanguage();
            if (!string.IsNullOrEmpty(lang))
            {
                key += "-" + lang;
            }
            var folders = LibraryManager.GetCollectionFolders(this)
                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
                .ToArray();
            if (folders.Length == 0)
            {
                return key;
            }
            return key + "-" + string.Join('-', folders);
        }
        private static string GetUniqueSeriesKey(BaseItem series)
        {
            return series.GetPresentationUniqueKey();
        }
        public override int GetChildCount(User user)
        {
            var seriesKey = GetUniqueSeriesKey(this);
            var result = LibraryManager.GetCount(new InternalItemsQuery(user)
            {
                AncestorWithPresentationUniqueKey = null,
                SeriesPresentationUniqueKey = seriesKey,
                IncludeItemTypes = new[] { nameof(Season) },
                IsVirtualItem = false,
                Limit = 0,
                DtoOptions = new DtoOptions(false)
                {
                    EnableImages = false
                }
            });
            return result;
        }
        public override int GetRecursiveChildCount(User user)
        {
            var seriesKey = GetUniqueSeriesKey(this);
            var query = new InternalItemsQuery(user)
            {
                AncestorWithPresentationUniqueKey = null,
                SeriesPresentationUniqueKey = seriesKey,
                DtoOptions = new DtoOptions(false)
                {
                    EnableImages = false
                }
            };
            if (query.IncludeItemTypes.Length == 0)
            {
                query.IncludeItemTypes = new[] { nameof(Episode) };
            }
            query.IsVirtualItem = false;
            query.Limit = 0;
            var totalRecordCount = LibraryManager.GetCount(query);
            return totalRecordCount;
        }
        /// 
        /// Gets the user data key.
        /// 
        /// System.String.
        public override List GetUserDataKeys()
        {
            var list = base.GetUserDataKeys();
            if (this.TryGetProviderId(MetadataProvider.Imdb, out var key))
            {
                list.Insert(0, key);
            }
            if (this.TryGetProviderId(MetadataProvider.Tvdb, out key))
            {
                list.Insert(0, key);
            }
            return list;
        }
        public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
        {
            return GetSeasons(user, new DtoOptions(true));
        }
        public List GetSeasons(User user, DtoOptions options)
        {
            var query = new InternalItemsQuery(user)
            {
                DtoOptions = options
            };
            SetSeasonQueryOptions(query, user);
            return LibraryManager.GetItemList(query);
        }
        private void SetSeasonQueryOptions(InternalItemsQuery query, User user)
        {
            var seriesKey = GetUniqueSeriesKey(this);
            query.AncestorWithPresentationUniqueKey = null;
            query.SeriesPresentationUniqueKey = seriesKey;
            query.IncludeItemTypes = new[] { nameof(Season) };
            query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
            if (user != null && !user.DisplayMissingEpisodes)
            {
                query.IsMissing = false;
            }
        }
        protected override QueryResult GetItemsInternal(InternalItemsQuery query)
        {
            var user = query.User;
            if (query.Recursive)
            {
                var seriesKey = GetUniqueSeriesKey(this);
                query.AncestorWithPresentationUniqueKey = null;
                query.SeriesPresentationUniqueKey = seriesKey;
                if (query.OrderBy.Count == 0)
                {
                    query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
                }
                if (query.IncludeItemTypes.Length == 0)
                {
                    query.IncludeItemTypes = new[] { nameof(Episode), nameof(Season) };
                }
                query.IsVirtualItem = false;
                return LibraryManager.GetItemsResult(query);
            }
            SetSeasonQueryOptions(query, user);
            return LibraryManager.GetItemsResult(query);
        }
        public IEnumerable GetEpisodes(User user, DtoOptions options)
        {
            var seriesKey = GetUniqueSeriesKey(this);
            var query = new InternalItemsQuery(user)
            {
                AncestorWithPresentationUniqueKey = null,
                SeriesPresentationUniqueKey = seriesKey,
                IncludeItemTypes = new[] { nameof(Episode), nameof(Season) },
                OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
                DtoOptions = options
            };
            if (!user.DisplayMissingEpisodes)
            {
                query.IsMissing = false;
            }
            var allItems = LibraryManager.GetItemList(query);
            var allSeriesEpisodes = allItems.OfType().ToList();
            var allEpisodes = allItems.OfType()
                .SelectMany(i => i.GetEpisodes(this, user, allSeriesEpisodes, options))
                .Reverse();
            // Specials could appear twice based on above - once in season 0, once in the aired season
            // This depends on settings for that series
            // When this happens, remove the duplicate from season 0
            return allEpisodes.GroupBy(i => i.Id).Select(x => x.First()).Reverse();
        }
        public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress progress, CancellationToken cancellationToken)
        {
            // Refresh bottom up, children first, then the boxset
            // By then hopefully the  movies within will have Tmdb collection values
            var items = GetRecursiveChildren();
            var totalItems = items.Count;
            var numComplete = 0;
            // Refresh seasons
            foreach (var item in items)
            {
                if (!(item is Season))
                {
                    continue;
                }
                cancellationToken.ThrowIfCancellationRequested();
                if (refreshOptions.RefreshItem(item))
                {
                    await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
                }
                numComplete++;
                double percent = numComplete;
                percent /= totalItems;
                progress.Report(percent * 100);
            }
            // Refresh episodes and other children
            foreach (var item in items)
            {
                if (item is Season)
                {
                    continue;
                }
                cancellationToken.ThrowIfCancellationRequested();
                var skipItem = false;
                var episode = item as Episode;
                if (episode != null
                    && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh
                    && !refreshOptions.ReplaceAllMetadata
                    && episode.IsMissingEpisode
                    && episode.LocationType == LocationType.Virtual
                    && episode.PremiereDate.HasValue
                    && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30)
                {
                    skipItem = true;
                }
                if (!skipItem)
                {
                    if (refreshOptions.RefreshItem(item))
                    {
                        await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
                    }
                }
                numComplete++;
                double percent = numComplete;
                percent /= totalItems;
                progress.Report(percent * 100);
            }
            refreshOptions = new MetadataRefreshOptions(refreshOptions);
            await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
        }
        public List GetSeasonEpisodes(Season parentSeason, User user, DtoOptions options)
        {
            var queryFromSeries = ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons;
            // add optimization when this setting is not enabled
            var seriesKey = queryFromSeries ?
                GetUniqueSeriesKey(this) :
                GetUniqueSeriesKey(parentSeason);
            var query = new InternalItemsQuery(user)
            {
                AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey,
                SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null,
                IncludeItemTypes = new[] { nameof(Episode) },
                OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
                DtoOptions = options
            };
            if (user != null)
            {
                if (!user.DisplayMissingEpisodes)
                {
                    query.IsMissing = false;
                }
            }
            var allItems = LibraryManager.GetItemList(query);
            return GetSeasonEpisodes(parentSeason, user, allItems, options);
        }
        public List GetSeasonEpisodes(Season parentSeason, User user, IEnumerable allSeriesEpisodes, DtoOptions options)
        {
            if (allSeriesEpisodes == null)
            {
                return GetSeasonEpisodes(parentSeason, user, options);
            }
            var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
            var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
            return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending).ToList();
        }
        /// 
        /// Filters the episodes by season.
        /// 
        public static IEnumerable FilterEpisodesBySeason(IEnumerable episodes, Season parentSeason, bool includeSpecials)
        {
            var seasonNumber = parentSeason.IndexNumber;
            var seasonPresentationKey = GetUniqueSeriesKey(parentSeason);
            var supportSpecialsInSeason = includeSpecials && seasonNumber.HasValue && seasonNumber.Value != 0;
            return episodes.Where(episode =>
            {
                var episodeItem = (Episode)episode;
                var currentSeasonNumber = supportSpecialsInSeason ? episodeItem.AiredSeasonNumber : episode.ParentIndexNumber;
                if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.Value)
                {
                    return true;
                }
                if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType.Virtual)
                {
                    return true;
                }
                var season = episodeItem.Season;
                return season != null && string.Equals(GetUniqueSeriesKey(season), seasonPresentationKey, StringComparison.OrdinalIgnoreCase);
            });
        }
        /// 
        /// Filters the episodes by season.
        /// 
        public static IEnumerable FilterEpisodesBySeason(IEnumerable episodes, int seasonNumber, bool includeSpecials)
        {
            if (!includeSpecials || seasonNumber < 1)
            {
                return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber);
            }
            return episodes.Where(i =>
            {
                var episode = i;
                if (episode != null)
                {
                    var currentSeasonNumber = episode.AiredSeasonNumber;
                    return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber;
                }
                return false;
            });
        }
        protected override bool GetBlockUnratedValue(User user)
        {
            return user.GetPreferenceValues(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series);
        }
        public override UnratedItem GetBlockUnratedType()
        {
            return UnratedItem.Series;
        }
        public SeriesInfo GetLookupInfo()
        {
            var info = GetItemLookupInfo();
            return info;
        }
        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
        {
            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
            if (!ProductionYear.HasValue)
            {
                var info = LibraryManager.ParseName(Name);
                var yearInName = info.Year;
                if (yearInName.HasValue)
                {
                    ProductionYear = yearInName;
                    hasChanges = true;
                }
            }
            return hasChanges;
        }
        public override List GetRelatedUrls()
        {
            var list = base.GetRelatedUrls();
            var imdbId = this.GetProviderId(MetadataProvider.Imdb);
            if (!string.IsNullOrEmpty(imdbId))
            {
                list.Add(new ExternalUrl
                {
                    Name = "Trakt",
                    Url = string.Format(CultureInfo.InvariantCulture, "https://trakt.tv/shows/{0}", imdbId)
                });
            }
            return list;
        }
        [JsonIgnore]
        public override bool StopRefreshIfLocalMetadataFound => false;
    }
}