"use server";

import { revalidatePath } from "next/cache";
import { eq, inArray, sql } from "drizzle-orm";
import { z } from "zod";
import { db } from "@/lib/db/client";
import { users } from "@/lib/db/schema";
import { requireAdmin } from "@/lib/auth/admin-guard";
import { logAdminAction } from "@/lib/audit";
import { issueResetToken } from "@/lib/auth/reset-token";
import { sendTemplatedEmail } from "@/lib/email/send";
import { loadEnv } from "@/lib/env";

async function ensureAdmin() {
  const session = await requireAdmin();
  return (session.user as { id?: string }).id!;
}

const roleValues = ["user", "admin"] as const;
const statusValues = ["pending", "active", "rejected", "suspended"] as const;

const setRoleSchema = z.object({
  id: z.string().uuid(),
  role: z.enum(roleValues),
});

export async function setMemberRole(formData: FormData) {
  const adminId = await ensureAdmin();
  const parsed = setRoleSchema.parse({
    id: formData.get("id"),
    role: formData.get("role"),
  });

  if (parsed.id === adminId && parsed.role !== "admin") {
    throw new Error("You cannot demote yourself.");
  }

  await db
    .update(users)
    .set({ role: parsed.role })
    .where(eq(users.id, parsed.id));

  await logAdminAction({
    adminUserId: adminId,
    action: `member_role_${parsed.role}`,
    targetId: parsed.id,
  });

  revalidatePath("/admin/members");
}

const setStatusSchema = z.object({
  id: z.string().uuid(),
  status: z.enum(statusValues),
});

export async function setMemberStatus(formData: FormData) {
  const adminId = await ensureAdmin();
  const parsed = setStatusSchema.parse({
    id: formData.get("id"),
    status: formData.get("status"),
  });

  if (parsed.id === adminId && parsed.status !== "active") {
    throw new Error("You cannot change your own status.");
  }

  // Suspending, rejecting, or reverting to pending should kick the user out
  // of every open session immediately.
  const revokeSessions =
    parsed.status !== "active" ? { tokenVersion: sql`${users.tokenVersion} + 1` } : {};
  await db
    .update(users)
    .set({
      status: parsed.status,
      approvedAt: parsed.status === "active" ? new Date() : null,
      approvedBy: parsed.status === "active" ? adminId : null,
      ...revokeSessions,
    })
    .where(eq(users.id, parsed.id));

  await logAdminAction({
    adminUserId: adminId,
    action: `member_status_${parsed.status}`,
    targetId: parsed.id,
  });

  revalidatePath("/admin/members");
}

const updateProfileSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(2).max(200),
  country: z.string().min(2).max(200),
  institution: z.string().min(2).max(300),
  professionalRole: z.string().min(2).max(200),
});

export async function updateMemberProfile(formData: FormData) {
  const adminId = await ensureAdmin();
  const parsed = updateProfileSchema.parse({
    id: formData.get("id"),
    name: formData.get("name") ?? "",
    country: formData.get("country") ?? "",
    institution: formData.get("institution") ?? "",
    professionalRole: formData.get("professionalRole") ?? "",
  });

  await db
    .update(users)
    .set({
      name: parsed.name,
      country: parsed.country,
      institution: parsed.institution,
      professionalRole: parsed.professionalRole,
    })
    .where(eq(users.id, parsed.id));

  await logAdminAction({
    adminUserId: adminId,
    action: "member_profile_update",
    targetId: parsed.id,
  });

  revalidatePath("/admin/members");
}

const resetPwSchema = z.object({ id: z.string().uuid() });

export async function issueMemberPasswordReset(formData: FormData) {
  const adminId = await ensureAdmin();
  const parsed = resetPwSchema.parse({ id: formData.get("id") });

  const [row] = await db
    .select({
      id: users.id,
      email: users.email,
      name: users.name,
      status: users.status,
    })
    .from(users)
    .where(eq(users.id, parsed.id))
    .limit(1);
  if (!row) throw new Error("User not found");
  // Admins can issue resets for any non-archived user — useful for applicants
  // who applied with a typo or can't recall their password before approval.

  const { token, expiresAt } = await issueResetToken(row.id, {
    issuedBy: "admin",
    ttlMinutes: 120,
  });
  const env = loadEnv();
  const resetUrl = `${env.nextAuthUrl}/reset-password/${token}`;

  const adminRecord = await db
    .select({ name: users.name })
    .from(users)
    .where(eq(users.id, adminId))
    .limit(1);

  try {
    await sendTemplatedEmail({
      to: row.email,
      templateSlug: "password-reset-admin",
      variables: {
        name: row.name,
        resetUrl,
        adminName: adminRecord[0]?.name ?? "an administrator",
        expiresInMinutes: Math.round((expiresAt.getTime() - Date.now()) / 60_000),
      },
    });
  } catch (err) {
    console.error("[admin] password-reset email failed", err);
  }

  await logAdminAction({
    adminUserId: adminId,
    action: "member_password_reset_issued",
    targetId: row.id,
  });
  revalidatePath("/admin/members");
}

const bulkStatusSchema = z.object({
  ids: z.array(z.string().uuid()).min(1),
  status: z.enum(["pending", "active", "rejected", "suspended"]),
});

export async function bulkSetMemberStatus(formData: FormData) {
  const adminId = await ensureAdmin();
  const idsRaw = formData.getAll("ids").map((v) => String(v));
  const parsed = bulkStatusSchema.parse({
    ids: idsRaw.filter((s) => s.length > 0),
    status: formData.get("status"),
  });
  const filtered = parsed.ids.filter((id) => id !== adminId);
  if (filtered.length === 0) {
    throw new Error("You cannot bulk-change your own status.");
  }
  await db
    .update(users)
    .set({
      status: parsed.status,
      approvedAt: parsed.status === "active" ? new Date() : null,
      approvedBy: parsed.status === "active" ? adminId : null,
    })
    .where(inArray(users.id, filtered));
  await logAdminAction({
    adminUserId: adminId,
    action: `member_bulk_status_${parsed.status}`,
    metadata: { count: filtered.length },
  });
  revalidatePath("/admin/members");
}
