import { NextResponse } from "next/server";
import { z } from "zod";
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { logCrudAccess } from "@open-mercato/shared/lib/crud/factory";
import { forbidden, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
import { UserAcl } from "@open-mercato/core/modules/auth/data/entities";
import {
  assertActorCanAccessUserTarget,
  assertActorCanGrantAcl,
  assertActorCanModifySuperAdminUserTarget,
  normalizeGrantFeatureList
} from "@open-mercato/core/modules/auth/lib/grantChecks";
const getSchema = z.object({ userId: z.string().uuid() });
const putSchema = z.object({
  userId: z.string().uuid(),
  isSuperAdmin: z.boolean().optional(),
  features: z.array(z.string()).optional(),
  organizations: z.array(z.string()).nullable().optional()
});
const metadata = {
  GET: { requireAuth: true, requireFeatures: ["auth.acl.manage"] },
  PUT: { requireAuth: true, requireFeatures: ["auth.acl.manage"] }
};
const userAclResponseSchema = z.object({
  hasCustomAcl: z.boolean(),
  isSuperAdmin: z.boolean(),
  features: z.array(z.string()),
  organizations: z.array(z.string()).nullable(),
  updatedAt: z.string().nullable()
});
const userAclUpdateResponseSchema = z.object({
  ok: z.literal(true),
  sanitized: z.boolean()
});
const userAclErrorSchema = z.object({ error: z.string() });
async function GET(req) {
  const auth = await getAuthFromRequest(req);
  if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  const url = new URL(req.url);
  const parsed = getSchema.safeParse({ userId: url.searchParams.get("userId") });
  if (!parsed.success) return NextResponse.json({ error: "Invalid input" }, { status: 400 });
  const container = await createRequestContainer();
  const em = container.resolve("em");
  const rbacService = container.resolve("rbacService");
  const actorAcl = auth.sub ? await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null }) : null;
  if (!actorAcl?.isSuperAdmin && auth.sub) {
    try {
      await assertActorCanModifySuperAdminUserTarget({
        em,
        rbacService,
        actorUserId: auth.sub,
        tenantId: auth.tenantId ?? null,
        organizationId: auth.orgId ?? null,
        targetUserId: parsed.data.userId,
        actorIsSuperAdmin: false
      });
      await assertActorCanAccessUserTarget({
        em,
        rbacService,
        actorUserId: auth.sub,
        tenantId: auth.tenantId ?? null,
        organizationId: auth.orgId ?? null,
        targetUserId: parsed.data.userId,
        actorIsSuperAdmin: false
      });
    } catch (err) {
      if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
      throw err;
    }
  }
  const acl = await em.findOne(UserAcl, { user: parsed.data.userId, tenantId: auth.tenantId });
  const response = acl ? {
    hasCustomAcl: true,
    isSuperAdmin: !!acl.isSuperAdmin,
    features: Array.isArray(acl.featuresJson) ? acl.featuresJson : [],
    organizations: Array.isArray(acl.organizationsJson) ? acl.organizationsJson : null,
    updatedAt: acl.updatedAt instanceof Date ? acl.updatedAt.toISOString() : null
  } : { hasCustomAcl: false, isSuperAdmin: false, features: [], organizations: null, updatedAt: null };
  await logCrudAccess({
    container,
    auth,
    request: req,
    items: [{ id: parsed.data.userId, ...response }],
    idField: "id",
    resourceKind: "auth.user_acl",
    organizationId: auth.orgId ?? null,
    tenantId: auth.tenantId ?? null,
    query: { userId: parsed.data.userId },
    accessType: "read:item"
  });
  return NextResponse.json(response);
}
async function PUT(req) {
  const auth = await getAuthFromRequest(req);
  if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  const body = await req.json().catch(() => ({}));
  const parsed = putSchema.safeParse(body);
  if (!parsed.success) return NextResponse.json({ error: "Invalid input" }, { status: 400 });
  const container = await createRequestContainer();
  const em = container.resolve("em");
  const rbacService = container.resolve("rbacService");
  const actorAcl = auth.sub ? await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null }) : null;
  const actorIsSuperAdmin = !!actorAcl?.isSuperAdmin;
  if (!actorIsSuperAdmin && auth.sub) {
    try {
      await assertActorCanModifySuperAdminUserTarget({
        em,
        rbacService,
        actorUserId: auth.sub,
        tenantId: auth.tenantId ?? null,
        organizationId: auth.orgId ?? null,
        targetUserId: parsed.data.userId,
        actorIsSuperAdmin: false
      });
      await assertActorCanAccessUserTarget({
        em,
        rbacService,
        actorUserId: auth.sub,
        tenantId: auth.tenantId ?? null,
        organizationId: auth.orgId ?? null,
        targetUserId: parsed.data.userId,
        actorIsSuperAdmin: false
      });
    } catch (err) {
      if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
      throw err;
    }
  }
  const requestedFeatures = normalizeGrantFeatureList(parsed.data.features);
  const organizations = normalizeOrganizations(parsed.data.organizations);
  let acl = await em.findOne(UserAcl, { user: parsed.data.userId, tenantId: auth.tenantId });
  if (acl) {
    try {
      await enforceCommandOptimisticLockWithGuards(container, {
        resourceKind: "auth.user_acl",
        resourceId: acl.id,
        current: acl.updatedAt ?? null,
        request: req
      });
    } catch (err) {
      if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
      throw err;
    }
  }
  const existingIsSuperAdmin = acl ? !!acl.isSuperAdmin : false;
  const existingFeatures = acl ? normalizeGrantFeatureList(acl.featuresJson) : [];
  const requestedIsSuperAdmin = parsed.data.isSuperAdmin ?? false;
  try {
    await assertActorCanGrantAcl({
      em,
      rbacService,
      actorUserId: auth.sub,
      tenantId: auth.tenantId ?? null,
      organizationId: auth.orgId ?? null,
      isSuperAdmin: requestedIsSuperAdmin,
      features: requestedFeatures,
      organizations
    });
  } catch (err) {
    if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
    throw err;
  }
  const effectiveFeatures = actorIsSuperAdmin ? requestedFeatures : sanitizeTenantFeatures(requestedFeatures);
  let effectiveIsSuperAdmin = requestedIsSuperAdmin;
  if (!actorIsSuperAdmin) {
    if (requestedIsSuperAdmin && !existingIsSuperAdmin) {
      throw forbidden("Only super administrators can grant super admin access.");
    }
    if (existingIsSuperAdmin && requestedIsSuperAdmin === false) {
      effectiveIsSuperAdmin = false;
    } else {
      effectiveIsSuperAdmin = existingIsSuperAdmin;
    }
  }
  const hasCustomAcl = effectiveIsSuperAdmin || effectiveFeatures.length > 0;
  if (!hasCustomAcl) {
    if (acl) {
      const aclToRemove = acl;
      await withAtomicFlush(em, [() => em.remove(aclToRemove)], { transaction: true });
    }
  } else {
    if (!acl) {
      acl = em.create(UserAcl, { user: parsed.data.userId, tenantId: auth.tenantId });
    }
    const aclRecord = acl;
    await withAtomicFlush(
      em,
      [
        () => {
          aclRecord.isSuperAdmin = effectiveIsSuperAdmin;
          aclRecord.featuresJson = effectiveFeatures;
          aclRecord.organizationsJson = organizations;
          em.persist(aclRecord);
        }
      ],
      { transaction: true }
    );
  }
  await rbacService.invalidateUserCache(parsed.data.userId);
  try {
    const cache = container.resolve("cache");
    if (cache) await cache.deleteByTags([`rbac:user:${parsed.data.userId}`]);
  } catch {
  }
  return NextResponse.json({
    ok: true,
    sanitized: !actorIsSuperAdmin && (hasRestrictedChanges(requestedFeatures, effectiveFeatures, existingFeatures) || requestedIsSuperAdmin !== effectiveIsSuperAdmin)
  });
}
function normalizeOrganizations(organizations) {
  if (!Array.isArray(organizations)) return null;
  return normalizeGrantFeatureList(organizations);
}
function sanitizeTenantFeatures(features) {
  return features.filter((feature) => !isTenantRestrictedFeature(feature));
}
function isTenantRestrictedFeature(feature) {
  if (feature === "*" || feature === "directory.*") return true;
  if (feature.startsWith("directory.tenants")) return true;
  return false;
}
function hasRestrictedChanges(requested, effective, existing) {
  if (requested.length === effective.length) return false;
  const effectiveSet = new Set(effective);
  const existingSet = new Set(existing);
  if (effectiveSet.size === existingSet.size) {
    let identical = true;
    for (const value of effectiveSet) {
      if (!existingSet.has(value)) {
        identical = false;
        break;
      }
    }
    if (identical) return false;
  }
  return true;
}
const openApi = {
  tag: "Authentication & Accounts",
  summary: "User ACL management",
  methods: {
    GET: {
      summary: "Fetch user ACL",
      description: "Returns custom ACL overrides for a user within the current tenant, if any.",
      query: getSchema,
      responses: [
        { status: 200, description: "User ACL entry", schema: userAclResponseSchema },
        { status: 400, description: "Invalid user id", schema: userAclErrorSchema },
        { status: 401, description: "Unauthorized", schema: userAclErrorSchema }
      ]
    },
    PUT: {
      summary: "Update user ACL",
      description: "Configures per-user ACL overrides, including super admin access, feature list, and organization scope.",
      requestBody: {
        contentType: "application/json",
        schema: putSchema
      },
      responses: [
        { status: 200, description: "User ACL updated", schema: userAclUpdateResponseSchema },
        { status: 400, description: "Invalid payload", schema: userAclErrorSchema },
        { status: 401, description: "Unauthorized", schema: userAclErrorSchema },
        { status: 403, description: "Insufficient privileges to modify ACL", schema: userAclErrorSchema }
      ]
    }
  }
};
export {
  GET,
  PUT,
  metadata,
  openApi
};
//# sourceMappingURL=route.js.map
