import { GraphQLError } from "graphql";
import type { Job } from "bullmq";
import { builder } from "../builder.js";
import { JobType, QueueInfoType } from "../types/index.js";
import { queues } from "../../queues/index.js";
import { requireAdmin } from "../../lib/acl.js";

const JOB_STATUSES = ["waiting", "active", "completed", "failed", "delayed"] as const;
type JobStatus = (typeof JOB_STATUSES)[number];

async function toJobData(job: Job, queueName: string) {
  const status = await job.getState();
  return {
    id: job.id ?? "",
    name: job.name,
    queue: queueName,
    status,
    data: job.data as unknown,
    result: job.returnvalue as unknown,
    failedReason: job.failedReason ?? null,
    attemptsMade: job.attemptsMade,
    createdAt: new Date(job.timestamp),
    processedAt: job.processedOn ? new Date(job.processedOn) : null,
    finishedAt: job.finishedOn ? new Date(job.finishedOn) : null,
    progress: typeof job.progress === "number" ? job.progress : null,
  };
}

builder.queryField("adminQueues", (t) =>
  t.field({
    type: [QueueInfoType],
    resolve: async (_root, _args, ctx) => {
      requireAdmin(ctx);
      return Promise.all(
        Object.entries(queues).map(async ([name, queue]) => {
          const counts = await queue.getJobCounts(
            "waiting",
            "active",
            "completed",
            "failed",
            "delayed",
            "paused",
          );
          const isPaused = await queue.isPaused();
          return {
            name,
            counts: {
              waiting: counts.waiting ?? 0,
              active: counts.active ?? 0,
              completed: counts.completed ?? 0,
              failed: counts.failed ?? 0,
              delayed: counts.delayed ?? 0,
              paused: counts.paused ?? 0,
            },
            isPaused,
          };
        }),
      );
    },
  }),
);

builder.queryField("adminQueueJobs", (t) =>
  t.field({
    type: [JobType],
    args: {
      queue: t.arg.string({ required: true }),
      status: t.arg.string(),
      limit: t.arg.int(),
      offset: t.arg.int(),
    },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      const queue = queues[args.queue];
      if (!queue) throw new GraphQLError(`Queue "${args.queue}" not found`);

      const limit = args.limit ?? 25;
      const offset = args.offset ?? 0;
      const statuses: JobStatus[] = args.status ? [args.status as JobStatus] : [...JOB_STATUSES];

      const jobs = await queue.getJobs(statuses, offset, offset + limit - 1);
      return Promise.all(jobs.map((job) => toJobData(job, args.queue)));
    },
  }),
);

builder.mutationField("adminRetryJob", (t) =>
  t.field({
    type: "Boolean",
    args: {
      queue: t.arg.string({ required: true }),
      jobId: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      const queue = queues[args.queue];
      if (!queue) throw new GraphQLError(`Queue "${args.queue}" not found`);
      const job = await queue.getJob(args.jobId);
      if (!job) throw new GraphQLError(`Job "${args.jobId}" not found`);
      await job.retry();
      return true;
    },
  }),
);

builder.mutationField("adminRemoveJob", (t) =>
  t.field({
    type: "Boolean",
    args: {
      queue: t.arg.string({ required: true }),
      jobId: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      const queue = queues[args.queue];
      if (!queue) throw new GraphQLError(`Queue "${args.queue}" not found`);
      const job = await queue.getJob(args.jobId);
      if (!job) throw new GraphQLError(`Job "${args.jobId}" not found`);
      await job.remove();
      return true;
    },
  }),
);

builder.mutationField("adminPauseQueue", (t) =>
  t.field({
    type: "Boolean",
    args: { queue: t.arg.string({ required: true }) },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      const queue = queues[args.queue];
      if (!queue) throw new GraphQLError(`Queue "${args.queue}" not found`);
      await queue.pause();
      return true;
    },
  }),
);

builder.mutationField("adminResumeQueue", (t) =>
  t.field({
    type: "Boolean",
    args: { queue: t.arg.string({ required: true }) },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      const queue = queues[args.queue];
      if (!queue) throw new GraphQLError(`Queue "${args.queue}" not found`);
      await queue.resume();
      return true;
    },
  }),
);
