"use server";

// Server actions for the Downloads page.
//
// Every one of these re-checks the session. Server Actions are reachable by
// direct POST, not only through the buttons on the page, so the layout's admin
// guard is not sufficient protection on its own -- it only governs rendering.

import { auth } from "@/auth";
import { db } from "@/db/client";
import { grabs, releaseBlocklist } from "@/db/schema";
import { eq, sql } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { pauseTorrent, resumeTorrent, removeTorrent } from "@/lib/qbittorrent";

async function requireAdmin() {
  const session = await auth();
  if (!session?.user?.isAdmin) throw new Error("Unauthorized");
  return session;
}

/**
 * Only act on torrents Ampelos grabbed.
 *
 * The page can only render our own grabs, so a hash arriving here that has no
 * row is either a stale form or someone posting by hand. Either way it must not
 * reach qBittorrent: /Niflheim/Downloads also holds torrents the user added
 * themselves, and this endpoint is not going to be the thing that deletes one.
 */
async function requireOwnGrab(infoHash: string) {
  const [grab] = await db.select().from(grabs).where(eq(grabs.infoHash, infoHash)).limit(1);
  if (!grab) throw new Error("No grab recorded for that torrent");
  return grab;
}

export async function pauseAction(formData: FormData) {
  await requireAdmin();
  const infoHash = String(formData.get("infoHash") ?? "");
  await requireOwnGrab(infoHash);
  await pauseTorrent(infoHash);
  revalidatePath("/admin/downloads");
}

export async function resumeAction(formData: FormData) {
  await requireAdmin();
  const infoHash = String(formData.get("infoHash") ?? "");
  await requireOwnGrab(infoHash);
  await resumeTorrent(infoHash);
  revalidatePath("/admin/downloads");
}

/**
 * Stop seeding and drop the download-side copy.
 *
 * Safe after import: that copy is a hardlink, so deleting it leaves the library
 * entry pointing at the same inode. Before import it is the only copy, and the
 * page labels the button accordingly rather than relying on the user to
 * remember which state the grab is in.
 */
export async function removeAction(formData: FormData) {
  await requireAdmin();
  const infoHash = String(formData.get("infoHash") ?? "");
  const grab = await requireOwnGrab(infoHash);

  const imported = grab.status === "imported";
  await removeTorrent(infoHash, true);

  await db
    .update(grabs)
    .set({
      // An imported grab has done its job; the seed is simply released early.
      // A grab removed before import has been abandoned.
      status: imported ? "imported" : "failed",
      statusDetail: imported
        ? "seed released from the dashboard"
        : "removed from the dashboard before import",
      seedReleasedAt: sql`now() at time zone 'utc'`,
      seedReleaseReason: "manual",
      updatedAt: sql`now() at time zone 'utc'`,
    })
    .where(eq(grabs.id, grab.id));

  revalidatePath("/admin/downloads");
}

/**
 * Refuse this release from now on.
 *
 * Without it, the next auto search finds the same broken release, scores it the
 * same way, and grabs it again -- forever.
 */
export async function blocklistAction(formData: FormData) {
  await requireAdmin();
  const infoHash = String(formData.get("infoHash") ?? "");
  const grab = await requireOwnGrab(infoHash);

  await db
    .insert(releaseBlocklist)
    .values({
      infoHash,
      releaseTitle: grab.releaseTitle,
      mediaItemId: grab.mediaItemId,
      reason: "blocked from the dashboard",
    })
    .onConflictDoNothing();

  revalidatePath("/admin/downloads");
}

/**
 * Refuse an entire release group.
 *
 * Possible only because the organizer keeps the group legible in the filename.
 * A group that reliably ships desynced audio or mislabelled quality has to be
 * refusable wholesale, or every new release from it must be caught by hand.
 */
export async function blockGroupAction(formData: FormData) {
  await requireAdmin();
  const group = String(formData.get("group") ?? "").trim();
  if (!group) throw new Error("No release group to block");

  await db
    .insert(releaseBlocklist)
    .values({
      releaseGroup: group.toLowerCase(),
      releaseTitle: `group: ${group}`,
      reason: "group blocked from the dashboard",
      permanent: true,
    })
    .onConflictDoNothing();

  revalidatePath("/admin/downloads");
}
