import { GraphQLError } from "graphql";
import { builder } from "../builder";
import { CurrentUserType, UserType, AdminUserListType, AdminUserDetailType } from "../types/index";
import { users, user_photos, files } from "../../db/schema/index";
import { eq, ilike, or, count, and, asc, type SQL } from "drizzle-orm";
import { requireAdmin } from "../../lib/acl";

builder.queryField("me", (t) =>
  t.field({
    type: CurrentUserType,
    nullable: true,
    resolve: async (_root, _args, ctx) => {
      if (!ctx.currentUser) return null;
      const user = await ctx.db
        .select()
        .from(users)
        .where(eq(users.id, ctx.currentUser.id))
        .limit(1);
      return user[0] || null;
    },
  }),
);

builder.queryField("userProfile", (t) =>
  t.field({
    type: UserType,
    nullable: true,
    args: {
      id: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      const user = await ctx.db.select().from(users).where(eq(users.id, args.id)).limit(1);
      return user[0] || null;
    },
  }),
);

builder.mutationField("updateProfile", (t) =>
  t.field({
    type: CurrentUserType,
    nullable: true,
    args: {
      firstName: t.arg.string(),
      lastName: t.arg.string(),
      artistName: t.arg.string(),
      description: t.arg.string(),
      tags: t.arg.stringList(),
      avatar: t.arg.string(),
    },
    resolve: async (_root, args, ctx) => {
      if (!ctx.currentUser) throw new GraphQLError("Unauthorized");

      const updates: Record<string, unknown> = { date_updated: new Date() };
      if (args.firstName !== undefined && args.firstName !== null)
        updates.first_name = args.firstName;
      if (args.lastName !== undefined && args.lastName !== null) updates.last_name = args.lastName;
      if (args.artistName !== undefined && args.artistName !== null)
        updates.artist_name = args.artistName;
      if (args.description !== undefined && args.description !== null)
        updates.description = args.description;
      if (args.tags !== undefined && args.tags !== null) updates.tags = args.tags;
      if (args.avatar !== undefined) updates.avatar = args.avatar;

      await ctx.db
        .update(users)
        .set(updates as Partial<typeof users.$inferInsert>)
        .where(eq(users.id, ctx.currentUser.id));

      const updated = await ctx.db
        .select()
        .from(users)
        .where(eq(users.id, ctx.currentUser.id))
        .limit(1);
      return updated[0] || null;
    },
  }),
);

// ─── Admin queries & mutations ────────────────────────────────────────────────

builder.queryField("adminListUsers", (t) =>
  t.field({
    type: AdminUserListType,
    args: {
      role: t.arg.string(),
      search: 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.role) {
        conditions.push(eq(users.role, args.role as "model" | "viewer" | "admin"));
      }
      if (args.search) {
        const pattern = `%${args.search}%`;
        conditions.push(
          or(ilike(users.email, pattern), ilike(users.artist_name, pattern)) as SQL<unknown>,
        );
      }

      const where = conditions.length > 0 ? and(...conditions) : undefined;
      const [items, totalRows] = await Promise.all([
        ctx.db
          .select()
          .from(users)
          .where(where)
          .orderBy(asc(users.artist_name))
          .limit(limit)
          .offset(offset),
        ctx.db.select({ total: count() }).from(users).where(where),
      ]);

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

builder.mutationField("adminUpdateUser", (t) =>
  t.field({
    type: UserType,
    nullable: true,
    args: {
      userId: t.arg.string({ required: true }),
      role: t.arg.string(),
      isAdmin: t.arg.boolean(),
      firstName: t.arg.string(),
      lastName: t.arg.string(),
      artistName: t.arg.string(),
      avatarId: t.arg.string(),
      bannerId: t.arg.string(),
      photoId: t.arg.string(),
    },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);

      const updates: Record<string, unknown> = { date_updated: new Date() };
      if (args.role !== undefined && args.role !== null)
        updates.role = args.role as "model" | "viewer" | "admin";
      if (args.isAdmin !== undefined && args.isAdmin !== null) updates.is_admin = args.isAdmin;
      if (args.firstName !== undefined && args.firstName !== null)
        updates.first_name = args.firstName;
      if (args.lastName !== undefined && args.lastName !== null) updates.last_name = args.lastName;
      if (args.artistName !== undefined && args.artistName !== null)
        updates.artist_name = args.artistName;
      if (args.avatarId !== undefined && args.avatarId !== null) updates.avatar = args.avatarId;
      if (args.bannerId !== undefined && args.bannerId !== null) updates.banner = args.bannerId;
      if (args.photoId !== undefined && args.photoId !== null) updates.photo = args.photoId;

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

      return updated[0] || null;
    },
  }),
);

builder.mutationField("adminDeleteUser", (t) =>
  t.field({
    type: "Boolean",
    args: {
      userId: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      if (args.userId === ctx.currentUser!.id) throw new GraphQLError("Cannot delete yourself");
      await ctx.db.delete(users).where(eq(users.id, args.userId));
      return true;
    },
  }),
);

builder.queryField("adminGetUser", (t) =>
  t.field({
    type: AdminUserDetailType,
    nullable: true,
    args: {
      userId: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      const user = await ctx.db.select().from(users).where(eq(users.id, args.userId)).limit(1);
      if (!user[0]) return null;
      const photoRows = await ctx.db
        .select({ id: files.id, filename: files.filename })
        .from(user_photos)
        .leftJoin(files, eq(user_photos.file_id, files.id))
        .where(eq(user_photos.user_id, args.userId))
        .orderBy(user_photos.sort);
      const seen = new Set<string>();
      const photos = photoRows
        .filter((p) => p.id !== null && !seen.has(p.id!) && seen.add(p.id!))
        .map((p) => ({ id: p.id!, filename: p.filename! }));
      return { ...user[0], photos };
    },
  }),
);

builder.mutationField("adminAddUserPhoto", (t) =>
  t.field({
    type: "Boolean",
    args: {
      userId: t.arg.string({ required: true }),
      fileId: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      await ctx.db.insert(user_photos).values({ user_id: args.userId, file_id: args.fileId });
      return true;
    },
  }),
);

builder.mutationField("adminRemoveUserPhoto", (t) =>
  t.field({
    type: "Boolean",
    args: {
      userId: t.arg.string({ required: true }),
      fileId: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      requireAdmin(ctx);
      await ctx.db
        .delete(user_photos)
        .where(and(eq(user_photos.user_id, args.userId), eq(user_photos.file_id, args.fileId)));
      return true;
    },
  }),
);
