import { GraphQLError } from "graphql";
import { builder } from "../builder";
import { RecordingType, AdminRecordingListType } from "../types/index";
import { recordings, recording_plays } from "../../db/schema/index";
import { eq, and, desc, ilike, count, type SQL } from "drizzle-orm";
import { slugify } from "../../lib/slugify";
import { requireAdmin } from "../../lib/acl";
import { gamificationQueue } from "../../queues/index";

builder.queryField("recordings", (t) =>
  t.field({
    type: [RecordingType],
    args: {
      status: t.arg.string(),
      tags: t.arg.string(),
      linkedVideoId: t.arg.string(),
      limit: t.arg.int(),
      page: t.arg.int(),
    },
    resolve: async (_root, args, ctx) => {
      if (!ctx.currentUser) throw new GraphQLError("Unauthorized");

      const conditions = [eq(recordings.user_id, ctx.currentUser.id)];
      if (args.status) conditions.push(eq(recordings.status, args.status as "draft" | "published"));
      if (args.linkedVideoId) conditions.push(eq(recordings.linked_video, args.linkedVideoId));

      const limit = args.limit || 50;
      const page = args.page || 1;
      const offset = (page - 1) * limit;

      return ctx.db
        .select()
        .from(recordings)
        .where(and(...conditions))
        .orderBy(desc(recordings.date_created))
        .limit(limit)
        .offset(offset);
    },
  }),
);

builder.queryField("recording", (t) =>
  t.field({
    type: RecordingType,
    nullable: true,
    args: {
      id: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      if (!ctx.currentUser) throw new GraphQLError("Unauthorized");

      const recording = await ctx.db
        .select()
        .from(recordings)
        .where(eq(recordings.id, args.id))
        .limit(1);

      if (!recording[0]) return null;

      if (recording[0].user_id !== ctx.currentUser.id && !recording[0].public) {
        throw new GraphQLError("Forbidden");
      }

      return recording[0];
    },
  }),
);

builder.queryField("communityRecordings", (t) =>
  t.field({
    type: [RecordingType],
    args: {
      limit: t.arg.int(),
      offset: t.arg.int(),
    },
    resolve: async (_root, args, ctx) => {
      return ctx.db
        .select()
        .from(recordings)
        .where(and(eq(recordings.status, "published"), eq(recordings.public, true)))
        .orderBy(desc(recordings.date_created))
        .limit(args.limit || 50)
        .offset(args.offset || 0);
    },
  }),
);

builder.mutationField("createRecording", (t) =>
  t.field({
    type: RecordingType,
    args: {
      title: t.arg.string({ required: true }),
      description: t.arg.string(),
      duration: t.arg.int({ required: true }),
      events: t.arg({ type: "JSON", required: true }),
      deviceInfo: t.arg({ type: "JSON", required: true }),
      tags: t.arg.stringList(),
      status: t.arg.string(),
      linkedVideoId: t.arg.string(),
    },
    resolve: async (_root, args, ctx) => {
      if (!ctx.currentUser) throw new GraphQLError("Unauthorized");

      const slug = slugify(args.title);

      const newRecording = await ctx.db
        .insert(recordings)
        .values({
          title: args.title,
          description: args.description || null,
          slug,
          duration: args.duration,
          events: (args.events as object[]) || [],
          device_info: (args.deviceInfo as object[]) || [],
          user_id: ctx.currentUser.id,
          tags: args.tags || [],
          linked_video: args.linkedVideoId || null,
          status: (args.status as "draft" | "published") || "draft",
          public: false,
        })
        .returning();

      const recording = newRecording[0];

      if (recording.status === "published") {
        await gamificationQueue.add("awardPoints", {
          job: "awardPoints",
          userId: ctx.currentUser.id,
          action: "RECORDING_CREATE",
          recordingId: recording.id,
        });
        await gamificationQueue.add("checkAchievements", {
          job: "checkAchievements",
          userId: ctx.currentUser.id,
          category: "recordings",
        });
      }

      return recording;
    },
  }),
);

builder.mutationField("updateRecording", (t) =>
  t.field({
    type: RecordingType,
    nullable: true,
    args: {
      id: t.arg.string({ required: true }),
      title: t.arg.string(),
      description: t.arg.string(),
      tags: t.arg.stringList(),
      status: t.arg.string(),
      public: t.arg.boolean(),
      linkedVideoId: t.arg.string(),
    },
    resolve: async (_root, args, ctx) => {
      if (!ctx.currentUser) throw new GraphQLError("Unauthorized");

      const existing = await ctx.db
        .select()
        .from(recordings)
        .where(eq(recordings.id, args.id))
        .limit(1);

      if (!existing[0]) throw new GraphQLError("Recording not found");
      if (existing[0].user_id !== ctx.currentUser.id) throw new GraphQLError("Forbidden");

      const updates: Record<string, unknown> = { date_updated: new Date() };
      if (args.title !== null && args.title !== undefined) {
        updates.title = args.title;
        updates.slug = slugify(args.title);
      }
      if (args.description !== null && args.description !== undefined)
        updates.description = args.description;
      if (args.tags !== null && args.tags !== undefined) updates.tags = args.tags;
      if (args.status !== null && args.status !== undefined) updates.status = args.status;
      if (args.public !== null && args.public !== undefined) updates.public = args.public;
      if (args.linkedVideoId !== null && args.linkedVideoId !== undefined)
        updates.linked_video = args.linkedVideoId;

      const updated = await ctx.db
        .update(recordings)
        .set(updates as Partial<typeof recordings.$inferInsert>)
        .where(eq(recordings.id, args.id))
        .returning();

      const recording = updated[0];

      if (args.status === "published" && existing[0].status !== "published") {
        // draft → published: award creation points
        await gamificationQueue.add("awardPoints", {
          job: "awardPoints",
          userId: ctx.currentUser.id,
          action: "RECORDING_CREATE",
          recordingId: recording.id,
        });
        await gamificationQueue.add("checkAchievements", {
          job: "checkAchievements",
          userId: ctx.currentUser.id,
          category: "recordings",
        });
      } else if (args.status === "draft" && existing[0].status === "published") {
        // published → draft: revoke creation points
        await gamificationQueue.add("revokePoints", {
          job: "revokePoints",
          userId: ctx.currentUser.id,
          action: "RECORDING_CREATE",
          recordingId: recording.id,
        });
        await gamificationQueue.add("checkAchievements", {
          job: "checkAchievements",
          userId: ctx.currentUser.id,
          category: "recordings",
        });
      } else if (args.status === "published" && recording.featured && !existing[0].featured) {
        // newly featured while published: award featured bonus
        await gamificationQueue.add("awardPoints", {
          job: "awardPoints",
          userId: ctx.currentUser.id,
          action: "RECORDING_FEATURED",
          recordingId: recording.id,
        });
        await gamificationQueue.add("checkAchievements", {
          job: "checkAchievements",
          userId: ctx.currentUser.id,
          category: "recordings",
        });
      }

      return recording;
    },
  }),
);

builder.mutationField("deleteRecording", (t) =>
  t.field({
    type: "Boolean",
    args: {
      id: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      if (!ctx.currentUser) throw new GraphQLError("Unauthorized");

      const existing = await ctx.db
        .select()
        .from(recordings)
        .where(eq(recordings.id, args.id))
        .limit(1);

      if (!existing[0]) throw new GraphQLError("Recording not found");
      if (existing[0].user_id !== ctx.currentUser.id) throw new GraphQLError("Forbidden");

      if (existing[0].status === "published") {
        await gamificationQueue.add("revokePoints", {
          job: "revokePoints",
          userId: ctx.currentUser.id,
          action: "RECORDING_CREATE",
          recordingId: args.id,
        });
        if (existing[0].featured) {
          await gamificationQueue.add("revokePoints", {
            job: "revokePoints",
            userId: ctx.currentUser.id,
            action: "RECORDING_FEATURED",
            recordingId: args.id,
          });
        }
        await gamificationQueue.add("checkAchievements", {
          job: "checkAchievements",
          userId: ctx.currentUser.id,
          category: "content",
        });
      }

      await ctx.db.delete(recordings).where(eq(recordings.id, args.id));

      return true;
    },
  }),
);

builder.mutationField("duplicateRecording", (t) =>
  t.field({
    type: RecordingType,
    args: {
      id: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      if (!ctx.currentUser) throw new GraphQLError("Unauthorized");

      const original = await ctx.db
        .select()
        .from(recordings)
        .where(eq(recordings.id, args.id))
        .limit(1);

      if (!original[0]) throw new GraphQLError("Recording not found");
      if (original[0].status !== "published" || !original[0].public) {
        throw new GraphQLError("Recording is not publicly shared");
      }

      const slug = `${slugify(original[0].title)}-copy-${Date.now()}`;

      const duplicated = await ctx.db
        .insert(recordings)
        .values({
          title: `${original[0].title} (Copy)`,
          description: original[0].description,
          slug,
          duration: original[0].duration,
          events: original[0].events || [],
          device_info: original[0].device_info || [],
          user_id: ctx.currentUser.id,
          tags: original[0].tags || [],
          status: "draft",
          public: false,
          original_recording_id: original[0].id,
        })
        .returning();

      return duplicated[0];
    },
  }),
);

builder.mutationField("recordRecordingPlay", (t) =>
  t.field({
    type: "JSON",
    args: {
      recordingId: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      const recording = await ctx.db
        .select()
        .from(recordings)
        .where(eq(recordings.id, args.recordingId))
        .limit(1);

      if (!recording[0]) throw new GraphQLError("Recording not found");

      const play = await ctx.db
        .insert(recording_plays)
        .values({
          recording_id: args.recordingId,
          user_id: ctx.currentUser?.id || null,
          duration_played: 0,
          completed: false,
        })
        .returning({ id: recording_plays.id });

      if (ctx.currentUser && recording[0].user_id !== ctx.currentUser.id) {
        await gamificationQueue.add("awardPoints", {
          job: "awardPoints",
          userId: ctx.currentUser.id,
          action: "RECORDING_PLAY",
          recordingId: args.recordingId,
        });
        await gamificationQueue.add("checkAchievements", {
          job: "checkAchievements",
          userId: ctx.currentUser.id,
          category: "playback",
        });
      }

      return { success: true, play_id: play[0].id };
    },
  }),
);

builder.mutationField("updateRecordingPlay", (t) =>
  t.field({
    type: "Boolean",
    args: {
      playId: t.arg.string({ required: true }),
      durationPlayed: t.arg.int({ required: true }),
      completed: t.arg.boolean({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      const existing = await ctx.db
        .select()
        .from(recording_plays)
        .where(eq(recording_plays.id, args.playId))
        .limit(1);

      if (!existing[0]) throw new GraphQLError("Play record not found");
      const wasCompleted = existing[0].completed;

      await ctx.db
        .update(recording_plays)
        .set({
          duration_played: args.durationPlayed,
          completed: args.completed,
          date_updated: new Date(),
        })
        .where(eq(recording_plays.id, args.playId));

      if (args.completed && !wasCompleted && ctx.currentUser) {
        await gamificationQueue.add("awardPoints", {
          job: "awardPoints",
          userId: ctx.currentUser.id,
          action: "RECORDING_COMPLETE",
          recordingId: existing[0].recording_id,
        });
        await gamificationQueue.add("checkAchievements", {
          job: "checkAchievements",
          userId: ctx.currentUser.id,
          category: "playback",
        });
      }

      return true;
    },
  }),
);

builder.queryField("adminListRecordings", (t) =>
  t.field({
    type: AdminRecordingListType,
    args: {
      search: t.arg.string(),
      status: t.arg.string(),
      limit: t.arg.int(),
      offset: t.arg.int(),
    },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      const limit = args.limit ?? 50;
      const offset = args.offset ?? 0;

      const conditions: SQL<unknown>[] = [];
      if (args.search) conditions.push(ilike(recordings.title, `%${args.search}%`));
      if (args.status) conditions.push(eq(recordings.status, args.status as "draft" | "published"));
      const where = conditions.length > 0 ? and(...conditions) : undefined;

      const [rows, totalRows] = await Promise.all([
        ctx.db
          .select()
          .from(recordings)
          .where(where)
          .orderBy(desc(recordings.date_created))
          .limit(limit)
          .offset(offset),
        ctx.db.select({ total: count() }).from(recordings).where(where),
      ]);

      return { items: rows, total: totalRows[0]?.total ?? 0 };
    },
  }),
);

builder.mutationField("adminDeleteRecording", (t) =>
  t.field({
    type: "Boolean",
    args: {
      id: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      await ctx.db.delete(recordings).where(eq(recordings.id, args.id));
      return true;
    },
  }),
);
