#nullable disable

#pragma warning disable CS1591, SA1300

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;

namespace MediaBrowser.Providers.Plugins.Omdb
{
    public class OmdbItemProvider : IRemoteMetadataProvider<Series, SeriesInfo>,
        IRemoteMetadataProvider<Movie, MovieInfo>, IRemoteMetadataProvider<Trailer, TrailerInfo>, IHasOrder
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly ILibraryManager _libraryManager;
        private readonly JsonSerializerOptions _jsonOptions;
        private readonly OmdbProvider _omdbProvider;

        public OmdbItemProvider(
            IHttpClientFactory httpClientFactory,
            ILibraryManager libraryManager,
            IFileSystem fileSystem,
            IServerConfigurationManager configurationManager)
        {
            _httpClientFactory = httpClientFactory;
            _libraryManager = libraryManager;
            _omdbProvider = new OmdbProvider(_httpClientFactory, fileSystem, configurationManager);

            _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
            _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter());
            _jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter());
        }

        public string Name => "The Open Movie Database";

        // After primary option
        public int Order => 2;

        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(TrailerInfo searchInfo, CancellationToken cancellationToken)
        {
            return GetSearchResultsInternal(searchInfo, true, cancellationToken);
        }

        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
        {
            return GetSearchResultsInternal(searchInfo, true, cancellationToken);
        }

        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
        {
            return GetSearchResultsInternal(searchInfo, true, cancellationToken);
        }

        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
        {
            return GetSearchResultsInternal(searchInfo, true, cancellationToken);
        }

        private async Task<IEnumerable<RemoteSearchResult>> GetSearchResultsInternal(ItemLookupInfo searchInfo, bool isSearch, CancellationToken cancellationToken)
        {
            var type = searchInfo switch
            {
                EpisodeInfo => "episode",
                SeriesInfo => "series",
                _ => "movie"
            };

            // This is a bit hacky?
            var episodeSearchInfo = searchInfo as EpisodeInfo;
            var indexNumberEnd = episodeSearchInfo?.IndexNumberEnd;

            var imdbId = searchInfo.GetProviderId(MetadataProvider.Imdb);

            var urlQuery = new StringBuilder("plot=full&r=json");
            if (episodeSearchInfo is not null)
            {
                episodeSearchInfo.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out imdbId);
                if (searchInfo.IndexNumber.HasValue)
                {
                    urlQuery.Append("&Episode=").Append(searchInfo.IndexNumber.Value);
                }

                if (searchInfo.ParentIndexNumber.HasValue)
                {
                    urlQuery.Append("&Season=").Append(searchInfo.ParentIndexNumber.Value);
                }
            }

            if (string.IsNullOrWhiteSpace(imdbId))
            {
                var name = searchInfo.Name;
                var year = searchInfo.Year;
                if (!string.IsNullOrWhiteSpace(name))
                {
                    var parsedName = _libraryManager.ParseName(name);
                    var yearInName = parsedName.Year;
                    name = parsedName.Name;
                    year ??= yearInName;
                }

                if (year.HasValue)
                {
                    urlQuery.Append("&y=").Append(year);
                }

                // &s means search and returns a list of results as opposed to t
                urlQuery.Append(isSearch ? "&s=" : "&t=");
                urlQuery.Append(WebUtility.UrlEncode(name));
                urlQuery.Append("&type=")
                    .Append(type);
            }
            else
            {
                urlQuery.Append("&i=")
                    .Append(imdbId);
                isSearch = false;
            }

            var url = OmdbProvider.GetOmdbUrl(urlQuery.ToString());

            using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
            var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
            await using (stream.ConfigureAwait(false))
            {
                if (isSearch)
                {
                    var searchResultList = await JsonSerializer.DeserializeAsync<SearchResultList>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
                    if (searchResultList?.Search is not null)
                    {
                        var resultCount = searchResultList.Search.Count;
                        var result = new RemoteSearchResult[resultCount];
                        for (var i = 0; i < resultCount; i++)
                        {
                            result[i] = ResultToMetadataResult(searchResultList.Search[i], searchInfo, indexNumberEnd);
                        }

                        return result;
                    }
                }
                else
                {
                    var result = await JsonSerializer.DeserializeAsync<SearchResult>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
                    if (string.Equals(result?.Response, "true", StringComparison.OrdinalIgnoreCase))
                    {
                        return new[] { ResultToMetadataResult(result, searchInfo, indexNumberEnd) };
                    }
                }
            }

            return Enumerable.Empty<RemoteSearchResult>();
        }

        public Task<MetadataResult<Trailer>> GetMetadata(TrailerInfo info, CancellationToken cancellationToken)
        {
            return GetResult<Trailer>(info, cancellationToken);
        }

        public Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
        {
            return GetResult<Series>(info, cancellationToken);
        }

        public Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
        {
            return GetResult<Movie>(info, cancellationToken);
        }

        private RemoteSearchResult ResultToMetadataResult(SearchResult result, ItemLookupInfo searchInfo, int? indexNumberEnd)
        {
            var item = new RemoteSearchResult
            {
                IndexNumber = searchInfo.IndexNumber,
                Name = result.Title,
                ParentIndexNumber = searchInfo.ParentIndexNumber,
                SearchProviderName = Name,
                IndexNumberEnd = indexNumberEnd
            };

            item.SetProviderId(MetadataProvider.Imdb, result.imdbID);

            if (OmdbProvider.TryParseYear(result.Year, out var parsedYear))
            {
                item.ProductionYear = parsedYear;
            }

            if (!string.IsNullOrEmpty(result.Released)
                && DateTime.TryParse(result.Released, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var released))
            {
                item.PremiereDate = released;
            }

            if (!string.IsNullOrWhiteSpace(result.Poster))
            {
                item.ImageUrl = result.Poster;
            }

            return item;
        }

        private async Task<MetadataResult<T>> GetResult<T>(ItemLookupInfo info, CancellationToken cancellationToken)
            where T : BaseItem, new()
        {
            var result = new MetadataResult<T>
            {
                Item = new T(),
                QueriedById = true
            };

            var imdbId = info.GetProviderId(MetadataProvider.Imdb);
            if (string.IsNullOrWhiteSpace(imdbId))
            {
                imdbId = await GetImdbId(info, cancellationToken).ConfigureAwait(false);
                result.QueriedById = false;
            }

            if (!string.IsNullOrEmpty(imdbId))
            {
                result.Item.SetProviderId(MetadataProvider.Imdb, imdbId);
                result.HasMetadata = true;

                await _omdbProvider.Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
            }

            return result;
        }

        private async Task<string> GetImdbId(ItemLookupInfo info, CancellationToken cancellationToken)
        {
            var results = await GetSearchResultsInternal(info, false, cancellationToken).ConfigureAwait(false);
            var first = results.FirstOrDefault();
            return first?.GetProviderId(MetadataProvider.Imdb);
        }

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

        private class SearchResult
        {
            public string Title { get; set; }

            public string Year { get; set; }

            public string Rated { get; set; }

            public string Released { get; set; }

            public string Season { get; set; }

            public string Episode { get; set; }

            public string Runtime { get; set; }

            public string Genre { get; set; }

            public string Director { get; set; }

            public string Writer { get; set; }

            public string Actors { get; set; }

            public string Plot { get; set; }

            public string Language { get; set; }

            public string Country { get; set; }

            public string Awards { get; set; }

            public string Poster { get; set; }

            public string Metascore { get; set; }

            public string imdbRating { get; set; }

            public string imdbVotes { get; set; }

            public string imdbID { get; set; }

            public string seriesID { get; set; }

            public string Type { get; set; }

            public string Response { get; set; }
        }

        private class SearchResultList
        {
            /// <summary>
            /// Gets or sets the results.
            /// </summary>
            /// <value>The results.</value>
            public List<SearchResult> Search { get; set; }
        }
    }
}
