using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers;
/// 
/// Movies controller.
/// 
[Authorize]
public class MoviesController : BaseJellyfinApiController
{
    private readonly IUserManager _userManager;
    private readonly ILibraryManager _libraryManager;
    private readonly IDtoService _dtoService;
    private readonly IServerConfigurationManager _serverConfigurationManager;
    /// 
    /// Initializes a new instance of the  class.
    /// 
    /// Instance of the  interface.
    /// Instance of the  interface.
    /// Instance of the  interface.
    /// Instance of the  interface.
    public MoviesController(
        IUserManager userManager,
        ILibraryManager libraryManager,
        IDtoService dtoService,
        IServerConfigurationManager serverConfigurationManager)
    {
        _userManager = userManager;
        _libraryManager = libraryManager;
        _dtoService = dtoService;
        _serverConfigurationManager = serverConfigurationManager;
    }
    /// 
    /// Gets movie recommendations.
    /// 
    /// Optional. Filter by user id, and attach user data.
    /// Specify this to localize the search to a specific item or folder. Omit to use the root.
    /// Optional. The fields to return.
    /// The max number of categories to return.
    /// The max number of items to return per category.
    /// Movie recommendations returned.
    /// The list of movie recommendations.
    [HttpGet("Recommendations")]
    public ActionResult> GetMovieRecommendations(
        [FromQuery] Guid? userId,
        [FromQuery] Guid? parentId,
        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
        [FromQuery] int categoryLimit = 5,
        [FromQuery] int itemLimit = 8)
    {
        userId = RequestHelpers.GetUserId(User, userId);
        var user = userId.IsNullOrEmpty()
            ? null
            : _userManager.GetUserById(userId.Value);
        var dtoOptions = new DtoOptions { Fields = fields }
            .AddClientFields(User);
        var categories = new List();
        var parentIdGuid = parentId ?? Guid.Empty;
        var query = new InternalItemsQuery(user)
        {
            IncludeItemTypes = new[]
            {
                BaseItemKind.Movie,
                // nameof(Trailer),
                // nameof(LiveTvProgram)
            },
            // IsMovie = true
            OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.Random, SortOrder.Descending) },
            Limit = 7,
            ParentId = parentIdGuid,
            Recursive = true,
            IsPlayed = true,
            DtoOptions = dtoOptions
        };
        var recentlyPlayedMovies = _libraryManager.GetItemList(query);
        var itemTypes = new List { BaseItemKind.Movie };
        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
        {
            itemTypes.Add(BaseItemKind.Trailer);
            itemTypes.Add(BaseItemKind.LiveTvProgram);
        }
        var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
        {
            IncludeItemTypes = itemTypes.ToArray(),
            IsMovie = true,
            OrderBy = new[] { (ItemSortBy.Random, SortOrder.Descending) },
            Limit = 10,
            IsFavoriteOrLiked = true,
            ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(),
            EnableGroupByMetadataKey = true,
            ParentId = parentIdGuid,
            Recursive = true,
            DtoOptions = dtoOptions
        });
        var mostRecentMovies = recentlyPlayedMovies.Take(Math.Min(recentlyPlayedMovies.Count, 6)).ToList();
        // Get recently played directors
        var recentDirectors = GetDirectors(mostRecentMovies)
            .ToList();
        // Get recently played actors
        var recentActors = GetActors(mostRecentMovies)
            .ToList();
        var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
        var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
        var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
        var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
        var categoryTypes = new List>
            {
                // Give this extra weight
                similarToRecentlyPlayed,
                similarToRecentlyPlayed,
                // Give this extra weight
                similarToLiked,
                similarToLiked,
                hasDirectorFromRecentlyPlayed,
                hasActorFromRecentlyPlayed
            };
        while (categories.Count < categoryLimit)
        {
            var allEmpty = true;
            foreach (var category in categoryTypes)
            {
                if (category.MoveNext())
                {
                    categories.Add(category.Current);
                    allEmpty = false;
                    if (categories.Count >= categoryLimit)
                    {
                        break;
                    }
                }
            }
            if (allEmpty)
            {
                break;
            }
        }
        return Ok(categories.OrderBy(i => i.RecommendationType).AsEnumerable());
    }
    private IEnumerable GetWithDirector(
        User? user,
        IEnumerable names,
        int itemLimit,
        DtoOptions dtoOptions,
        RecommendationType type)
    {
        var itemTypes = new List { BaseItemKind.Movie };
        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
        {
            itemTypes.Add(BaseItemKind.Trailer);
            itemTypes.Add(BaseItemKind.LiveTvProgram);
        }
        foreach (var name in names)
        {
            var items = _libraryManager.GetItemList(
                new InternalItemsQuery(user)
                {
                    Person = name,
                    // Account for duplicates by IMDb id, since the database doesn't support this yet
                    Limit = itemLimit + 2,
                    PersonTypes = new[] { PersonType.Director },
                    IncludeItemTypes = itemTypes.ToArray(),
                    IsMovie = true,
                    EnableGroupByMetadataKey = true,
                    DtoOptions = dtoOptions
                }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))
                .Take(itemLimit)
                .ToList();
            if (items.Count > 0)
            {
                var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
                yield return new RecommendationDto
                {
                    BaselineItemName = name,
                    CategoryId = name.GetMD5(),
                    RecommendationType = type,
                    Items = returnItems
                };
            }
        }
    }
    private IEnumerable GetWithActor(User? user, IEnumerable names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
    {
        var itemTypes = new List { BaseItemKind.Movie };
        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
        {
            itemTypes.Add(BaseItemKind.Trailer);
            itemTypes.Add(BaseItemKind.LiveTvProgram);
        }
        foreach (var name in names)
        {
            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
            {
                Person = name,
                // Account for duplicates by IMDb id, since the database doesn't support this yet
                Limit = itemLimit + 2,
                IncludeItemTypes = itemTypes.ToArray(),
                IsMovie = true,
                EnableGroupByMetadataKey = true,
                DtoOptions = dtoOptions
            }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))
                .Take(itemLimit)
                .ToList();
            if (items.Count > 0)
            {
                var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
                yield return new RecommendationDto
                {
                    BaselineItemName = name,
                    CategoryId = name.GetMD5(),
                    RecommendationType = type,
                    Items = returnItems
                };
            }
        }
    }
    private IEnumerable GetSimilarTo(User? user, IEnumerable baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
    {
        var itemTypes = new List { BaseItemKind.Movie };
        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
        {
            itemTypes.Add(BaseItemKind.Trailer);
            itemTypes.Add(BaseItemKind.LiveTvProgram);
        }
        foreach (var item in baselineItems)
        {
            var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
            {
                Limit = itemLimit,
                IncludeItemTypes = itemTypes.ToArray(),
                IsMovie = true,
                EnableGroupByMetadataKey = true,
                DtoOptions = dtoOptions
            });
            if (similar.Count > 0)
            {
                var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user);
                yield return new RecommendationDto
                {
                    BaselineItemName = item.Name,
                    CategoryId = item.Id,
                    RecommendationType = type,
                    Items = returnItems
                };
            }
        }
    }
    private IEnumerable GetActors(IEnumerable items)
    {
        var people = _libraryManager.GetPeople(new InternalPeopleQuery(Array.Empty(), new[] { PersonType.Director })
        {
            MaxListOrder = 3
        });
        var itemIds = items.Select(i => i.Id).ToList();
        return people
            .Where(i => itemIds.Contains(i.ItemId))
            .Select(i => i.Name)
            .DistinctNames();
    }
    private IEnumerable GetDirectors(IEnumerable items)
    {
        var people = _libraryManager.GetPeople(new InternalPeopleQuery(
            new[] { PersonType.Director },
            Array.Empty()));
        var itemIds = items.Select(i => i.Id).ToList();
        return people
            .Where(i => itemIds.Contains(i.ItemId))
            .Select(i => i.Name)
            .DistinctNames();
    }
}