context: using System;
  using System.Collections.Generic;
  using System.Collections.Specialized;
  using System.Diagnostics.CodeAnalysis;
  using System.Globalization;
  using System.Linq;
  using System.Net;
  using System.Text;
  using System.Threading.Tasks;
  using AngleSharp.Html.Parser;
  using Jackett.Common.Models;
  using Jackett.Common.Models.IndexerConfig.Bespoke;
  using Jackett.Common.Services.Interfaces;
  using Jackett.Common.Utils;
  using Jackett.Common.Utils.Clients;
  using Newtonsoft.Json.Linq;
  using NLog;
  using WebClient = Jackett.Common.Utils.Clients.WebClient;
  
  namespace Jackett.Common.Indexers.Abstract
  {
      [ExcludeFromCodeCoverage]
      public abstract class GazelleTracker : BaseWebIndexer
      {
          protected virtual string LoginUrl => SiteLink + "login.php";
          protected virtual string APIUrl => SiteLink + "ajax.php";
          protected virtual string DownloadUrl => SiteLink + "torrents.php?action=download&usetoken=" + (useTokens ? "1" : "0") + (usePassKey ? "&torrent_pass=" + configData.PassKey.Value : "") + "&id=";
          protected virtual string DetailsUrl => SiteLink + "torrents.php?torrentid=";
          protected virtual string PosterUrl => SiteLink;
          protected virtual string AuthorizationFormat => "{0}";
          protected virtual int ApiKeyLength => 41;
  
          protected bool useTokens;
          protected string cookie = "";
  
          private readonly bool imdbInTags;
          private readonly bool useApiKey;
          private readonly bool usePassKey;
  
          private new ConfigurationDataGazelleTracker configData => (ConfigurationDataGazelleTracker)base.configData;
  
          protected GazelleTracker(string link, string id, string name, string description,
                                   IIndexerConfigurationService configService, WebClient client, Logger logger,
                                   IProtectionService p, ICacheService cs, TorznabCapabilities caps,
                                   bool supportsFreeleechTokens, bool imdbInTags = false, bool has2Fa = false,
                                   bool useApiKey = false, bool usePassKey = false, string instructionMessageOptional = null)
              : base(id: id,
                     name: name,
                     description: description,
                     link: link,
                     caps: caps,
                     configService: configService,
                     client: client,
                     logger: logger,
                     p: p,
                     cacheService: cs,
                     configData: new ConfigurationDataGazelleTracker(
                         has2Fa, supportsFreeleechTokens, useApiKey, usePassKey, instructionMessageOptional))
          {
              Encoding = Encoding.UTF8;
  
              this.imdbInTags = imdbInTags;
              this.useApiKey = useApiKey;
              this.usePassKey = usePassKey;
          }
  
          public override void LoadValuesFromJson(JToken jsonConfig, bool useProtectionService = false)
          {
              base.LoadValuesFromJson(jsonConfig, useProtectionService);
  
              var cookieItem = configData.CookieItem;
              if (cookieItem != null)
                  cookie = cookieItem.Value;
  
              var useTokenItem = configData.UseTokenItem;
              if (useTokenItem != null)
                  useTokens = useTokenItem.Value;
          }
  
          public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
          {
              LoadValuesFromJson(configJson);
  
              if (useApiKey)
              {
                  var apiKey = configData.ApiKey;
                  if (apiKey?.Value == null)
                      throw new Exception("Invalid API Key configured");
                  if (apiKey.Value.Length != ApiKeyLength)
                      throw new Exception($"Invalid API Key configured: expected length: {ApiKeyLength}, got {apiKey.Value.Length}");
  
                  try
                  {
                      var results = await PerformQuery(new TorznabQuery());
                      if (!results.Any())
                          throw new Exception("Found 0 results in the tracker");
  
                      IsConfigured = true;
                      SaveConfig();
                      return IndexerConfigurationStatus.Completed;
                  }
                  catch (Exception e)
                  {
                      IsConfigured = false;
                      throw new Exception($"Your API Key did not work: {e.Message}");
                  }
              }
  
              var pairs = new Dictionary<string, string> {
                  { "username", configData.Username.Value },
                  { "password", configData.Password.Value },
                  { "keeplogged", "1"}
              };
  
              if (!string.IsNullOrWhiteSpace(cookie))
              {
                  // Cookie was manually supplied
                  CookieHeader = cookie;
                  try
                  {
                      var results = await PerformQuery(new TorznabQuery());
                      if (!results.Any())
                          throw new Exception("Found 0 results in the tracker");
  
                      IsConfigured = true;
                      SaveConfig();
                      return IndexerConfigurationStatus.Completed;
                  }
                  catch (Exception e)
                  {
                      IsConfigured = false;
                      throw new Exception($"Your cookie did not work: {e.Message}");
                  }
              }
  
              var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, string.Empty, true, SiteLink);
              await ConfigureIfOK(response.Cookies, response.ContentString != null && response.ContentString.Contains("logout.php"), () =>
              {
                  var loginResultParser = new HtmlParser();
                  var loginResultDocument = loginResultParser.ParseDocument(response.ContentString);
                  var loginform = loginResultDocument.QuerySelector("#loginform");
                  if (loginform == null)
                      throw new ExceptionWithConfigData(response.ContentString, configData);
  
                  loginform.QuerySelector("table").Remove();
                  var errorMessage = loginform.TextContent.Replace("\n\t", " ").Trim();
                  throw new ExceptionWithConfigData(errorMessage, configData);
              });
              return IndexerConfigurationStatus.RequiresTesting;
          }
  
          // hook to adjust the search term
          protected virtual string GetSearchTerm(TorznabQuery query) => query.GetQueryString();
  
          protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
          {
              var releases = new List<ReleaseInfo>();
              var searchString = GetSearchTerm(query);
  
              var searchUrl = APIUrl;
              var queryCollection = new NameValueCollection
              {
                  { "action", "browse" },
                  //{"group_results", "0"}, # results won't include all information
                  { "order_by", "time" },
                  { "order_way", "desc" }
              };
  
              if (!string.IsNullOrWhiteSpace(query.ImdbID))
              {
                  if (imdbInTags)
                      queryCollection.Add("taglist", query.ImdbID);
                  else
                      queryCollection.Add("cataloguenumber", query.ImdbID);
              }
              else if (!string.IsNullOrWhiteSpace(searchString))
                  queryCollection.Add("searchstr", searchString);
  
              if (query.Artist != null)
                  queryCollection.Add("artistname", query.Artist);
  
              if (query.Label != null)
                  queryCollection.Add("recordlabel", query.Label);
  
              if (query.Year != null)
                  queryCollection.Add("year", query.Year.ToString());
  
              if (query.Album != null)
                  queryCollection.Add("groupname", query.Album);
  
              foreach (var cat in MapTorznabCapsToTrackers(query))
                  queryCollection.Add("filter_cat[" + cat + "]", "1");
  
              searchUrl += "?" + queryCollection.GetQueryString();
  
              var apiKey = configData.ApiKey;
              var headers = apiKey != null ? new Dictionary<string, string> { ["Authorization"] = String.Format(AuthorizationFormat, apiKey.Value) } : null;
  
              var response = await RequestWithCookiesAndRetryAsync(searchUrl, headers: headers);
              // we get a redirect in html pages and an error message in json response (api)
              if (response.IsRedirect || (response.ContentString != null && response.ContentString.Contains("\"bad credentials\"")))
              {
                  // re-login
                  await ApplyConfiguration(null);
                  response = await RequestWithCookiesAndRetryAsync(searchUrl);
              }
  
              try
              {
  
gt:                 var json = JObject.Parse(response.ContentString);