using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using TMDbLib.Objects.Find;
using TMDbLib.Objects.General;
using TMDbLib.Objects.Search;

namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
    /// <summary>
    /// Movie provider powered by TMDb.
    /// </summary>
    public class TmdbMovieProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IHasOrder
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly ILibraryManager _libraryManager;
        private readonly TmdbClientManager _tmdbClientManager;

        /// <summary>
        /// Initializes a new instance of the <see cref="TmdbMovieProvider"/> class.
        /// </summary>
        /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param>
        /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
        /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
        public TmdbMovieProvider(
            ILibraryManager libraryManager,
            TmdbClientManager tmdbClientManager,
            IHttpClientFactory httpClientFactory)
        {
            _libraryManager = libraryManager;
            _tmdbClientManager = tmdbClientManager;
            _httpClientFactory = httpClientFactory;
        }

        /// <inheritdoc />
        public int Order => 1;

        /// <inheritdoc />
        public string Name => TmdbUtils.ProviderName;

        /// <inheritdoc />
        public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
        {
            if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var id))
            {
                var movie = await _tmdbClientManager
                    .GetMovieAsync(
                        int.Parse(id, CultureInfo.InvariantCulture),
                        searchInfo.MetadataLanguage,
                        TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
                        searchInfo.MetadataCountryCode,
                        cancellationToken)
                    .ConfigureAwait(false);

                if (movie is not null)
                {
                    var remoteResult = new RemoteSearchResult
                    {
                        Name = movie.Title ?? movie.OriginalTitle,
                        SearchProviderName = Name,
                        ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath),
                        Overview = movie.Overview
                    };

                    if (movie.ReleaseDate is not null)
                    {
                        var releaseDate = movie.ReleaseDate.Value.ToUniversalTime();
                        remoteResult.PremiereDate = releaseDate;
                        remoteResult.ProductionYear = releaseDate.Year;
                    }

                    remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
                    remoteResult.TrySetProviderId(MetadataProvider.Imdb, movie.ImdbId);

                    return [remoteResult];
                }
            }

            IReadOnlyList<SearchMovie>? movieResults = null;
            if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out id))
            {
                var result = await _tmdbClientManager.FindByExternalIdAsync(
                    id,
                    FindExternalSource.Imdb,
                    TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
                    searchInfo.MetadataCountryCode,
                    cancellationToken).ConfigureAwait(false);
                movieResults = result?.MovieResults;
            }

            if (movieResults is null && searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id))
            {
                var result = await _tmdbClientManager.FindByExternalIdAsync(
                    id,
                    FindExternalSource.TvDb,
                    TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
                    searchInfo.MetadataCountryCode,
                    cancellationToken).ConfigureAwait(false);
                movieResults = result?.MovieResults;
            }

            if (movieResults is null)
            {
                movieResults = await _tmdbClientManager
                    .SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode, cancellationToken)
                    .ConfigureAwait(false);
            }

            if (movieResults is null)
            {
                return [];
            }

            var len = movieResults.Count;
            var remoteSearchResults = new RemoteSearchResult[len];
            for (var i = 0; i < len; i++)
            {
                var movieResult = movieResults[i];
                var remoteSearchResult = new RemoteSearchResult
                {
                    Name = movieResult.Title ?? movieResult.OriginalTitle,
                    ImageUrl = _tmdbClientManager.GetPosterUrl(movieResult.PosterPath),
                    Overview = movieResult.Overview,
                    SearchProviderName = Name
                };

                var releaseDate = movieResult.ReleaseDate?.ToUniversalTime();
                remoteSearchResult.PremiereDate = releaseDate;
                remoteSearchResult.ProductionYear = releaseDate?.Year;

                remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, movieResult.Id.ToString(CultureInfo.InvariantCulture));
                remoteSearchResults[i] = remoteSearchResult;
            }

            return remoteSearchResults;
        }

        /// <inheritdoc />
        public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
        {
            var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
            var imdbId = info.GetProviderId(MetadataProvider.Imdb);
            var config = Plugin.Instance.Configuration;

            if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId))
            {
                // ParseName is required here.
                // Caller provides the filename with extension stripped and NOT the parsed filename
                var parsedName = _libraryManager.ParseName(info.Name);
                var cleanedName = TmdbUtils.CleanName(parsedName.Name);

                var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);

                if (searchResults?.Count > 0)
                {
                    tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
                }
            }

            if (string.IsNullOrEmpty(tmdbId) && !string.IsNullOrEmpty(imdbId))
            {
                var movieResultFromImdbId = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
                if (movieResultFromImdbId?.MovieResults?.Count > 0)
                {
                    tmdbId = movieResultFromImdbId.MovieResults[0].Id.ToString(CultureInfo.InvariantCulture);
                }
            }

            if (string.IsNullOrEmpty(tmdbId))
            {
                return new MetadataResult<Movie>();
            }

            var movieResult = await _tmdbClientManager
                .GetMovieAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken)
                .ConfigureAwait(false);

            if (movieResult is null)
            {
                return new MetadataResult<Movie>();
            }

            var movie = new Movie
            {
                Name = movieResult.Title ?? movieResult.OriginalTitle,
                OriginalTitle = movieResult.OriginalTitle,
                Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture),
                Tagline = movieResult.Tagline,
                ProductionLocations = movieResult.ProductionCountries?.Select(pc => pc.Name).ToArray() ?? Array.Empty<string>()
            };
            var metadataResult = new MetadataResult<Movie>
            {
                HasMetadata = true,
                ResultLanguage = info.MetadataLanguage,
                Item = movie
            };

            movie.SetProviderId(MetadataProvider.Tmdb, tmdbId);
            movie.TrySetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
            if (movieResult.BelongsToCollection is not null)
            {
                movie.SetProviderId(MetadataProvider.TmdbCollection, movieResult.BelongsToCollection.Id.ToString(CultureInfo.InvariantCulture));
                movie.CollectionName = movieResult.BelongsToCollection.Name;
            }

            movie.CommunityRating = Convert.ToSingle(movieResult.VoteAverage);

            if (movieResult.Releases?.Countries is not null)
            {
                var releases = movieResult.Releases.Countries.Where(i => !string.IsNullOrWhiteSpace(i.Certification)).ToList();

                var ourRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, info.MetadataCountryCode, StringComparison.OrdinalIgnoreCase));

                if (ourRelease?.Certification is not null)
                {
                    movie.OfficialRating = TmdbUtils.BuildParentalRating(info.MetadataCountryCode, ourRelease.Certification);
                }
                else
                {
                    var usRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase));
                    if (usRelease?.Certification is not null)
                    {
                        movie.OfficialRating = usRelease.Certification;
                    }
                }
            }

            movie.PremiereDate = movieResult.ReleaseDate;
            movie.ProductionYear = movieResult.ReleaseDate?.Year;

            if (movieResult.ProductionCompanies is not null)
            {
                movie.SetStudios(movieResult.ProductionCompanies.Select(c => c.Name));
            }

            var genres = movieResult.Genres;

            if (genres is not null)
            {
                foreach (var genre in genres.Select(g => g.Name).Trimmed())
                {
                    movie.AddGenre(genre);
                }
            }

            if (movieResult.Keywords?.Keywords is not null)
            {
                foreach (var keyword in movieResult.Keywords.Keywords)
                {
                    var name = keyword.Name;
                    if (!string.IsNullOrWhiteSpace(name))
                    {
                        movie.AddTag(name);
                    }
                }
            }

            if (movieResult.Credits?.Cast is not null)
            {
                var castQuery = movieResult.Credits.Cast.AsEnumerable();

                if (config.HideMissingCastMembers)
                {
                    castQuery = castQuery.Where(a => !string.IsNullOrEmpty(a.ProfilePath));
                }

                castQuery = castQuery.OrderBy(a => a.Order).Take(config.MaxCastMembers);

                foreach (var actor in castQuery)
                {
                    if (string.IsNullOrWhiteSpace(actor.Name))
                    {
                        continue;
                    }

                    var personInfo = new PersonInfo
                    {
                        Name = actor.Name.Trim(),
                        Role = actor.Character?.Trim() ?? string.Empty,
                        Type = PersonKind.Actor,
                        SortOrder = actor.Order
                    };

                    if (!string.IsNullOrWhiteSpace(actor.ProfilePath))
                    {
                        personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath);
                    }

                    if (actor.Id > 0)
                    {
                        personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
                    }

                    metadataResult.AddPerson(personInfo);
                }
            }

            if (movieResult.Credits?.Crew is not null)
            {
                var crewQuery = movieResult.Credits.Crew
                    .Select(crewMember => new
                    {
                        CrewMember = crewMember,
                        PersonType = TmdbUtils.MapCrewToPersonType(crewMember)
                    })
                    .Where(entry => TmdbUtils.WantedCrewKinds.Contains(entry.PersonType));

                if (config.HideMissingCrewMembers)
                {
                    crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath));
                }

                crewQuery = crewQuery.Take(config.MaxCrewMembers);

                foreach (var entry in crewQuery)
                {
                    var crewMember = entry.CrewMember;

                    if (string.IsNullOrWhiteSpace(crewMember.Name))
                    {
                        continue;
                    }

                    var personInfo = new PersonInfo
                    {
                        Name = crewMember.Name.Trim(),
                        Role = crewMember.Job?.Trim() ?? string.Empty,
                        Type = entry.PersonType
                    };

                    if (!string.IsNullOrWhiteSpace(crewMember.ProfilePath))
                    {
                        personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath);
                    }

                    if (crewMember.Id > 0)
                    {
                        personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture));
                    }

                    metadataResult.AddPerson(personInfo);
                }
            }

            if (movieResult.Videos?.Results is not null)
            {
                var trailers = new List<MediaUrl>();

                var sortedVideos = movieResult.Videos.Results
                    .OrderByDescending(video => string.Equals(video.Type, "trailer", StringComparison.OrdinalIgnoreCase));

                foreach (var video in sortedVideos)
                {
                    if (!TmdbUtils.IsTrailerType(video))
                    {
                        continue;
                    }

                    trailers.Add(new MediaUrl
                    {
                        Url = string.Format(CultureInfo.InvariantCulture, "https://www.youtube.com/watch?v={0}", video.Key),
                        Name = video.Name
                    });
                }

                movie.RemoteTrailers = trailers;
            }

            if (!string.IsNullOrEmpty(movieResult.OriginalLanguage))
            {
                movie.OriginalLanguage = movieResult.OriginalLanguage;
            }

            return metadataResult;
        }

        /// <inheritdoc />
        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
        {
            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
        }
    }
}
