import { NextResponse } from "next/server";
import { z } from "zod";
import { logCrudAccess, makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { Role, RoleAcl, UserRole } from "@open-mercato/core/modules/auth/data/entities";
import { Tenant } from "@open-mercato/core/modules/directory/data/entities";
import { E } from "../../../../generated/entities.ids.generated.js";
import { loadCustomFieldValues } from "@open-mercato/shared/lib/crud/custom-fields";
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { roleCrudEvents, roleCrudIndexer } from "@open-mercato/core/modules/auth/commands/roles";
import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
import { assertActorCanModifySuperAdminRoleTarget } from "@open-mercato/core/modules/auth/lib/grantChecks";
import { enforceRoleTenantAccess } from "@open-mercato/core/modules/auth/lib/roleTenantGuard";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("auth").child({ component: "roles" });
const querySchema = z.object({
  id: z.string().uuid().optional(),
  page: z.coerce.number().min(1).default(1),
  pageSize: z.coerce.number().min(1).max(100).default(50),
  search: z.string().optional(),
  tenantId: z.string().uuid().optional()
}).passthrough();
const roleCreateSchema = z.object({
  name: z.string().min(2).max(100),
  tenantId: z.string().uuid().optional()
});
const roleUpdateSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(2).max(100).optional(),
  tenantId: z.string().uuid().optional()
});
const roleListItemSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  usersCount: z.number().int().nonnegative(),
  tenantId: z.string().uuid().nullable(),
  tenantIds: z.array(z.string().uuid()).optional(),
  tenantName: z.string().nullable(),
  updatedAt: z.string().nullable().optional()
});
const roleListResponseSchema = z.object({
  items: z.array(roleListItemSchema),
  total: z.number().int().nonnegative(),
  totalPages: z.number().int().positive(),
  isSuperAdmin: z.boolean().optional()
});
const okResponseSchema = z.object({ ok: z.literal(true) });
const errorResponseSchema = z.object({ error: z.string() });
const routeMetadata = {
  GET: { requireAuth: true, requireFeatures: ["auth.roles.list"] },
  POST: { requireAuth: true, requireFeatures: ["auth.roles.manage"] },
  PUT: { requireAuth: true, requireFeatures: ["auth.roles.manage"] },
  DELETE: { requireAuth: true, requireFeatures: ["auth.roles.manage"] }
};
const metadata = routeMetadata;
const rawBodySchema = z.object({}).passthrough();
const crud = makeCrudRoute({
  metadata: routeMetadata,
  orm: {
    entity: Role,
    idField: "id",
    orgField: null,
    tenantField: null,
    softDeleteField: "deletedAt"
  },
  events: roleCrudEvents,
  indexer: roleCrudIndexer,
  actions: {
    create: {
      commandId: "auth.roles.create",
      schema: rawBodySchema,
      mapInput: async ({ parsed, ctx }) => enforceRoleTenantAccess("create", parsed, { auth: ctx.auth, container: ctx.container }),
      response: ({ result }) => ({ id: String(result.id) }),
      status: 201
    },
    update: {
      commandId: "auth.roles.update",
      schema: rawBodySchema,
      mapInput: async ({ parsed, ctx }) => {
        if (ctx.request && typeof parsed.id === "string" && parsed.id.length) {
          await assertCanModifySuperAdminRole(ctx.request, parsed.id);
        }
        return enforceRoleTenantAccess("update", parsed, { auth: ctx.auth, container: ctx.container });
      },
      response: () => ({ ok: true })
    },
    delete: {
      commandId: "auth.roles.delete",
      mapInput: async ({ parsed, raw, ctx }) => {
        const targetId = resolveDeleteTargetId(parsed, raw);
        if (ctx.request && targetId) {
          await assertCanModifySuperAdminRole(ctx.request, targetId);
        }
        return enforceRoleTenantAccess("delete", parsed, { auth: ctx.auth, container: ctx.container });
      },
      response: () => ({ ok: true })
    }
  }
});
async function GET(req) {
  const auth = await getAuthFromRequest(req);
  if (!auth) return NextResponse.json({ items: [], total: 0, totalPages: 1 });
  const url = new URL(req.url);
  const parsed = querySchema.safeParse({
    id: url.searchParams.get("id") || void 0,
    page: url.searchParams.get("page") || void 0,
    pageSize: url.searchParams.get("pageSize") || void 0,
    search: url.searchParams.get("search") || void 0,
    tenantId: url.searchParams.get("tenantId") || void 0
  });
  if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 });
  const container = await createRequestContainer();
  const em = container.resolve("em");
  let isSuperAdmin = false;
  try {
    if (auth.sub) {
      const rbacService = container.resolve("rbacService");
      const acl = await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null });
      isSuperAdmin = !!acl?.isSuperAdmin;
    }
  } catch (err) {
    logger.error("Failed to resolve rbac", { err });
  }
  const actorTenantId = auth.tenantId ? String(auth.tenantId) : null;
  if (!isSuperAdmin && !actorTenantId) {
    return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin });
  }
  let superAdminRoleIds = null;
  if (!isSuperAdmin && actorTenantId) {
    const superAdminAcls = await findWithDecryption(em, RoleAcl, { tenantId: actorTenantId, isSuperAdmin: true }, {}, { tenantId: actorTenantId, organizationId: null });
    if (superAdminAcls.length) {
      superAdminRoleIds = new Set(
        superAdminAcls.map((acl) => {
          const roleRef = acl.role;
          const idValue = roleRef?.id;
          return idValue ? String(idValue) : null;
        }).filter((id2) => !!id2)
      );
    } else {
      superAdminRoleIds = /* @__PURE__ */ new Set();
    }
  }
  const { id, page, pageSize, search, tenantId: requestedTenantId } = parsed.data;
  const tenantFilter = isSuperAdmin && requestedTenantId ? String(requestedTenantId) : null;
  const filters = [{ deletedAt: null }];
  if (id) filters.push({ id });
  if (search) filters.push({ name: { $ilike: `%${escapeLikePattern(search)}%` } });
  if (!isSuperAdmin && actorTenantId) {
    filters.push({ tenantId: actorTenantId });
    filters.push({ name: { $ne: "superadmin" } });
    if (superAdminRoleIds && superAdminRoleIds.size) {
      filters.push({ id: { $nin: Array.from(superAdminRoleIds) } });
    }
  } else if (tenantFilter) {
    filters.push({ tenantId: tenantFilter });
  }
  const where = filters.length > 1 ? { $and: filters } : filters[0];
  const [rows, count] = await em.findAndCount(Role, where, { limit: pageSize, offset: (page - 1) * pageSize });
  const roleIds = rows.map((r) => String(r.id));
  const counts = {};
  if (roleIds.length) {
    const userRoleFilter = { role: { $in: roleIds }, deletedAt: null };
    const links = await findWithDecryption(em, UserRole, userRoleFilter, {}, { tenantId: null, organizationId: null });
    for (const l of links) {
      const rid = String(l.role?.id || l.role);
      counts[rid] = (counts[rid] || 0) + 1;
    }
  }
  const roleTenantIds = rows.map((role) => role.tenantId ? String(role.tenantId) : null).filter((tenantId) => typeof tenantId === "string" && tenantId.length > 0);
  const uniqueTenantIds = Array.from(new Set(roleTenantIds));
  let tenantMap = {};
  if (uniqueTenantIds.length) {
    const tenants = await findWithDecryption(em, Tenant, { id: { $in: uniqueTenantIds }, deletedAt: null }, {}, { tenantId: null, organizationId: null });
    tenantMap = tenants.reduce((acc, tenant) => {
      const tid = tenant?.id ? String(tenant.id) : null;
      if (!tid) return acc;
      const rawName = tenant?.name;
      const name = typeof rawName === "string" && rawName.length > 0 ? rawName : tid;
      acc[tid] = name;
      return acc;
    }, {});
  }
  const tenantByRole = {};
  for (const role of rows) {
    const rid = String(role.id);
    tenantByRole[rid] = role.tenantId ? String(role.tenantId) : null;
  }
  const tenantFallbacks = Array.from(/* @__PURE__ */ new Set([
    auth.tenantId ?? null,
    tenantFilter ?? null,
    ...Object.values(tenantByRole)
  ]));
  const cfByRole = roleIds.length ? await loadCustomFieldValues({
    em,
    entityId: E.auth.role,
    recordIds: roleIds,
    tenantIdByRecord: tenantByRole,
    tenantFallbacks
  }) : {};
  const items = rows.map((r) => {
    const idStr = String(r.id);
    const tenantId = tenantByRole[idStr];
    const tenantName = tenantId ? tenantMap[tenantId] ?? tenantId : null;
    const exposeTenant = isSuperAdmin || tenantId && auth.tenantId && tenantId === auth.tenantId;
    return {
      id: idStr,
      name: String(r.name),
      usersCount: counts[idStr] || 0,
      tenantId: tenantId ?? null,
      tenantIds: exposeTenant && tenantId ? [tenantId] : [],
      tenantName: exposeTenant ? tenantName : null,
      updatedAt: r.updatedAt instanceof Date ? r.updatedAt.toISOString() : null,
      ...cfByRole[idStr] || {}
    };
  });
  const totalPages = Math.max(1, Math.ceil(count / pageSize));
  await logCrudAccess({
    container,
    auth,
    request: req,
    items,
    idField: "id",
    resourceKind: "auth.role",
    organizationId: null,
    tenantId: auth.tenantId ?? null,
    query: parsed.data,
    accessType: id ? "read:item" : void 0
  });
  return NextResponse.json({ items, total: count, totalPages, isSuperAdmin });
}
const POST = crud.POST;
const PUT = crud.PUT;
const DELETE = async (req) => {
  const targetId = new URL(req.url).searchParams.get("id");
  if (targetId) {
    try {
      await assertCanModifySuperAdminRole(req, targetId);
    } catch (err) {
      if (err instanceof CrudHttpError) {
        return NextResponse.json(err.body, { status: err.status });
      }
      throw err;
    }
  }
  return crud.DELETE(req);
};
async function assertCanModifySuperAdminRole(req, targetRoleId) {
  const auth = await getAuthFromRequest(req);
  if (!auth?.sub) throw new CrudHttpError(401, { error: "Unauthorized" });
  const container = await createRequestContainer();
  const em = container.resolve("em");
  await assertActorCanModifySuperAdminRoleTarget({
    em,
    rbacService: container.resolve("rbacService"),
    actorUserId: auth.sub,
    tenantId: auth.tenantId ?? null,
    organizationId: auth.orgId ?? null,
    targetRoleId
  });
}
function resolveDeleteTargetId(parsed, raw) {
  const fromParsed = readId(parsed);
  if (fromParsed) return fromParsed;
  const rawRecord = raw;
  return readId(rawRecord?.query) ?? readId(rawRecord?.body);
}
function readId(record) {
  const value = record?.id;
  return typeof value === "string" && value.length > 0 ? value : null;
}
const openApi = {
  tag: "Authentication & Accounts",
  summary: "Role management",
  methods: {
    GET: {
      summary: "List roles",
      description: "Returns available roles within the current tenant. Super administrators receive visibility across tenants.",
      query: querySchema,
      responses: [
        { status: 200, description: "Role collection", schema: roleListResponseSchema }
      ]
    },
    POST: {
      summary: "Create role",
      description: "Creates a new role anchored to the caller's tenant. Non-superadmins cannot target another tenant; supplying a foreign `tenantId` is rejected.",
      requestBody: {
        contentType: "application/json",
        schema: roleCreateSchema
      },
      responses: [
        {
          status: 201,
          description: "Role created",
          schema: z.object({ id: z.string().uuid() })
        }
      ],
      errors: [
        { status: 400, description: "Invalid payload", schema: errorResponseSchema },
        { status: 401, description: "Unauthorized", schema: errorResponseSchema }
      ]
    },
    PUT: {
      summary: "Update role",
      description: "Updates mutable fields on an existing role.",
      requestBody: {
        contentType: "application/json",
        schema: roleUpdateSchema
      },
      responses: [
        {
          status: 200,
          description: "Role updated",
          schema: okResponseSchema
        }
      ],
      errors: [
        { status: 400, description: "Invalid payload", schema: errorResponseSchema },
        { status: 401, description: "Unauthorized", schema: errorResponseSchema },
        { status: 404, description: "Role not found", schema: errorResponseSchema }
      ]
    },
    DELETE: {
      summary: "Delete role",
      description: "Deletes a role by identifier. Fails when users remain assigned.",
      query: z.object({ id: z.string().uuid().describe("Role identifier") }),
      responses: [
        { status: 200, description: "Role deleted", schema: okResponseSchema }
      ],
      errors: [
        { status: 400, description: "Role cannot be deleted", schema: errorResponseSchema },
        { status: 401, description: "Unauthorized", schema: errorResponseSchema },
        { status: 404, description: "Role not found", schema: errorResponseSchema }
      ]
    }
  }
};
export {
  DELETE,
  GET,
  POST,
  PUT,
  metadata,
  openApi
};
//# sourceMappingURL=route.js.map
