import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
import { requireSupabaseAuth } from "@/integrations/supabase/auth-middleware";

const ASSIGNABLE = [
  "super_admin",
  "company_admin",
  "ops_manager",
  "security_officer",
  "supervisor",
  "guard",
] as const;
type Role = (typeof ASSIGNABLE)[number];

async function assertAdmin(userId: string) {
  const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
  const { data, error } = await supabaseAdmin
    .from("user_roles")
    .select("role")
    .eq("user_id", userId);
  if (error) throw new Error(error.message);
  const roles = (data ?? []).map((r) => r.role);
  if (!roles.some((r) => r === "super_admin" || r === "admin" || r === "company_admin")) {
    throw new Error("Forbidden: admin role required");
  }
}

export const listUsersWithRoles = createServerFn({ method: "GET" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    await assertAdmin(context.userId);
    const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
    const [{ data: profiles, error: pErr }, { data: roles, error: rErr }] = await Promise.all([
      supabaseAdmin.from("profiles").select("id, full_name, phone"),
      supabaseAdmin.from("user_roles").select("user_id, role"),
    ]);
    if (pErr) throw new Error(pErr.message);
    if (rErr) throw new Error(rErr.message);
    const profMap = new Map(
      (profiles ?? []).map((p) => [p.id, { full_name: p.full_name, phone: p.phone }]),
    );
    const byUser = new Map<string, string[]>();
    for (const r of roles ?? []) {
      const arr = byUser.get(r.user_id) ?? [];
      arr.push(r.role);
      byUser.set(r.user_id, arr);
    }
    // Use auth.admin as the source of truth for users (includes those without profiles).
    const users: Array<{
      id: string;
      email: string;
      full_name: string | null;
      phone: string | null;
      created_at: string | null;
      roles: string[];
    }> = [];
    let page = 1;
    while (page <= 20) {
      const { data, error } = await supabaseAdmin.auth.admin.listUsers({ page, perPage: 200 });
      if (error) throw new Error(error.message);
      for (const u of data.users) {
        const p = profMap.get(u.id);
        users.push({
          id: u.id,
          email: u.email ?? "",
          full_name:
            p?.full_name ??
            (u.user_metadata?.full_name as string | undefined) ??
            null,
          phone: p?.phone ?? null,
          created_at: u.created_at ?? null,
          roles: byUser.get(u.id) ?? [],
        });
      }
      if (data.users.length < 200) break;
      page++;
    }
    users.sort((a, b) => (a.full_name ?? a.email).localeCompare(b.full_name ?? b.email));
    return users;
  });

const mutSchema = z.object({
  user_id: z.string().uuid(),
  role: z.enum(ASSIGNABLE),
});

export const grantRole = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((d) => mutSchema.parse(d))
  .handler(async ({ data, context }) => {
    await assertAdmin(context.userId);
    const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
    const { error } = await supabaseAdmin
      .from("user_roles")
      .upsert(
        { user_id: data.user_id, role: data.role as Role },
        { onConflict: "user_id,role", ignoreDuplicates: true },
      );
    if (error) throw new Error(error.message);
    return { ok: true };
  });

export const revokeRole = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((d) => mutSchema.parse(d))
  .handler(async ({ data, context }) => {
    await assertAdmin(context.userId);
    if (data.user_id === context.userId && data.role === "super_admin") {
      throw new Error("You cannot revoke your own super_admin role");
    }
    const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
    const { error } = await supabaseAdmin
      .from("user_roles")
      .delete()
      .eq("user_id", data.user_id)
      .eq("role", data.role as Role);
    if (error) throw new Error(error.message);
    return { ok: true };
  });