/// <inheritdoc />
        public async Task <IEnumerable <RemoteSubtitleInfo> > Search(SubtitleSearchRequest request, CancellationToken cancellationToken)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            await Login(cancellationToken).ConfigureAwait(false);

            if (request.IsAutomated && _login?.User?.RemainingDownloads <= 0)
            {
                if (_lastRatelimitLog == null || DateTime.UtcNow.Subtract(_lastRatelimitLog.Value).TotalSeconds > 60)
                {
                    _logger.LogInformation("Daily download limit reached, returning no results for automated task");
                    _lastRatelimitLog = DateTime.UtcNow;
                }

                return(Enumerable.Empty <RemoteSubtitleInfo>());
            }

            long.TryParse(request.GetProviderId(MetadataProvider.Imdb)?.TrimStart('t') ?? string.Empty, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbId);

            if (request.ContentType == VideoContentType.Episode && (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName)))
            {
                _logger.LogDebug("Episode information missing");
                return(Enumerable.Empty <RemoteSubtitleInfo>());
            }

            if (string.IsNullOrEmpty(request.MediaPath))
            {
                _logger.LogDebug("Path Missing");
                return(Enumerable.Empty <RemoteSubtitleInfo>());
            }

            var language = await GetLanguage(request.TwoLetterISOLanguageName, cancellationToken).ConfigureAwait(false);

            string hash;

            try
            {
                #pragma warning disable CA2007
                await using var fileStream = File.OpenRead(request.MediaPath);
                #pragma warning restore CA2007

                hash = OpenSubtitlesRequestHelper.ComputeHash(fileStream);
            }
            catch (IOException ex)
            {
                throw new IOException(string.Format(CultureInfo.InvariantCulture, "IOException while computing hash for {0}", request.MediaPath), ex);
            }

            var options = new Dictionary <string, string>
            {
                { "languages", language },
                { "moviehash", hash },
                { "type", request.ContentType == VideoContentType.Episode ? "episode" : "movie" }
            };

            // If we have the IMDb ID we use that, otherwise query with the details
            if (imdbId != 0)
            {
                options.Add("imdb_id", imdbId.ToString(CultureInfo.InvariantCulture));
            }
            else
            {
                options.Add("query", Path.GetFileName(request.MediaPath));

                if (request.ContentType == VideoContentType.Episode)
                {
                    if (request.ParentIndexNumber.HasValue)
                    {
                        options.Add("season_number", request.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture));
                    }

                    if (request.IndexNumber.HasValue)
                    {
                        options.Add("episode_number", request.IndexNumber.Value.ToString(CultureInfo.InvariantCulture));
                    }
                }
            }

            if (request.IsPerfectMatch)
            {
                options.Add("moviehash_match", "only");
            }

            _logger.LogDebug("Search query: {Query}", options);

            var searchResponse = await OpenSubtitlesHandler.OpenSubtitles.SearchSubtitlesAsync(options, ApiKey, cancellationToken).ConfigureAwait(false);

            if (!searchResponse.Ok)
            {
                _logger.LogError("Invalid response: {Code} - {Body}", searchResponse.Code, searchResponse.Body);
                return(Enumerable.Empty <RemoteSubtitleInfo>());
            }

            bool MediaFilter(ResponseData x) =>
            x.Attributes?.FeatureDetails?.FeatureType == (request.ContentType == VideoContentType.Episode ? "Episode" : "Movie") &&
            request.ContentType == VideoContentType.Episode
                    ? x.Attributes.FeatureDetails.SeasonNumber == request.ParentIndexNumber &&
            x.Attributes.FeatureDetails.EpisodeNumber == request.IndexNumber
                    : x.Attributes?.FeatureDetails?.ImdbId == imdbId;

            if (searchResponse.Data == null)
            {
                return(Enumerable.Empty <RemoteSubtitleInfo>());
            }

            return(searchResponse.Data
                   .Where(x => MediaFilter(x) && (!request.IsPerfectMatch || (x.Attributes?.MovieHashMatch ?? false)))
                   .OrderByDescending(x => x.Attributes?.MovieHashMatch ?? false)
                   .ThenByDescending(x => x.Attributes?.DownloadCount)
                   .ThenByDescending(x => x.Attributes?.Ratings)
                   .ThenByDescending(x => x.Attributes?.FromTrusted)
                   .Select(i => new RemoteSubtitleInfo
            {
                Author = i.Attributes?.Uploader?.Name,
                Comment = i.Attributes?.Comments,
                CommunityRating = i.Attributes?.Ratings,
                DownloadCount = i.Attributes?.DownloadCount,
                Format = "srt",
                ProviderName = Name,
                ThreeLetterISOLanguageName = request.Language,
                Id = $"srt-{request.Language}-{i.Attributes?.Files[0].FileId}",
                Name = i.Attributes?.Release,
                DateCreated = i.Attributes?.UploadDate,
                IsHashMatch = i.Attributes?.MovieHashMatch
            })
                   .Where(i => !string.Equals(i.Format, "sub", StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Format, "idx", StringComparison.OrdinalIgnoreCase)));
        }
 public void ComputeHash_Success(string filename, string hash)
 {
     using var str = File.OpenRead(Path.Join("Test Data", filename));
     Assert.Equal(hash, OpenSubtitlesRequestHelper.ComputeHash(str));
 }