// Phase 1 deliverable: a throwaway console harness proving the core engine
// (queue + persistence + bootguard + one source) end-to-end, ahead of any
// TUI. `search` hits apibay and prints results; `run` restores persisted
// state (in safe mode if the previous run was killed mid-restore), adds an
// optional magnet/info hash, and prints live progress once a second until
// Ctrl+C -- or until something less polite than Ctrl+C ends it, which is
// exactly the case bootguard exists to survive.

#include <atomic>
#include <chrono>
#include <csignal>
#include <cstdio>
#include <iostream>
#include <thread>

#include "torlinkc/config/config.hpp"
#include "torlinkc/config/paths.hpp"
#include "torlinkc/engine/bootguard.hpp"
#include "torlinkc/engine/persist.hpp"
#include "torlinkc/engine/queue.hpp"
#include "torlinkc/engine/reconcile.hpp"
#include "torlinkc/sources/cache.hpp"
#include "torlinkc/sources/magnet.hpp"
#include "torlinkc/sources/piratebay.hpp"
#include "torlinkc/util/format.hpp"

using namespace torlinkc;

namespace {

std::atomic<bool> gStop{false};
void handleSignal(int) { gStop = true; }

void printUsage() {
  std::cout << "usage: torlinkc_core_cli search <query>\n"
               "       torlinkc_core_cli run [magnet|infohash] [download-dir]\n";
}

void doSearch(const std::string& query) {
  SearchCache cache;
  std::vector<TorrentResult> results = cache.cachedSearch(tpbMoviesSource(), query);
  auto tv = cache.cachedSearch(tpbTvSource(), query);
  results.insert(results.end(), tv.begin(), tv.end());

  if (results.empty()) {
    std::cout << "no results\n";
    return;
  }
  for (const auto& r : results) {
    std::cout << "[" << r.source << "] " << r.name << "  (" << formatBytes(static_cast<double>(r.sizeBytes))
               << ", seeders=" << r.seeders << ", leechers=" << r.leechers << ")\n"
               << "    " << r.magnet << "\n";
  }
}

void printItem(const QueueItem& it) {
  std::cout << "  " << it.name.substr(0, 60) << "  [" << downloadStatusToString(it.status) << "] " << it.progress
            << "%  "
            << formatBytes(static_cast<double>(it.downloadedBytes)) << "/"
            << formatBytes(static_cast<double>(it.totalBytes)) << "  " << formatBytes(it.speed) << "/s  peers="
            << it.peers;
  if (it.error) std::cout << "  error=" << *it.error;
  std::cout << "\n";
}

void printSeed(const SeedItem& s) {
  std::cout << "  " << s.name.substr(0, 60) << "  [" << seedStatusToString(s.status) << "] "
            << formatBytes(static_cast<double>(s.uploaded)) << " up  " << formatBytes(s.uploadSpeed)
            << "/s  peers=" << s.peers << "\n";
}

int runHarness(const std::vector<std::string>& args) {
  std::signal(SIGINT, handleSignal);
  std::signal(SIGTERM, handleSignal);

  Config config = loadConfig();
  std::cout << "state dir:    " << paths::queueFile() << "\n";
  std::cout << "download dir: " << config.downloadDir << "\n";

  const bool safe = wasBootInterrupted();
  if (safe) {
    std::cout << "*** previous run did not shut down cleanly -- restoring in SAFE MODE (all paused) ***\n";
  }
  armBootMarker();

  DownloadQueue queue;
  queue.setTrackers(config.trackers);
  queue.restoreHistory(loadHistory());
  queue.restore(reconcileQueue(loadQueue()), RestoreOptions{safe});
  queue.restoreSeeds(loadSeeds(), RestoreOptions{safe});

  if (args.size() >= 2) {
    auto parsed = parseInput(args[1]);
    if (!parsed) {
      std::cerr << "not a magnet URI or info hash: " << args[1] << "\n";
      return 1;
    }
    const std::string dir = args.size() >= 3 ? args[2] : config.downloadDir;
    queue.add(AddInput{parsed->infoHash, parsed->name, parsed->magnet, "", std::nullopt}, dir);
    std::cout << "added " << parsed->name << " -> " << dir << "\n";
  }

  std::cout << "running. Ctrl+C to stop cleanly (kill -9 <pid> to test crash recovery instead).\n";

  const auto bootAt = std::chrono::steady_clock::now();
  bool settled = false;

  while (!gStop) {
    queue.tick();

    if (!settled && std::chrono::steady_clock::now() - bootAt > std::chrono::milliseconds(kBootSettleMs)) {
      // The boot survived long enough to be worth trusting.
      disarmBootMarker();
      settled = true;
    }

    auto items = queue.getItems();
    auto seeds = queue.getSeeds();
    if (!items.empty() || !seeds.empty()) {
      std::cout << "\033[2J\033[H";  // clear screen for a readable live view
      std::cout << "Downloads:\n";
      for (const auto& it : items) printItem(it);
      std::cout << "Seeding:\n";
      for (const auto& s : seeds) printSeed(s);
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  }

  std::cout << "\nshutting down cleanly\n";
  queue.suspend();
  return 0;
}

}  // namespace

int main(int argc, char** argv) {
  std::vector<std::string> args(argv + 1, argv + argc);
  if (args.empty()) {
    printUsage();
    return 1;
  }
  if (args[0] == "search") {
    if (args.size() < 2) {
      printUsage();
      return 1;
    }
    doSearch(args[1]);
    return 0;
  }
  if (args[0] == "run") return runHarness(args);

  printUsage();
  return 1;
}
