import { db } from "@/db/client";
import { corruptFiles } from "@/db/schema";
import { and, eq, inArray } from "drizzle-orm";
import { timingSafeEqual } from "node:crypto";

export const dynamic = "force-dynamic";

// Stage 2 and the execution half of stage 3.
//
//   POST                 agent reports files it could not read
//   GET  ?status=approved  agent asks what a human has approved for deletion
//   PATCH                agent confirms a deletion succeeded or failed
//
// Ampelos never deletes these itself: it mounts the archive read-only so a scan
// can never damage the backup, and the reporting agent already has write access
// to the tier it found the problem on.

function authorize(request: Request) {
  const expected = process.env.AMPELOS_AGENT_TOKEN;
  if (!expected) return { ok: false, status: 503, error: "AMPELOS_AGENT_TOKEN is not configured" };

  const header = request.headers.get("authorization") ?? "";
  const provided = header.startsWith("Bearer ") ? header.slice(7) : "";
  const a = Buffer.from(provided);
  const b = Buffer.from(expected);
  if (!provided || a.length !== b.length || !timingSafeEqual(a, b)) {
    return { ok: false, status: 401, error: "unauthorized" };
  }
  return { ok: true as const };
}

export async function POST(request: Request) {
  const auth = authorize(request);
  if (!auth.ok) return Response.json({ error: auth.error }, { status: auth.status });

  let body: unknown;
  try {
    body = await request.json();
  } catch {
    return Response.json({ error: "invalid JSON body" }, { status: 400 });
  }

  const payload = body as { agent?: unknown; files?: unknown } | null;
  const agent = typeof payload?.agent === "string" ? payload.agent.trim() : "";
  if (!agent) return Response.json({ error: "agent is required" }, { status: 400 });
  if (!Array.isArray(payload?.files)) return Response.json({ error: "files must be an array" }, { status: 400 });

  let recorded = 0;
  for (const entry of payload.files as Record<string, unknown>[]) {
    const fullPath = typeof entry?.fullPath === "string" ? entry.fullPath.trim() : "";
    const reason = typeof entry?.reason === "string" ? entry.reason.slice(0, 1000) : "";
    if (!fullPath || !reason) continue;

    const sizeBytes =
      typeof entry?.sizeBytes === "number" && Number.isFinite(entry.sizeBytes)
        ? BigInt(Math.round(entry.sizeBytes))
        : null;
    const tier = typeof entry?.tier === "string" ? entry.tier : null;

    await db
      .insert(corruptFiles)
      .values({ fullPath, agent, tier, reason, sizeBytes })
      .onConflictDoUpdate({
        target: corruptFiles.fullPath,
        // Refresh the evidence, but never resurrect something a human already
        // dismissed or that has been deleted.
        set: { reason, sizeBytes, agent, tier, lastDetectedAt: new Date() },
        where: eq(corruptFiles.status, "pending"),
      });
    recorded += 1;
  }

  return Response.json({ ok: true, recorded });
}

export async function GET(request: Request) {
  const auth = authorize(request);
  if (!auth.ok) return Response.json({ error: auth.error }, { status: auth.status });

  const url = new URL(request.url);
  const agent = url.searchParams.get("agent");
  const status = url.searchParams.get("status") ?? "approved";

  if (!["pending", "approved", "deleted", "dismissed", "failed"].includes(status)) {
    return Response.json({ error: "unknown status" }, { status: 400 });
  }

  const rows = await db
    .select({
      id: corruptFiles.id,
      fullPath: corruptFiles.fullPath,
      reason: corruptFiles.reason,
      sizeBytes: corruptFiles.sizeBytes,
      tier: corruptFiles.tier,
    })
    .from(corruptFiles)
    .where(
      agent
        ? and(eq(corruptFiles.status, status as "approved"), eq(corruptFiles.agent, agent))
        : eq(corruptFiles.status, status as "approved"),
    );

  return Response.json({
    files: rows.map((row) => ({ ...row, sizeBytes: row.sizeBytes === null ? null : Number(row.sizeBytes) })),
  });
}

export async function PATCH(request: Request) {
  const auth = authorize(request);
  if (!auth.ok) return Response.json({ error: auth.error }, { status: auth.status });

  let body: unknown;
  try {
    body = await request.json();
  } catch {
    return Response.json({ error: "invalid JSON body" }, { status: 400 });
  }

  const payload = body as { results?: unknown } | null;
  if (!Array.isArray(payload?.results)) return Response.json({ error: "results must be an array" }, { status: 400 });

  let updated = 0;
  for (const entry of payload.results as Record<string, unknown>[]) {
    const id = typeof entry?.id === "string" ? entry.id : "";
    const deleted = entry?.deleted === true;
    const error = typeof entry?.error === "string" ? entry.error.slice(0, 1000) : null;
    if (!id) continue;

    // Only an approved row may transition, so a stale agent cannot delete
    // something a human has since dismissed.
    const result = await db
      .update(corruptFiles)
      .set(
        deleted
          ? { status: "deleted", deletedAt: new Date(), deleteError: null }
          : { status: "failed", deleteError: error ?? "agent reported failure" },
      )
      .where(and(eq(corruptFiles.id, id), inArray(corruptFiles.status, ["approved", "failed"])))
      .returning({ id: corruptFiles.id });

    updated += result.length;
  }

  return Response.json({ ok: true, updated });
}
