using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers
{
    /// 
    /// The tv shows controller.
    /// 
    [Route("Shows")]
    [Authorize(Policy = Policies.DefaultAuthorization)]
    public class TvShowsController : BaseJellyfinApiController
    {
        private readonly IUserManager _userManager;
        private readonly ILibraryManager _libraryManager;
        private readonly IDtoService _dtoService;
        private readonly ITVSeriesManager _tvSeriesManager;
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// Instance of the  interface.
        /// Instance of the  interface.
        /// Instance of the  interface.
        /// Instance of the  interface.
        public TvShowsController(
            IUserManager userManager,
            ILibraryManager libraryManager,
            IDtoService dtoService,
            ITVSeriesManager tvSeriesManager)
        {
            _userManager = userManager;
            _libraryManager = libraryManager;
            _dtoService = dtoService;
            _tvSeriesManager = tvSeriesManager;
        }
        /// 
        /// Gets a list of next up episodes.
        /// 
        /// The user id of the user to get the next up episodes for.
        /// Optional. The record index to start at. All items with a lower index will be dropped from the results.
        /// Optional. The maximum number of records to return.
        /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.
        /// Optional. Filter by series id.
        /// Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.
        /// Optional. Include image information in output.
        /// Optional. The max number of images to return, per image type.
        /// Optional. The image types to include in the output.
        /// Optional. Include user data.
        /// Whether to enable the total records count. Defaults to true.
        /// A  with the next up episodes.
        [HttpGet("NextUp")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        public ActionResult> GetNextUp(
            [FromQuery, Required] Guid? userId,
            [FromQuery] int? startIndex,
            [FromQuery] int? limit,
            [FromQuery] string? fields,
            [FromQuery] string? seriesId,
            [FromQuery] string? parentId,
            [FromQuery] bool? enableImges,
            [FromQuery] int? imageTypeLimit,
            [FromQuery] string? enableImageTypes,
            [FromQuery] bool? enableUserData,
            [FromQuery] bool enableTotalRecordCount = true)
        {
            var options = new DtoOptions()
                .AddItemFields(fields!)
                .AddClientFields(Request)
                .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!);
            var result = _tvSeriesManager.GetNextUp(
                new NextUpQuery
                {
                    Limit = limit,
                    ParentId = parentId,
                    SeriesId = seriesId,
                    StartIndex = startIndex,
                    UserId = userId ?? Guid.Empty,
                    EnableTotalRecordCount = enableTotalRecordCount
                },
                options);
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
                ? _userManager.GetUserById(userId.Value)
                : null;
            var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user);
            return new QueryResult
            {
                TotalRecordCount = result.TotalRecordCount,
                Items = returnItems
            };
        }
        /// 
        /// Gets a list of upcoming episodes.
        /// 
        /// The user id of the user to get the upcoming episodes for.
        /// Optional. The record index to start at. All items with a lower index will be dropped from the results.
        /// Optional. The maximum number of records to return.
        /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.
        /// Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.
        /// Optional. Include image information in output.
        /// Optional. The max number of images to return, per image type.
        /// Optional. The image types to include in the output.
        /// Optional. Include user data.
        /// A  with the next up episodes.
        [HttpGet("Upcoming")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        public ActionResult> GetUpcomingEpisodes(
            [FromQuery, Required] Guid? userId,
            [FromQuery] int? startIndex,
            [FromQuery] int? limit,
            [FromQuery] string? fields,
            [FromQuery] string? parentId,
            [FromQuery] bool? enableImges,
            [FromQuery] int? imageTypeLimit,
            [FromQuery] string? enableImageTypes,
            [FromQuery] bool? enableUserData)
        {
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
                ? _userManager.GetUserById(userId.Value)
                : null;
            var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1);
            var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
            var options = new DtoOptions()
                .AddItemFields(fields!)
                .AddClientFields(Request)
                .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!);
            var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
            {
                IncludeItemTypes = new[] { nameof(Episode) },
                OrderBy = new[] { ItemSortBy.PremiereDate, ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Ascending)).ToArray(),
                MinPremiereDate = minPremiereDate,
                StartIndex = startIndex,
                Limit = limit,
                ParentId = parentIdGuid,
                Recursive = true,
                DtoOptions = options
            });
            var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user);
            return new QueryResult
            {
                TotalRecordCount = itemsResult.Count,
                Items = returnItems
            };
        }
        /// 
        /// Gets episodes for a tv season.
        /// 
        /// The series id.
        /// The user id.
        /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.
        /// Optional filter by season number.
        /// Optional. Filter by season id.
        /// Optional. Filter by items that are missing episodes or not.
        /// Optional. Return items that are siblings of a supplied item.
        /// Optional. Skip through the list until a given item is found.
        /// Optional. The record index to start at. All items with a lower index will be dropped from the results.
        /// Optional. The maximum number of records to return.
        /// Optional, include image information in output.
        /// Optional, the max number of images to return, per image type.
        /// Optional. The image types to include in the output.
        /// Optional. Include user data.
        /// Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.
        /// A  with the episodes on success or a  if the series was not found.
        [HttpGet("{seriesId}/Episodes")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public ActionResult> GetEpisodes(
            [FromRoute, Required] string? seriesId,
            [FromQuery, Required] Guid? userId,
            [FromQuery] string? fields,
            [FromQuery] int? season,
            [FromQuery] string? seasonId,
            [FromQuery] bool? isMissing,
            [FromQuery] string? adjacentTo,
            [FromQuery] string? startItemId,
            [FromQuery] int? startIndex,
            [FromQuery] int? limit,
            [FromQuery] bool? enableImages,
            [FromQuery] int? imageTypeLimit,
            [FromQuery] string? enableImageTypes,
            [FromQuery] bool? enableUserData,
            [FromQuery] string? sortBy)
        {
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
                ? _userManager.GetUserById(userId.Value)
                : null;
            List episodes;
            var dtoOptions = new DtoOptions()
                .AddItemFields(fields!)
                .AddClientFields(Request)
                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
            if (!string.IsNullOrWhiteSpace(seasonId)) // Season id was supplied. Get episodes by season id.
            {
                var item = _libraryManager.GetItemById(new Guid(seasonId));
                if (!(item is Season seasonItem))
                {
                    return NotFound("No season exists with Id " + seasonId);
                }
                episodes = seasonItem.GetEpisodes(user, dtoOptions);
            }
            else if (season.HasValue) // Season number was supplied. Get episodes by season number
            {
                if (!(_libraryManager.GetItemById(seriesId) is Series series))
                {
                    return NotFound("Series not found");
                }
                var seasonItem = series
                    .GetSeasons(user, dtoOptions)
                    .FirstOrDefault(i => i.IndexNumber == season.Value);
                episodes = seasonItem == null ?
                    new List()
                    : ((Season)seasonItem).GetEpisodes(user, dtoOptions);
            }
            else // No season number or season id was supplied. Returning all episodes.
            {
                if (!(_libraryManager.GetItemById(seriesId) is Series series))
                {
                    return NotFound("Series not found");
                }
                episodes = series.GetEpisodes(user, dtoOptions).ToList();
            }
            // Filter after the fact in case the ui doesn't want them
            if (isMissing.HasValue)
            {
                var val = isMissing.Value;
                episodes = episodes
                    .Where(i => ((Episode)i).IsMissingEpisode == val)
                    .ToList();
            }
            if (!string.IsNullOrWhiteSpace(startItemId))
            {
                episodes = episodes
                    .SkipWhile(i => !string.Equals(i.Id.ToString("N", CultureInfo.InvariantCulture), startItemId, StringComparison.OrdinalIgnoreCase))
                    .ToList();
            }
            // This must be the last filter
            if (!string.IsNullOrEmpty(adjacentTo))
            {
                episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo).ToList();
            }
            if (string.Equals(sortBy, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
            {
                episodes.Shuffle();
            }
            var returnItems = episodes;
            if (startIndex.HasValue || limit.HasValue)
            {
                returnItems = ApplyPaging(episodes, startIndex, limit).ToList();
            }
            var dtos = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user);
            return new QueryResult
            {
                TotalRecordCount = episodes.Count,
                Items = dtos
            };
        }
        /// 
        /// Gets seasons for a tv series.
        /// 
        /// The series id.
        /// The user id.
        /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.
        /// Optional. Filter by special season.
        /// Optional. Filter by items that are missing episodes or not.
        /// Optional. Return items that are siblings of a supplied item.
        /// Optional. Include image information in output.
        /// Optional. The max number of images to return, per image type.
        /// Optional. The image types to include in the output.
        /// Optional. Include user data.
        /// A  on success or a  if the series was not found.
        [HttpGet("{seriesId}/Seasons")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public ActionResult> GetSeasons(
            [FromRoute, Required] string? seriesId,
            [FromQuery, Required] Guid? userId,
            [FromQuery] string? fields,
            [FromQuery] bool? isSpecialSeason,
            [FromQuery] bool? isMissing,
            [FromQuery] string? adjacentTo,
            [FromQuery] bool? enableImages,
            [FromQuery] int? imageTypeLimit,
            [FromQuery] string? enableImageTypes,
            [FromQuery] bool? enableUserData)
        {
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
                ? _userManager.GetUserById(userId.Value)
                : null;
            if (!(_libraryManager.GetItemById(seriesId) is Series series))
            {
                return NotFound("Series not found");
            }
            var seasons = series.GetItemList(new InternalItemsQuery(user)
            {
                IsMissing = isMissing,
                IsSpecialSeason = isSpecialSeason,
                AdjacentTo = adjacentTo
            });
            var dtoOptions = new DtoOptions()
                .AddItemFields(fields)
                .AddClientFields(Request)
                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
            var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user);
            return new QueryResult
            {
                TotalRecordCount = returnItems.Count,
                Items = returnItems
            };
        }
        /// 
        /// Applies the paging.
        /// 
        /// The items.
        /// The start index.
        /// The limit.
        /// IEnumerable{BaseItem}.
        private IEnumerable ApplyPaging(IEnumerable items, int? startIndex, int? limit)
        {
            // Start at
            if (startIndex.HasValue)
            {
                items = items.Skip(startIndex.Value);
            }
            // Return limit
            if (limit.HasValue)
            {
                items = items.Take(limit.Value);
            }
            return items;
        }
    }
}