import {
  mutation,
  query,
  internalMutation,
  internalQuery,
} from "./_generated/server";
import { paginationOptsValidator } from "convex/server";
import { v } from "convex/values";
import * as Users from "./model/users";

export const store = mutation({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Unauthenticated");

    const existing = await ctx.db
      .query("users")
      .withIndex("by_external_id", (q) =>
        q.eq("externalId", identity.subject),
      )
      .unique();

    if (existing) {
      if (existing.name !== identity.name) {
        await ctx.db.patch(existing._id, {
          name: identity.name ?? existing.name,
        });
      }
      return existing._id;
    }

    const metadataRole = (identity as Record<string, unknown> & { public_metadata?: { role?: unknown } }).public_metadata?.role;
    const role =
      metadataRole === "admin" || metadataRole === "super_admin"
        ? metadataRole
        : "customer";

    return await ctx.db.insert("users", {
      name: identity.name ?? "Anonymous",
      email: identity.email ?? "",
      role,
      externalId: identity.subject,
      avatarUrl: identity.pictureUrl ?? undefined,
    });
  },
});

export const current = query({
  args: {},
  handler: async (ctx) => Users.getCurrentUser(ctx),
});

export const updateProfile = mutation({
  args: {
    name: v.optional(v.string()),
    phone: v.optional(v.string()),
    avatarUrl: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUserOrThrow(ctx);
    const patch: { name?: string; phone?: string; avatarUrl?: string } = {};
    if (args.name !== undefined) patch.name = args.name;
    if (args.phone !== undefined) patch.phone = args.phone;
    if (args.avatarUrl !== undefined) patch.avatarUrl = args.avatarUrl;
    if (Object.keys(patch).length === 0) return user._id;
    await ctx.db.patch(user._id, patch);
    return user._id;
  },
});

export const listCustomers = query({
  args: {
    paginationOpts: paginationOptsValidator,
  },
  handler: async (ctx, args) => {
    await Users.requireAdmin(ctx);
    return await ctx.db
      .query("users")
      .withIndex("by_role", (q) => q.eq("role", "customer"))
      .order("desc")
      .paginate(args.paginationOpts);
  },
});

export const upsertFromClerk = internalMutation({
  args: {
    externalId: v.string(),
    name: v.string(),
    email: v.string(),
    avatarUrl: v.optional(v.string()),
    role: v.optional(v.union(v.literal("admin"), v.literal("super_admin"))),
  },
  handler: async (ctx, args) => {
    const existing = await ctx.db
      .query("users")
      .withIndex("by_external_id", (q) =>
        q.eq("externalId", args.externalId),
      )
      .unique();

    if (existing) {
      const patch: Record<string, string | undefined> = {
        name: args.name,
        email: args.email,
        avatarUrl: args.avatarUrl,
      };
      if (args.role) patch.role = args.role;
      await ctx.db.patch(existing._id, patch);
    } else {
      await ctx.db.insert("users", {
        externalId: args.externalId,
        name: args.name,
        email: args.email,
        avatarUrl: args.avatarUrl,
        role: args.role ?? "customer",
      });
    }
  },
});

export const deleteFromClerk = internalMutation({
  args: { externalId: v.string() },
  handler: async (ctx, { externalId }) => {
    const user = await ctx.db
      .query("users")
      .withIndex("by_external_id", (q) => q.eq("externalId", externalId))
      .unique();
    if (user) await ctx.db.delete(user._id);
  },
});

export const assertSuperAdmin = internalQuery({
  args: {},
  handler: async (ctx) => {
    return await Users.requireSuperAdmin(ctx);
  },
});

export const getById = internalQuery({
  args: { userId: v.id("users") },
  handler: async (ctx, args) => {
    const user = await ctx.db.get(args.userId);
    if (!user) throw new Error("User not found");
    return user;
  },
});

export const setStripeCustomerId = internalMutation({
  args: {
    userId: v.id("users"),
    stripeCustomerId: v.string(),
  },
  handler: async (ctx, args) => {
    await ctx.db.patch(args.userId, {
      stripeCustomerId: args.stripeCustomerId,
    });
  },
});