using System;
using AutoMapper;
using Ombi.Api.TvMaze;
using Ombi.Api.TheMovieDb;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Helpers;
using Ombi.Store.Entities;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Helpers;
using Ombi.Core.Models.UI;
using Ombi.Core.Rule;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Senders;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Core.Models;
using System.Threading;
using Microsoft.Extensions.Logging;

namespace Ombi.Core.Engine
{
    public class TvRequestEngine : BaseMediaEngine, ITvRequestEngine
    {
        public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, ICurrentUser user,
            INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, ILogger<TvRequestEngine> logger,
            ITvSender sender, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
            IRepository<RequestSubscription> sub, IMediaCacheService mediaCacheService,
            IUserPlayedEpisodeRepository userPlayedEpisodeRepository) : base(user, requestService, rule, manager, cache, settings, sub)
        {
            TvApi = tvApi;
            MovieDbApi = movApi;
            NotificationHelper = helper;
            _logger = logger;
            TvSender = sender;
            _requestLog = rl;
            _mediaCacheService = mediaCacheService;
            _userPlayedEpisodeRepository = userPlayedEpisodeRepository;
        }

        private INotificationHelper NotificationHelper { get; }
        private ITvMazeApi TvApi { get; }
        private IMovieDbApi MovieDbApi { get; }
        private ITvSender TvSender { get; }

        private readonly ILogger<TvRequestEngine> _logger;
        private readonly IRepository<RequestLog> _requestLog;
        private readonly IMediaCacheService _mediaCacheService;
        private readonly IUserPlayedEpisodeRepository _userPlayedEpisodeRepository;

        public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
        {
            var user = await GetUser();
            var canRequestOnBehalf = false;

            if (tv.RequestOnBehalf.HasValue())
            {
                canRequestOnBehalf = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin);

                if (!canRequestOnBehalf)
                {
                    return new RequestEngineResult
                    {
                        Result = false,
                        Message = "You do not have the correct permissions to request on behalf of users!",
                        ErrorMessage = $"You do not have the correct permissions to request on behalf of users!"
                    };
                }
            }

            var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi, _logger);
            (await tvBuilder
                .GetShowInfo(tv.TvDbId))
                .CreateTvList(tv)
                .CreateChild(tv, canRequestOnBehalf ? tv.RequestOnBehalf : user.Id);

            await tvBuilder.BuildEpisodes(tv);

            var ruleResults = await RunRequestRules(tvBuilder.ChildRequest);
            var results = ruleResults as RuleResult[] ?? ruleResults.ToArray();
            if (results.Any(x => !x.Success))
            {
                return new RequestEngineResult
                {
                    ErrorMessage = results.FirstOrDefault(x => !string.IsNullOrEmpty(x.Message)).Message
                };
            }

            // Check if we have auto approved the request, if we have then mark the episodes as approved
            if (tvBuilder.ChildRequest.Approved)
            {
                foreach (var seasons in tvBuilder.ChildRequest.SeasonRequests)
                {
                    foreach (var ep in seasons.Episodes)
                    {
                        ep.Approved = true;
                        ep.Requested = true;
                    }
                }
            }

            var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId);
            if (existingRequest != null)
            {
                // Remove requests we already have, we just want new ones
                foreach (var existingSeason in existingRequest.ChildRequests)
                    foreach (var existing in existingSeason.SeasonRequests)
                    {
                        var newChild = tvBuilder.ChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == existing.SeasonNumber);
                        if (newChild != null)
                        {
                            // We have some requests in this season...
                            // Let's find the episodes.
                            foreach (var existingEp in existing.Episodes)
                            {
                                var duplicateEpisode = newChild.Episodes.FirstOrDefault(x => x.EpisodeNumber == existingEp.EpisodeNumber);
                                if (duplicateEpisode != null)
                                {
                                    // Remove it.
                                    newChild.Episodes.Remove(duplicateEpisode);
                                }
                            }
                            if (!newChild.Episodes.Any())
                            {
                                // We may have removed all episodes
                                tvBuilder.ChildRequest.SeasonRequests.Remove(newChild);
                            }
                        }
                    }

                // Remove the ID since this is a new child
                // This was a TVDBID for the request rules to run
                tvBuilder.ChildRequest.Id = 0;
                if (!tvBuilder.ChildRequest.SeasonRequests.Any())
                {
                    // Looks like we have removed them all! They were all duplicates...
                    return new RequestEngineResult
                    {
                        Result = false,
                        ErrorCode = ErrorCode.AlreadyRequested,
                        ErrorMessage = "This has already been requested"
                    };
                }
                return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf, tv.RootFolderOverride.GetValueOrDefault(), tv.QualityPathOverride.GetValueOrDefault());
            }

            // This is a new request
            var newRequest = tvBuilder.CreateNewRequest(tv);
            return await AddRequest(newRequest.NewRequest, tv.RequestOnBehalf);
        }

        public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModelV2 tv)
        {
            var user = await GetUser();
            var canRequestOnBehalf = tv.RequestOnBehalf.HasValue();

            var isAdmin = Username.Equals("API", StringComparison.CurrentCultureIgnoreCase) || await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin);
            if (tv.RequestOnBehalf.HasValue() && !isAdmin)
            {
                return new RequestEngineResult
                {
                    Result = false,
                    ErrorCode = ErrorCode.NoPermissionsOnBehalf,
                    Message = "You do not have the correct permissions to request on behalf of users!",
                    ErrorMessage = $"You do not have the correct permissions to request on behalf of users!"
                };
            }

            if ((tv.RootFolderOverride.HasValue || tv.QualityPathOverride.HasValue || tv.LanguageProfile.HasValue) && !isAdmin)
            {
                return new RequestEngineResult
                {
                    Result = false,
                    ErrorCode = ErrorCode.NoPermissions,
                    Message = "You do not have the correct permissions!",
                    ErrorMessage = $"You do not have the correct permissions!"
                };
            }

            var tvBuilder = new TvShowRequestBuilderV2(MovieDbApi);
            (await tvBuilder
                .GetShowInfo(tv.TheMovieDbId, tv.languageCode))
                .CreateTvList(tv)
                .CreateChild(tv, canRequestOnBehalf ? tv.RequestOnBehalf : user.Id, tv.Source);

            await tvBuilder.BuildEpisodes(tv);

            var ruleResults = await RunRequestRules(tvBuilder.ChildRequest);
            var results = ruleResults as RuleResult[] ?? ruleResults.ToArray();
            if (results.Any(x => !x.Success))
            {
                return new RequestEngineResult
                {
                    ErrorMessage = results.FirstOrDefault(x => !string.IsNullOrEmpty(x.Message)).Message
                };
            }

            // Check if we have auto approved the request, if we have then mark the episodes as approved
            if (tvBuilder.ChildRequest.Approved)
            {
                foreach (var seasons in tvBuilder.ChildRequest.SeasonRequests)
                {
                    foreach (var ep in seasons.Episodes)
                    {
                        ep.Approved = true;
                        ep.Requested = true;
                    }
                }
            }

            var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.ExternalProviderId == tv.TheMovieDbId);
            if (existingRequest != null)
            {
                // Remove requests we already have, we just want new ones
                foreach (var existingSeason in existingRequest.ChildRequests)
                    foreach (var existing in existingSeason.SeasonRequests)
                    {
                        var newChild = tvBuilder.ChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == existing.SeasonNumber);
                        if (newChild != null)
                        {
                            // We have some requests in this season...
                            // Let's find the episodes.
                            foreach (var existingEp in existing.Episodes)
                            {
                                var duplicateEpisode = newChild.Episodes.FirstOrDefault(x => x.EpisodeNumber == existingEp.EpisodeNumber);
                                if (duplicateEpisode != null)
                                {
                                    // Remove it.
                                    newChild.Episodes.Remove(duplicateEpisode);
                                }
                            }
                            if (!newChild.Episodes.Any())
                            {
                                // We may have removed all episodes
                                tvBuilder.ChildRequest.SeasonRequests.Remove(newChild);
                            }
                        }
                    }

                // Remove the ID since this is a new child
                // This was a TVDBID for the request rules to run
                tvBuilder.ChildRequest.Id = 0;
                if (!tvBuilder.ChildRequest.SeasonRequests.Any())
                {
                    // Looks like we have removed them all! They were all duplicates...
                    return new RequestEngineResult
                    {
                        Result = false,
                        ErrorCode = ErrorCode.AlreadyRequested,
                        ErrorMessage = "This has already been requested"
                    };
                }
                return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf, tv.RootFolderOverride.GetValueOrDefault(), tv.QualityPathOverride.GetValueOrDefault());
            }

            // This is a new request
            var newRequest = tvBuilder.CreateNewRequest(tv, tv.RootFolderOverride.GetValueOrDefault(), tv.QualityPathOverride.GetValueOrDefault(), tv.LanguageProfile.GetValueOrDefault());
            return await AddRequest(newRequest.NewRequest, tv.RequestOnBehalf);
        }

        public async Task<RequestsViewModel<TvRequests>> GetRequests(int count, int position, OrderFilterModel type)
        {
            var shouldHide = await HideFromOtherUsers();
            List<TvRequests> allRequests;
            if (shouldHide.Hide)
            {
                allRequests = await TvRepository.Get(shouldHide.UserId)
                    .Include(x => x.ChildRequests)
                    .ThenInclude(x => x.SeasonRequests)
                    .ThenInclude(x => x.Episodes)
                    .OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
                    .Skip(position).Take(count).ToListAsync();

                // Filter out children

                FilterChildren(allRequests, shouldHide);
            }
            else
            {
                allRequests = await TvRepository.Get()
                    .Include(x => x.ChildRequests)
                    .ThenInclude(x => x.SeasonRequests)
                    .ThenInclude(x => x.Episodes)
                    .OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
                    .Skip(position).Take(count).ToListAsync();

            }
            await FillAdditionalFields(shouldHide, allRequests);

            return new RequestsViewModel<TvRequests>
            {
                Collection = allRequests
            };
        }

        public async Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type)
        {
            var shouldHide = await HideFromOtherUsers();
            List<TvRequests> allRequests = null;
            if (shouldHide.Hide)
            {
                var tv = TvRepository.GetLite(shouldHide.UserId);
                if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
                {
                    allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
                }

                // Filter out children
                FilterChildren(allRequests, shouldHide);
            }
            else
            {
                var tv = TvRepository.GetLite();
                if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
                {
                    allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
                }
            }
            if (allRequests == null)
            {
                return new RequestsViewModel<TvRequests>();
            }

            await FillAdditionalFields(shouldHide, allRequests);

            return new RequestsViewModel<TvRequests>
            {
                Collection = allRequests
            };
        }

        public async Task<IEnumerable<TvRequests>> GetRequests()
        {
            var shouldHide = await HideFromOtherUsers();
            List<TvRequests> allRequests;
            if (shouldHide.Hide)
            {
                allRequests = await TvRepository.Get(shouldHide.UserId).ToListAsync();

                FilterChildren(allRequests, shouldHide);
            }
            else
            {
                allRequests = await TvRepository.Get().ToListAsync();
            }

            await FillAdditionalFields(shouldHide, allRequests);
            return allRequests;
        }

        public async Task<RequestsViewModel<ChildRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder)
        {
            var shouldHide = await HideFromOtherUsers();
            List<ChildRequests> allRequests;
            if (shouldHide.Hide)
            {
                allRequests = await TvRepository.GetChild(shouldHide.UserId).ToListAsync();

                // Filter out children

                FilterChildren(allRequests, shouldHide);
            }
            else
            {
                allRequests = await TvRepository.GetChild().ToListAsync();

            }

            if (allRequests == null)
            {
                return new RequestsViewModel<ChildRequests>();
            }

            var total = allRequests.Count;


            var prop = TypeDescriptor.GetProperties(typeof(ChildRequests)).Find(sortProperty, true);

            if (sortProperty.Contains('.'))
            {
                // This is a navigation property currently not supported
                prop = TypeDescriptor.GetProperties(typeof(ChildRequests)).Find("Title", true);
                //var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
                //var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
                //var propType = firstProp.PropertyType;
                //var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
            }
            allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
                ? allRequests.OrderBy(x => prop.GetValue(x)).ToList()
                : allRequests.OrderByDescending(x => prop.GetValue(x)).ToList();
            
            await FillAdditionalFields(shouldHide, allRequests);

            // Make sure we do not show duplicate child requests
            allRequests = allRequests.DistinctBy(x => x.ParentRequest.Title).ToList();

            allRequests = allRequests.Skip(position).Take(count).ToList();

            return new RequestsViewModel<ChildRequests>
            {
                Collection = allRequests,
                Total = total,
            };
        }

        public async Task<RequestsViewModel<ChildRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder, RequestStatus status)
        {
            var shouldHide = await HideFromOtherUsers();
            List<ChildRequests> allRequests;
            if (shouldHide.Hide)
            {
                allRequests = await TvRepository.GetChild(shouldHide.UserId).ToListAsync();

                // Filter out children

                FilterChildren(allRequests, shouldHide);
            }
            else
            {
                allRequests = await TvRepository.GetChild().ToListAsync();

            }

            switch (status)
            {
                case RequestStatus.PendingApproval:
                    allRequests = allRequests.Where(x => !x.Approved && !x.Available && (!x.Denied.HasValue || !x.Denied.Value)).ToList();
                    break;
                case RequestStatus.ProcessingRequest:
                    allRequests = allRequests.Where(x => x.Approved && !x.Available && (!x.Denied.HasValue || !x.Denied.Value)).ToList();
                    break;
                case RequestStatus.Available:
                    allRequests = allRequests.Where(x => x.Available && (!x.Denied.HasValue || !x.Denied.Value)).ToList();
                    break;
                case RequestStatus.Denied:
                    allRequests = allRequests.Where(x => x.Denied.HasValue  && x.Denied.Value).ToList();
                    break;
                default:
                    break;
            }

            if (allRequests == null)
            {
                return new RequestsViewModel<ChildRequests>();
            }

            var total = allRequests.Count;


            var prop = TypeDescriptor.GetProperties(typeof(ChildRequests)).Find(sortProperty, true);

            if (sortProperty.Contains('.'))
            {
                // This is a navigation property currently not supported
                prop = TypeDescriptor.GetProperties(typeof(ChildRequests)).Find("Title", true);
                //var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
                //var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
                //var propType = firstProp.PropertyType;
                //var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
            }
            allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
                ? allRequests.OrderBy(x => prop.GetValue(x)).ToList()
                : allRequests.OrderByDescending(x => prop.GetValue(x)).ToList();
            
            await FillAdditionalFields(shouldHide, allRequests);

            // Make sure we do not show duplicate child requests
            allRequests = allRequests.DistinctBy(x => x.ParentRequest.Title).ToList();

            allRequests = allRequests.Skip(position).Take(count).ToList();

            return new RequestsViewModel<ChildRequests>
            {
                Collection = allRequests,
                Total = total,
            };
        }

        public async Task<RequestsViewModel<ChildRequests>> GetUnavailableRequests(int count, int position, string sortProperty, string sortOrder)
        {
            var shouldHide = await HideFromOtherUsers();
            List<ChildRequests> allRequests;
            if (shouldHide.Hide)
            {
                allRequests = await TvRepository.GetChild(shouldHide.UserId).Where(x => !x.Available && x.Approved).ToListAsync();

                // Filter out children

                FilterChildren(allRequests, shouldHide);
            }
            else
            {
                allRequests = await TvRepository.GetChild().Where(x => !x.Available && x.Approved).ToListAsync();

            }

            if (allRequests == null)
            {
                return new RequestsViewModel<ChildRequests>();
            }

            var total = allRequests.Count;


            var prop = TypeDescriptor.GetProperties(typeof(ChildRequests)).Find(sortProperty, true);

            if (sortProperty.Contains('.'))
            {
                // This is a navigation property currently not supported
                prop = TypeDescriptor.GetProperties(typeof(ChildRequests)).Find("Title", true);
                //var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
                //var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
                //var propType = firstProp.PropertyType;
                //var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
            }
            allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
                ? allRequests.OrderBy(x => prop.GetValue(x)).ToList()
                : allRequests.OrderByDescending(x => prop.GetValue(x)).ToList();
            await FillAdditionalFields(shouldHide, allRequests);

            // Make sure we do not show duplicate child requests
            allRequests = allRequests.DistinctBy(x => x.ParentRequest.Title).ToList();
            allRequests = allRequests.Skip(position).Take(count).ToList();

            return new RequestsViewModel<ChildRequests>
            {
                Collection = allRequests,
                Total = total,
            };
        }

        public async Task<IEnumerable<TvRequests>> GetRequestsLite()
        {
            var shouldHide = await HideFromOtherUsers();
            List<TvRequests> allRequests;
            if (shouldHide.Hide)
            {
                allRequests = await TvRepository.GetLite(shouldHide.UserId).ToListAsync();

                FilterChildren(allRequests, shouldHide);
            }
            else
            {
                allRequests = await TvRepository.GetLite().ToListAsync();
            }

            await FillAdditionalFields(shouldHide, allRequests);
            return allRequests;
        }

        public async Task<TvRequests> GetTvRequest(int requestId)
        {
            var shouldHide = await HideFromOtherUsers();
            TvRequests request;
            if (shouldHide.Hide)
            {
                request = await TvRepository.Get(shouldHide.UserId).Where(x => x.Id == requestId).FirstOrDefaultAsync();

                FilterChildren(request, shouldHide);
            }
            else
            {
                request = await TvRepository.Get().Where(x => x.Id == requestId).FirstOrDefaultAsync();
            }

            await FillAdditionalFields(shouldHide, new List<TvRequests>{request});
            return request;
        }

        private static void FilterChildren(IEnumerable<TvRequests> allRequests, HideResult shouldHide)
        {
            if (allRequests == null)
            {
                return;
            }
            // Filter out children
            foreach (var t in allRequests)
            {
                for (var j = 0; j < t.ChildRequests.Count; j++)
                {
                    FilterChildren(t, shouldHide);
                }
            }
        }

        private static void FilterChildren(TvRequests t, HideResult shouldHide)
        {
            // Filter out children
            FilterChildren(t.ChildRequests, shouldHide);
        }

        private static void FilterChildren(List<ChildRequests> t, HideResult shouldHide)
        {
            // Filter out children

            for (var j = 0; j < t.Count; j++)
            {
                var child = t[j];
                if (child.RequestedUserId != shouldHide.UserId)
                {
                    t.RemoveAt(j);
                    j--;
                }
            }
        }

        public async Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId)
        {
            var shouldHide = await HideFromOtherUsers();
            List<ChildRequests> allRequests;
            if (shouldHide.Hide)
            {
                allRequests = await TvRepository.GetChild(shouldHide.UserId).Include(x => x.SeasonRequests).Where(x => x.ParentRequestId == tvId).ToListAsync();
            }
            else
            {
                allRequests = await TvRepository.GetChild().Include(x => x.SeasonRequests).Where(x => x.ParentRequestId == tvId).ToListAsync();
            }

            await FillAdditionalFields(shouldHide, allRequests);

            return allRequests;
        }

        public async Task<IEnumerable<TvRequests>> SearchTvRequest(string search)
        {
            var shouldHide = await HideFromOtherUsers();
            IQueryable<TvRequests> allRequests;
            if (shouldHide.Hide)
            {
                allRequests = TvRepository.Get(shouldHide.UserId);
            }
            else
            {
                allRequests = TvRepository.Get();
            }
            var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync();

            await FillAdditionalFields(shouldHide, results);
            return results;
        }

        public async Task UpdateRootPath(int requestId, int rootPath)
        {
            var allRequests = TvRepository.Get();
            var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId);
            results.RootFolder = rootPath;

            await TvRepository.Update(results);
        }

        public async Task UpdateQualityProfile(int requestId, int profileId)
        {
            var allRequests = TvRepository.Get();
            var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId);
            results.QualityOverride = profileId;

            await TvRepository.Update(results);
        }

        public async Task<TvRequests> UpdateTvRequest(TvRequests request)
        {
            var allRequests = TvRepository.Get();
            var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id);

            results.TvDbId = request.TvDbId;
            results.ImdbId = request.ImdbId;
            results.Overview = request.Overview;
            results.PosterPath = PosterPathHelper.FixPosterPath(request.PosterPath);
            results.Background = PosterPathHelper.FixBackgroundPath(request.Background);
            results.QualityOverride = request.QualityOverride;
            results.RootFolder = request.RootFolder;

            await TvRepository.Update(results);
            return results;
        }

        public async Task<RequestEngineResult> ApproveChildRequest(int id)
        {
            var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == id);
            if (request == null)
            {
                return new RequestEngineResult
                {
                    ErrorCode = ErrorCode.ChildRequestDoesNotExist,
                    ErrorMessage = "Child Request does not exist"
                };
            }
            request.Approved = true;
            request.Denied = false;

            foreach (var s in request.SeasonRequests)
            {
                foreach (var ep in s.Episodes)
                {
                    ep.Approved = true;
                    ep.Requested = true;
                }
            }

            await TvRepository.UpdateChild(request);
            await _mediaCacheService.Purge();

            if (request.Approved)
            {
                var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification, string.Empty);
                if (canNotify.Success)
                {
                    await NotificationHelper.Notify(request, NotificationType.RequestApproved);
                }
                // Autosend
                await TvSender.Send(request);
            }
            return new RequestEngineResult
            {
                Result = true
            };
        }

        public async Task<RequestEngineResult> DenyChildRequest(int requestId, string reason)
        {
            var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId);
            if (request == null)
            {
                return new RequestEngineResult
                {
                    ErrorCode = ErrorCode.ChildRequestDoesNotExist,
                    ErrorMessage = "Child Request does not exist"
                };
            }
            request.Denied = true;
            request.DeniedReason = reason;
            await TvRepository.UpdateChild(request);
            await _mediaCacheService.Purge();
            await NotificationHelper.Notify(request, NotificationType.RequestDeclined);
            return new RequestEngineResult
            {
                Result = true
            };
        }

        public async Task<ChildRequests> UpdateChildRequest(ChildRequests request)
        {
            await TvRepository.UpdateChild(request);
            await _mediaCacheService.Purge();
            return request;
        }

        public async Task<RequestEngineResult> RemoveTvChild(int requestId)
        {
            var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId);

            var result = await CheckCanManageRequest(request);
            if (result.IsError)
                return result;

            TvRepository.Db.ChildRequests.Remove(request);
            var all = TvRepository.Db.TvRequests.Include(x => x.ChildRequests);
            var parent = all.FirstOrDefault(x => x.Id == request.ParentRequestId);

            // Is this the only child? If so delete the parent
            if (parent.ChildRequests.Count <= 1)
            {
                // Delete the parent
                TvRepository.Db.TvRequests.Remove(parent);
            }

            await TvRepository.Db.SaveChangesAsync();
            await _mediaCacheService.Purge();

            return new RequestEngineResult
            {
                Result = true,
            };
        }

        public async Task RemoveTvRequest(int requestId)
        {
            var request = await TvRepository.Get().FirstOrDefaultAsync(x => x.Id == requestId);
            await TvRepository.Delete(request);
            await _mediaCacheService.Purge();
        }

        public async Task<bool> UserHasRequest(string userId)
        {
            return await TvRepository.GetChild().AnyAsync(x => x.RequestedUserId == userId);
        }

        public async Task<RequestEngineResult> MarkUnavailable(int modelId, bool is4K)
        {
            var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == modelId);
            if (request == null)
            {
                return new RequestEngineResult
                {
                    ErrorCode = ErrorCode.ChildRequestDoesNotExist,
                    ErrorMessage = "Child Request does not exist"
                };
            }
            request.Available = false;
            foreach (var season in request.SeasonRequests)
            {
                foreach (var e in season.Episodes)
                {
                    e.Available = false;
                }
            }
            await TvRepository.UpdateChild(request);
            await _mediaCacheService.Purge();
            return new RequestEngineResult
            {
                Result = true,
                Message = "Request is now unavailable",
            };
        }

        public async Task<RequestEngineResult> MarkAvailable(int modelId, bool is4K)
        {
            ChildRequests request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == modelId);
            if (request == null)
            {
                return new RequestEngineResult
                {
                    ErrorCode = ErrorCode.ChildRequestDoesNotExist,
                    ErrorMessage = "Child Request does not exist"
                };
            }
            request.Available = true;
            request.MarkedAsAvailable = DateTime.Now;
            foreach (var season in request.SeasonRequests)
            {
                foreach (var e in season.Episodes)
                {
                    e.Available = true;
                }
            }
            await TvRepository.UpdateChild(request);
            await NotificationHelper.Notify(request, NotificationType.RequestAvailable);
            await _mediaCacheService.Purge();
            return new RequestEngineResult
            {
                Result = true,
                Message = "Request is now available",
            };
        }

        public async Task<int> GetTotal()
        {
            var shouldHide = await HideFromOtherUsers();
            if (shouldHide.Hide)
            {
                return await TvRepository.Get(shouldHide.UserId).CountAsync();
            }
            else
            {
                return await TvRepository.Get().CountAsync();
            }
        }

        private async Task FillAdditionalFields(HideResult shouldHide, List<TvRequests> x)
        {
            foreach (var tvRequest in x)
            {
                await FillAdditionalFields(shouldHide, tvRequest.ChildRequests);
            }
        }

        private async Task FillAdditionalFields(HideResult shouldHide, List<ChildRequests> childRequests)
        {
            await CheckForSubscription(shouldHide, childRequests);
            CheckForPlayed(shouldHide, childRequests);
        }

        private async Task CheckForSubscription(HideResult shouldHide, List<ChildRequests> childRequests)
        {
            var sub = _subscriptionRepository.GetAll();
            var childIds = childRequests.Select(x => x.Id);
            var relevantSubs = await sub.Where(s =>
                s.UserId == shouldHide.UserId && childIds.Contains(s.Id) && s.RequestType == RequestType.TvShow).ToListAsync();
            foreach (var x in childRequests)
            {
                if (shouldHide.UserId == x.RequestedUserId)
                {
                    x.ShowSubscribe = false;
                }
                else
                {
                    if (!x.Available && (!x.Denied ?? true))
                    {
                        x.ShowSubscribe = true;
                    }
                    var result = relevantSubs.FirstOrDefault(s => s.RequestId == x.Id);
                    x.Subscribed = result != null;
                }
            }
        }

        private class EpisodeKey
        {
            public int SeasonNumber;
            public int EpisodeNumber;
        }

        private void CheckForPlayed(HideResult shouldHide, List<ChildRequests> childRequests)
        {
            var theMovieDbIds = childRequests.Select(x => x.Id);
            foreach (var request in childRequests)
            {
                var requestedEpisodes = GetEpisodesKeys(request);

                var playedEpisodes = _userPlayedEpisodeRepository
                    .GetAll()
                    .Where(x => x.TheMovieDbId == request.Id && x.UserId == request.RequestedUserId)
                    .AsEnumerable()
                    .Join(requestedEpisodes,
                        played => new { played.SeasonNumber, played.EpisodeNumber },
                        requested => new { requested.SeasonNumber, requested.EpisodeNumber },
                        (played, requested) => new { played });

                var playedCount = playedEpisodes.Count();
                var toWatchCount = requestedEpisodes.Count();
                if (playedCount == 0 || toWatchCount == 0)
                {
                    request.RequestedUserPlayedProgress = 0;
                }
                else
                {
                    request.RequestedUserPlayedProgress = 100 * playedCount / toWatchCount;
                }
                
            }
        }

        private List<EpisodeKey> GetEpisodesKeys(ChildRequests request)
        {
            List<EpisodeKey> result = new List<EpisodeKey>();
            foreach(var season in request.SeasonRequests) 
            {
                foreach(var episode in season.Episodes) 
                {
                    result.Add(new EpisodeKey
                    {
                        SeasonNumber = season.SeasonNumber,
                        EpisodeNumber = episode.EpisodeNumber
                    });
                }
            }
            return result;
        }

        private async Task<RequestEngineResult> AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest, string requestOnBehalf, int rootFolder, int qualityProfile)
        {
            // Add the child
            existingRequest.ChildRequests.Add(newRequest);
            if (qualityProfile > 0)
            {
                existingRequest.QualityOverride = qualityProfile;
            }
            if (rootFolder > 0)
            {
                existingRequest.RootFolder = rootFolder;
            }

            await TvRepository.Update(existingRequest);

            return await AfterRequest(newRequest, requestOnBehalf);
        }

        private async Task<RequestEngineResult> AddRequest(TvRequests model, string requestOnBehalf)
        {
            await TvRepository.Add(model);
            // This is a new request so we should only have 1 child
            return await AfterRequest(model.ChildRequests.FirstOrDefault(), requestOnBehalf);
        }

        public async Task<RequestEngineResult> ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken)
        {
            var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId, cancellationToken);
            if (request == null)
            {
                return new RequestEngineResult
                {
                    Result = false,
                    ErrorCode = ErrorCode.RequestDoesNotExist,
                    ErrorMessage = "Request does not exist"
                };
            }

            return await ProcessSendingShow(request);
        }


        private async Task<RequestEngineResult> AfterRequest(ChildRequests model, string requestOnBehalf)
        {
            var sendRuleResult = await RunSpecificRule(model, SpecificRules.CanSendNotification, requestOnBehalf);
            if (sendRuleResult.Success)
            {
                await NotificationHelper.NewRequest(model);
            }

            await _requestLog.Add(new RequestLog
            {
                UserId = requestOnBehalf.HasValue() ? requestOnBehalf : (await GetUser()).Id,
                RequestDate = DateTime.UtcNow,
                RequestId = model.Id,
                RequestType = RequestType.TvShow,
                EpisodeCount = model.SeasonRequests.Select(m => m.Episodes.Count).Sum(),
            });
            await _mediaCacheService.Purge();

            return await ProcessSendingShow(model);
        }

        private async Task<RequestEngineResult> ProcessSendingShow(ChildRequests model)
        {
            if (model.Approved)
            {
                // Autosend
                var canNotify = await RunSpecificRule(model, SpecificRules.CanSendNotification, string.Empty);
                if (canNotify.Success)
                {
                    await NotificationHelper.Notify(model, NotificationType.RequestApproved);
                }
                var result = await TvSender.Send(model);
                if (result.Success)
                {
                    return new RequestEngineResult { Result = true, RequestId = model.Id };
                }
                return new RequestEngineResult
                {
                    ErrorMessage = result.Message,
                    RequestId = model.Id
                };
            }

            return new RequestEngineResult { Result = true, RequestId = model.Id };
        }

       

        public async Task<RequestEngineResult> UpdateAdvancedOptions(MediaAdvancedOptions options)
        {
            var request = await TvRepository.Find(options.RequestId);
            if (request == null)
            {
                return new RequestEngineResult
                {
                    Result = false,
                    ErrorCode = ErrorCode.RequestDoesNotExist,
                    ErrorMessage = "Request does not exist"
                };
            }

            request.QualityOverride = options.QualityOverride;
            request.RootFolder = options.RootPathOverride;
            if (options.LanguageProfile > 0)
            {
                request.LanguageProfile = options.LanguageProfile;
            }

            await TvRepository.Update(request);

            return new RequestEngineResult
            {
                Result = true
            };
        }
    }
}