import { NextResponse } from "next/server";
import { headers } from "next/headers";
import { auth } from "@/lib/auth/server";
import { db, users, eq } from "@epicure/db";
import { z } from "zod";
import { gravatarUrl } from "@/lib/gravatar";
import { USERNAME_PATTERN, isUsernameTaken } from "@/lib/username";
import { isOwnedAvatarKey, getPublicUrl } from "@/lib/storage";
import { checkTierLimitInTransaction, TierLimitError } from "@/lib/tiers";

const PatchSchema = z.object({
  name: z.string().min(1).max(100).optional(),
  locale: z.string().max(10).optional(),
  bio: z.string().max(500).optional().nullable(),
  privateBio: z.string().max(2000).optional().nullable(),
  isPrivate: z.boolean().optional(),
  username: z.string().trim().toLowerCase().regex(USERNAME_PATTERN, "3-20 characters, lowercase letters, numbers, and underscores only").optional(),
  // The storage key returned by /api/v1/upload/avatar-presign (not a URL) —
  // the server validates it was actually issued to this user and builds the
  // public URL itself, so a client can't point avatarUrl at an arbitrary or
  // another user's object. `null` reverts to the initials fallback (or
  // Gravatar, if useGravatar is on — see below).
  avatarKey: z.string().max(500).optional().nullable(),
  // Size (MB, rounded up) of the file behind avatarKey — as returned by
  // /api/v1/upload/avatar-presign. Ignored unless avatarKey is set.
  avatarSizeMb: z.number().int().min(0).max(50).default(0),
  // Off by default (Settings → Profile) — Gravatar is looked up by an MD5
  // hash of the user's email, sent to a third party.
  useGravatar: z.boolean().optional(),
});

export async function PATCH(req: Request) {
  const session = await auth.api.getSession({ headers: await headers() });
  if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const body = PatchSchema.safeParse(await req.json());
  if (!body.success) return NextResponse.json({ error: body.error.flatten() }, { status: 400 });

  if (body.data.username !== undefined && (await isUsernameTaken(body.data.username, session.user.id))) {
    return NextResponse.json({ error: "Username already taken" }, { status: 409 });
  }

  const { avatarKey, avatarSizeMb, useGravatar, ...rest } = body.data;
  const updates: Partial<typeof users.$inferInsert> = { ...rest };

  // useGravatar is only ever toggled from the settings form, never alongside
  // an avatar upload/removal in the same request — so it's fine for these
  // two branches to each independently decide the resulting avatarUrl below.
  if (useGravatar !== undefined) {
    updates.useGravatar = useGravatar;
    if (!(await hasCustomAvatar(session.user.id))) {
      updates.avatarUrl = useGravatar ? gravatarUrl(session.user.email) : null;
    }
  }

  let avatarSizeDeltaMb = 0;
  if (avatarKey !== undefined) {
    if (avatarKey === null) {
      const gravatarOptedIn = useGravatar ?? (await getUseGravatar(session.user.id));
      updates.avatarUrl = gravatarOptedIn ? gravatarUrl(session.user.email) : null;
      updates.hasCustomAvatar = false;
      updates.avatarSizeMb = 0;
    } else {
      if (!isOwnedAvatarKey(avatarKey, session.user.id)) {
        return NextResponse.json({ error: "Invalid avatar key" }, { status: 400 });
      }
      const currentAvatarSizeMb = await getAvatarSizeMb(session.user.id);
      avatarSizeDeltaMb = avatarSizeMb - currentAvatarSizeMb;
      updates.avatarUrl = getPublicUrl(avatarKey);
      updates.hasCustomAvatar = true;
      updates.avatarSizeMb = avatarSizeMb;
    }
  }

  try {
    await db.transaction(async (tx) => {
      if (avatarSizeDeltaMb > 0) {
        await checkTierLimitInTransaction(tx, session.user.id, session.user.tier as "free" | "pro" | "family", "storage", avatarSizeDeltaMb);
      }
      await tx.update(users).set(updates).where(eq(users.id, session.user.id));
    });
  } catch (err) {
    if (err instanceof TierLimitError) {
      return NextResponse.json({ error: "Storage limit reached for your tier" }, { status: 403 });
    }
    throw err;
  }

  return NextResponse.json({ ok: true, avatarUrl: updates.avatarUrl });
}

async function hasCustomAvatar(userId: string): Promise<boolean> {
  const row = await db.query.users.findFirst({ where: eq(users.id, userId), columns: { hasCustomAvatar: true } });
  return row?.hasCustomAvatar ?? false;
}

async function getUseGravatar(userId: string): Promise<boolean> {
  const row = await db.query.users.findFirst({ where: eq(users.id, userId), columns: { useGravatar: true } });
  return row?.useGravatar ?? false;
}

async function getAvatarSizeMb(userId: string): Promise<number> {
  const row = await db.query.users.findFirst({ where: eq(users.id, userId), columns: { avatarSizeMb: true } });
  return row?.avatarSizeMb ?? 0;
}
