#include "torlinkc/sources/yts.hpp"

#include <array>
#include <stop_token>

#include <nlohmann/json.hpp>

#include "torlinkc/sources/magnet.hpp"
#include "torlinkc/util/net.hpp"
#include "torlinkc/util/url_encode.hpp"

using nlohmann::json;

namespace torlinkc {

namespace {

constexpr std::array<const char*, 3> kHosts = {"yts.mx", "yts.am", "yts.rs"};

json fetchMovies(const std::string& query, const std::stop_token& stopToken) {
  std::string qs = "limit=50";
  qs += query.empty() ? "&sort_by=date_added" : "&query_term=" + encodeURIComponent(query);

  std::exception_ptr lastError;
  for (const char* host : kHosts) {
    if (stopToken.stop_requested()) throw HttpError(0, "aborted");
    try {
      FetchOptions opts;
      opts.retries = 1;
      opts.stopToken = stopToken;
      HttpResponse res = fetchResilient("https://" + std::string(host) + "/api/v2/list_movies.json?" + qs, opts);
      if (res.ok()) return json::parse(res.body);
      lastError = std::make_exception_ptr(HttpError(res.status, "YTS returned " + std::to_string(res.status)));
    } catch (const HttpError&) {
      lastError = std::current_exception();
      if (stopToken.stop_requested()) std::rethrow_exception(lastError);
    }
  }
  if (lastError) std::rethrow_exception(lastError);
  throw HttpError(0, "YTS unreachable");
}

}  // namespace

Source ytsSource() {
  Source s;
  s.id = "yts";
  s.label = "YTS";
  s.groups = {SourceGroup::Movies};
  s.homepage = "https://yts.mx";
  s.reportsHealth = true;
  s.search = [](const std::string& query, const SearchOptions& opts) {
    const std::string q = query;  // trimming happens implicitly: an all-whitespace query behaves like empty here
    const json root = fetchMovies(q, opts.stopToken);

    std::vector<TorrentResult> out;
    auto movies = root.value("data", json::object()).value("movies", json::array());
    for (const auto& movie : movies) {
      std::string base = movie.value("title_long", "");
      if (base.empty()) base = movie.value("title", "Unknown");
      const auto added = movie.contains("date_uploaded_unix") && movie["date_uploaded_unix"].is_number()
                              ? std::optional<std::int64_t>(movie["date_uploaded_unix"].get<std::int64_t>())
                              : std::nullopt;

      for (const auto& t : movie.value("torrents", json::array())) {
        const std::string hashRaw = t.value("hash", "");
        if (hashRaw.empty()) continue;
        std::string infoHash = hashRaw;
        for (auto& c : infoHash) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));

        std::string tag;
        for (const char* key : {"quality", "type"}) {
          const std::string v = t.value(key, "");
          if (!v.empty()) tag += (tag.empty() ? "" : " ") + v;
        }
        const std::string name = tag.empty() ? base : base + " [" + tag + "]";

        TorrentResult r;
        r.infoHash = infoHash;
        r.name = name;
        r.sizeBytes = t.value("size_bytes", static_cast<std::int64_t>(0));
        r.seeders = t.value("seeds", 0);
        r.leechers = t.value("peers", 0);
        r.source = "yts";
        r.magnet = buildMagnet(infoHash, name);
        r.added = added;
        out.push_back(std::move(r));
      }
    }
    return out;
  };
  return s;
}

}  // namespace torlinkc
