import { NextResponse } from "next/server";
import { z } from "zod";
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
import { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from "@open-mercato/shared/lib/crud/mutation-guard";
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { isOrganizationReadAccessAllowed } from "@open-mercato/core/modules/directory/utils/organizationScopeGuard";
import { User } from "@open-mercato/core/modules/auth/data/entities";
import { CustomerEntity, CustomerEntityRole } from "../data/entities.js";
import { entityRoleCreateSchema, entityRoleUpdateSchema, entityRoleDeleteSchema } from "../data/validators.js";
import { withScopedPayload } from "./utils.js";
import { resolveCustomersRequestContext, resolveAuthActorId } from "../lib/interactionRequestContext.js";
import { deriveDisplayNameFromEmail } from "../lib/displayName.js";
import { withOperationMetadata } from "../lib/operationMetadata.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("customers");
const paramsSchema = z.object({ id: z.string().uuid() });
const roleIdQuerySchema = z.object({ roleId: z.string().uuid() });
const createBodySchema = z.object({
  roleType: z.string().trim().min(1).max(100),
  userId: z.string().uuid()
});
const updateBodySchema = z.object({
  userId: z.string().uuid()
});
const listItemSchema = z.object({
  id: z.string().uuid(),
  entityType: z.enum(["company", "person"]),
  entityId: z.string().uuid(),
  userId: z.string().uuid(),
  userName: z.string().nullable().optional(),
  userEmail: z.string().nullable().optional(),
  userPhone: z.string().nullable().optional(),
  roleType: z.string(),
  createdAt: z.string(),
  updatedAt: z.string()
});
const listResponseSchema = z.object({ items: z.array(listItemSchema) });
const okResponseSchema = z.object({ ok: z.boolean() });
const createResponseSchema = z.object({ id: z.string().uuid() });
const errorSchema = z.object({ error: z.string() });
function getRoleContext(entityType, entityId) {
  const resourceKind = entityType === "company" ? "customers.company" : "customers.person";
  return { entityType, entityId, resourceKind, resourceId: entityId };
}
function buildValidationErrorResponse(error, translate) {
  return NextResponse.json(
    { error: translate("customers.errors.validationFailed", "Validation failed"), fieldErrors: error.flatten().fieldErrors },
    { status: 400 }
  );
}
async function buildContext(request) {
  const context = await resolveCustomersRequestContext(request);
  return {
    ...context,
    ctx: context.commandContext
  };
}
function ensureRouteOrganizationAccess(organizationId, scope, auth, translate) {
  if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId })) {
    throw new CrudHttpError(403, { error: translate("customers.errors.access_denied", "Access denied") });
  }
}
async function ensureFeatureOnOrganization(container, auth, feature, organizationId, translate) {
  const actorId = resolveAuthActorId(auth);
  if (!actorId) {
    throw new CrudHttpError(401, { error: translate("customers.errors.unauthorized", "Unauthorized") });
  }
  let rbac;
  try {
    rbac = container.resolve("rbacService");
  } catch (err) {
    logger.error("rbacService resolve failed", { component: "entity-roles-factory", err });
    rbac = void 0;
  }
  if (!rbac) {
    throw new CrudHttpError(500, { error: translate("customers.errors.internal", "Internal error") });
  }
  const hasFeature = await rbac.userHasAllFeatures(actorId, [feature], {
    tenantId: auth.tenantId,
    organizationId
  });
  if (!hasFeature) {
    throw new CrudHttpError(403, { error: translate("customers.errors.access_denied", "Access denied") });
  }
}
async function resolveEntityRouteScope(em, auth, scope, entityType, entityId, translate) {
  const entity = await findOneWithDecryption(
    em,
    CustomerEntity,
    { id: entityId, kind: entityType, tenantId: auth.tenantId, deletedAt: null },
    void 0,
    { tenantId: auth.tenantId, organizationId: scope?.selectedId ?? auth.orgId ?? null }
  );
  if (!entity || entity.tenantId !== auth.tenantId) {
    throw new CrudHttpError(404, { error: translate("customers.errors.customer_not_found", "Customer not found") });
  }
  ensureRouteOrganizationAccess(entity.organizationId, scope, auth, translate);
  return {
    entity,
    organizationId: entity.organizationId,
    tenantId: entity.tenantId
  };
}
async function resolveRoleRouteScope(em, auth, scope, entityType, entityId, roleId, translate) {
  const role = await findOneWithDecryption(
    em,
    CustomerEntityRole,
    { id: roleId, tenantId: auth.tenantId, entityType, entityId, deletedAt: null },
    void 0,
    { tenantId: auth.tenantId, organizationId: scope?.selectedId ?? auth.orgId ?? null }
  );
  if (!role || role.tenantId !== auth.tenantId || role.entityType !== entityType || role.entityId !== entityId) {
    throw new CrudHttpError(404, { error: translate("customers.errors.role_not_found", "Role not found") });
  }
  ensureRouteOrganizationAccess(role.organizationId, scope, auth, translate);
  return {
    role,
    organizationId: role.organizationId,
    tenantId: role.tenantId
  };
}
function createScopedCommandContext(ctx, organizationId) {
  return {
    ...ctx,
    selectedOrganizationId: organizationId,
    organizationIds: [organizationId]
  };
}
const entityRolesMetadata = {
  GET: { requireAuth: true, requireFeatures: ["customers.roles.view"] },
  POST: { requireAuth: true, requireFeatures: ["customers.roles.manage"] },
  PUT: { requireAuth: true, requireFeatures: ["customers.roles.manage"] },
  DELETE: { requireAuth: true, requireFeatures: ["customers.roles.manage"] }
};
function buildEntityRolesOpenApi(entityType) {
  const label = entityType === "company" ? "company" : "person";
  return {
    tag: "Customers",
    summary: `${label.charAt(0).toUpperCase() + label.slice(1)} role assignments`,
    pathParams: paramsSchema,
    methods: {
      GET: {
        summary: `List roles for a ${label}`,
        responses: [{ status: 200, description: "Role assignments", schema: listResponseSchema }],
        errors: [
          { status: 400, description: "Invalid request", schema: errorSchema },
          { status: 401, description: "Unauthorized", schema: errorSchema }
        ]
      },
      POST: {
        summary: `Assign a role to a ${label}`,
        requestBody: { contentType: "application/json", schema: createBodySchema },
        responses: [{ status: 201, description: "Role created", schema: createResponseSchema }],
        errors: [
          { status: 400, description: "Invalid request", schema: errorSchema },
          { status: 401, description: "Unauthorized", schema: errorSchema },
          { status: 409, description: "Role already assigned", schema: errorSchema }
        ]
      },
      PUT: {
        summary: `Update a ${label} role assignment`,
        query: roleIdQuerySchema,
        requestBody: { contentType: "application/json", schema: updateBodySchema },
        responses: [{ status: 200, description: "Role updated", schema: okResponseSchema }],
        errors: [
          { status: 400, description: "Invalid request", schema: errorSchema },
          { status: 401, description: "Unauthorized", schema: errorSchema },
          { status: 404, description: "Role not found", schema: errorSchema }
        ]
      },
      DELETE: {
        summary: `Remove a ${label} role assignment`,
        query: roleIdQuerySchema,
        responses: [{ status: 200, description: "Role deleted", schema: okResponseSchema }],
        errors: [
          { status: 400, description: "Invalid request", schema: errorSchema },
          { status: 401, description: "Unauthorized", schema: errorSchema },
          { status: 404, description: "Role not found", schema: errorSchema }
        ]
      }
    }
  };
}
function createEntityRolesHandlers(entityType) {
  const resourceKind = entityType === "company" ? "customers.company" : "customers.person";
  const logPrefix = entityType === "company" ? "customers.company.roles" : "customers.person.roles";
  async function GET(request, { params }) {
    const { translate } = await resolveTranslations();
    try {
      const { id: entityId } = paramsSchema.parse(params);
      const { container, em, auth, scope } = await buildContext(request);
      const targetScope = await resolveEntityRouteScope(em, auth, scope, entityType, entityId, translate);
      await ensureFeatureOnOrganization(container, auth, "customers.roles.view", targetScope.organizationId, translate);
      const roles = await findWithDecryption(
        em,
        CustomerEntityRole,
        {
          entityType,
          entityId,
          organizationId: targetScope.organizationId,
          tenantId: targetScope.tenantId,
          deletedAt: null
        },
        { orderBy: { roleType: "asc" } },
        {
          tenantId: targetScope.tenantId,
          organizationId: targetScope.organizationId
        }
      );
      const userIds = Array.from(new Set(roles.map((role) => role.userId).filter((value) => typeof value === "string" && value.length > 0)));
      const users = userIds.length ? await findWithDecryption(
        em,
        User,
        {
          id: { $in: userIds },
          deletedAt: null,
          ...targetScope.tenantId ? { tenantId: targetScope.tenantId } : {}
        },
        void 0,
        {
          tenantId: targetScope.tenantId,
          organizationId: targetScope.organizationId
        }
      ) : [];
      const userMap = new Map(users.map((user) => [user.id, {
        name: user.name ?? deriveDisplayNameFromEmail(user.email) ?? null,
        email: user.email ?? null,
        phone: null
      }]));
      return NextResponse.json({
        items: roles.map((role) => ({
          ...userMap.has(role.userId) ? {
            userName: userMap.get(role.userId)?.name ?? null,
            userEmail: userMap.get(role.userId)?.email ?? null,
            userPhone: userMap.get(role.userId)?.phone ?? null
          } : {},
          id: role.id,
          entityType: role.entityType,
          entityId: role.entityId,
          userId: role.userId,
          roleType: role.roleType,
          createdAt: role.createdAt.toISOString(),
          updatedAt: role.updatedAt.toISOString()
        }))
      });
    } catch (err) {
      if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
      if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate);
      logger.error("Entity roles GET failed", { component: logPrefix, err });
      return NextResponse.json({ error: translate("customers.errors.failed_to_load_roles", "Failed to load roles") }, { status: 500 });
    }
  }
  async function POST(request, { params }) {
    const { translate } = await resolveTranslations();
    try {
      const { id: entityId } = paramsSchema.parse(params);
      const { container, em, auth, scope, ctx } = await buildContext(request);
      const targetScope = await resolveEntityRouteScope(em, auth, scope, entityType, entityId, translate);
      await ensureFeatureOnOrganization(container, auth, "customers.roles.manage", targetScope.organizationId, translate);
      const commandCtx = createScopedCommandContext(ctx, targetScope.organizationId);
      const rawBody = await readJsonSafe(request, {});
      const scoped = withScopedPayload(
        {
          ...rawBody,
          organizationId: targetScope.organizationId,
          tenantId: targetScope.tenantId,
          ...getRoleContext(entityType, entityId)
        },
        commandCtx,
        translate
      );
      const parsed = entityRoleCreateSchema.parse(scoped);
      const guardUserId = resolveAuthActorId(auth);
      const guardResult = await validateCrudMutationGuard(container, {
        tenantId: targetScope.tenantId,
        organizationId: targetScope.organizationId,
        userId: guardUserId,
        resourceKind,
        resourceId: entityId,
        operation: "custom",
        requestMethod: request.method,
        requestHeaders: request.headers,
        mutationPayload: rawBody
      });
      if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status });
      const commandBus = container.resolve("commandBus");
      const { result, logEntry } = await commandBus.execute(
        "customers.entityRoles.create",
        { input: parsed, ctx: commandCtx }
      );
      if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
        await runCrudMutationGuardAfterSuccess(container, {
          tenantId: targetScope.tenantId,
          organizationId: targetScope.organizationId,
          userId: guardUserId,
          resourceKind,
          resourceId: entityId,
          operation: "custom",
          requestMethod: request.method,
          requestHeaders: request.headers,
          metadata: guardResult.metadata ?? null
        });
      }
      return withOperationMetadata(
        NextResponse.json({ id: result?.roleId ?? null }, { status: 201 }),
        logEntry,
        { resourceKind, resourceId: entityId }
      );
    } catch (err) {
      if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
      if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate);
      logger.error("Entity roles POST failed", { component: logPrefix, err });
      return NextResponse.json({ error: translate("customers.errors.failed_to_assign_role", "Failed to assign role") }, { status: 500 });
    }
  }
  async function PUT(request, { params }) {
    const { translate } = await resolveTranslations();
    try {
      const { id: entityId } = paramsSchema.parse(params);
      const { roleId } = roleIdQuerySchema.parse(Object.fromEntries(new URL(request.url).searchParams));
      const { container, em, auth, scope, ctx } = await buildContext(request);
      const targetScope = await resolveRoleRouteScope(em, auth, scope, entityType, entityId, roleId, translate);
      await ensureFeatureOnOrganization(container, auth, "customers.roles.manage", targetScope.organizationId, translate);
      const commandCtx = createScopedCommandContext(ctx, targetScope.organizationId);
      const rawBody = await readJsonSafe(request, {});
      const scoped = withScopedPayload(
        {
          ...rawBody,
          id: roleId,
          organizationId: targetScope.organizationId,
          tenantId: targetScope.tenantId
        },
        commandCtx,
        translate
      );
      const parsed = entityRoleUpdateSchema.parse(scoped);
      const guardUserId = resolveAuthActorId(auth);
      const guardResult = await validateCrudMutationGuard(container, {
        tenantId: targetScope.tenantId,
        organizationId: targetScope.organizationId,
        userId: guardUserId,
        resourceKind,
        resourceId: entityId,
        operation: "custom",
        requestMethod: request.method,
        requestHeaders: request.headers,
        mutationPayload: { roleId, ...rawBody }
      });
      if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status });
      const commandBus = container.resolve("commandBus");
      const { logEntry } = await commandBus.execute(
        "customers.entityRoles.update",
        { input: parsed, ctx: commandCtx }
      );
      if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
        await runCrudMutationGuardAfterSuccess(container, {
          tenantId: targetScope.tenantId,
          organizationId: targetScope.organizationId,
          userId: guardUserId,
          resourceKind,
          resourceId: entityId,
          operation: "custom",
          requestMethod: request.method,
          requestHeaders: request.headers,
          metadata: guardResult.metadata ?? null
        });
      }
      return withOperationMetadata(
        NextResponse.json({ ok: true }),
        logEntry,
        { resourceKind, resourceId: entityId }
      );
    } catch (err) {
      if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
      if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate);
      logger.error("Entity roles PUT failed", { component: logPrefix, err });
      return NextResponse.json({ error: translate("customers.errors.failed_to_update_role", "Failed to update role") }, { status: 500 });
    }
  }
  async function DELETE(request, { params }) {
    const { translate } = await resolveTranslations();
    try {
      const { id: entityId } = paramsSchema.parse(params);
      const { roleId } = roleIdQuerySchema.parse(Object.fromEntries(new URL(request.url).searchParams));
      const { container, em, auth, scope, ctx } = await buildContext(request);
      const targetScope = await resolveRoleRouteScope(em, auth, scope, entityType, entityId, roleId, translate);
      await ensureFeatureOnOrganization(container, auth, "customers.roles.manage", targetScope.organizationId, translate);
      const commandCtx = createScopedCommandContext(ctx, targetScope.organizationId);
      const parsed = entityRoleDeleteSchema.parse(
        withScopedPayload(
          {
            id: roleId,
            organizationId: targetScope.organizationId,
            tenantId: targetScope.tenantId
          },
          commandCtx,
          translate
        )
      );
      const guardUserId = resolveAuthActorId(auth);
      const guardResult = await validateCrudMutationGuard(container, {
        tenantId: targetScope.tenantId,
        organizationId: targetScope.organizationId,
        userId: guardUserId,
        resourceKind,
        resourceId: entityId,
        operation: "custom",
        requestMethod: request.method,
        requestHeaders: request.headers,
        mutationPayload: { roleId }
      });
      if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status });
      const commandBus = container.resolve("commandBus");
      const { logEntry } = await commandBus.execute(
        "customers.entityRoles.delete",
        { input: parsed, ctx: commandCtx }
      );
      if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
        await runCrudMutationGuardAfterSuccess(container, {
          tenantId: targetScope.tenantId,
          organizationId: targetScope.organizationId,
          userId: guardUserId,
          resourceKind,
          resourceId: entityId,
          operation: "custom",
          requestMethod: request.method,
          requestHeaders: request.headers,
          metadata: guardResult.metadata ?? null
        });
      }
      return withOperationMetadata(
        NextResponse.json({ ok: true }),
        logEntry,
        { resourceKind, resourceId: entityId }
      );
    } catch (err) {
      if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
      if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate);
      logger.error("Entity roles DELETE failed", { component: logPrefix, err });
      return NextResponse.json({ error: translate("customers.errors.failed_to_delete_role", "Failed to delete role") }, { status: 500 });
    }
  }
  return { GET, POST, PUT, DELETE };
}
export {
  buildEntityRolesOpenApi,
  createEntityRolesHandlers,
  entityRolesMetadata
};
//# sourceMappingURL=entity-roles-factory.js.map
