import { NextResponse } from "next/server";
import { z } from "zod";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { LockMode } from "@mikro-orm/core";
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { getCachedRateLimiterService } from "@open-mercato/core/bootstrap";
import { readEndpointRateLimitConfig } from "@open-mercato/shared/lib/ratelimit/config";
import { checkRateLimit, getClientIp, rateLimitErrorSchema } from "@open-mercato/shared/lib/ratelimit/helpers";
import { validateSameOriginMutationRequest } from "./originGuard.js";
import { hashAuthToken } from "../../../../auth/lib/tokenHash.js";
import { SalesOrder, SalesQuote } from "../../../data/entities.js";
import { quoteAcceptSchema } from "../../../data/validators.js";
import { sendEmail } from "@open-mercato/shared/lib/email/send";
import { resolveStatusEntryIdByValue } from "../../../lib/statusHelpers.js";
import { QuoteAcceptedAdminEmail } from "../../../emails/QuoteAcceptedAdminEmail.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("sales");
const metadata = {
  POST: { requireAuth: false }
};
const quoteAcceptRateLimitConfig = readEndpointRateLimitConfig("SALES_QUOTES_ACCEPT", {
  points: 10,
  duration: 60,
  blockDuration: 300,
  keyPrefix: "sales_quote_accept"
});
async function POST(req) {
  try {
    const { translate } = await resolveTranslations();
    const sameOriginViolation = validateSameOriginMutationRequest(req);
    if (sameOriginViolation) {
      return NextResponse.json(
        { error: translate("sales.quotes.accept.forbidden", "Cross-site quote acceptance is not allowed.") },
        { status: 403 }
      );
    }
    const rateLimiterService = getCachedRateLimiterService();
    const clientIp = rateLimiterService ? getClientIp(req, rateLimiterService.trustProxyDepth) : null;
    if (rateLimiterService && clientIp) {
      const rateLimitResponse = await checkRateLimit(
        rateLimiterService,
        quoteAcceptRateLimitConfig,
        clientIp,
        translate("api.errors.rateLimit", "Too many requests. Please try again later.")
      );
      if (rateLimitResponse) return rateLimitResponse;
    }
    const { token } = quoteAcceptSchema.parse(await req.json().catch(() => ({})));
    const auth = await getAuthFromRequest(req);
    const container = await createRequestContainer();
    const em = container.resolve("em").fork();
    const hashedToken = hashAuthToken(token);
    const tenantScope = auth?.tenantId ? { tenantId: auth.tenantId } : void 0;
    const commandBus = container.resolve("commandBus");
    const { quote, orderId } = await em.transactional(async (trx) => {
      const findQuoteByToken = (acceptanceToken) => findOneWithDecryption(
        trx,
        SalesQuote,
        {
          acceptanceToken,
          ...auth?.tenantId ? { tenantId: auth.tenantId } : {},
          deletedAt: null
        },
        { lockMode: LockMode.PESSIMISTIC_WRITE },
        tenantScope
      );
      const quote2 = await findQuoteByToken(hashedToken) ?? await findQuoteByToken(token);
      if (!quote2) {
        throw new CrudHttpError(404, { error: translate("sales.quotes.accept.notFound", "Quote not found.") });
      }
      const now = /* @__PURE__ */ new Date();
      if (quote2.validUntil && quote2.validUntil.getTime() < now.getTime()) {
        throw new CrudHttpError(400, { error: translate("sales.quotes.accept.expired", "This quote has expired.") });
      }
      if ((quote2.status ?? null) !== "sent") {
        throw new CrudHttpError(400, {
          error: translate("sales.quotes.accept.invalidStatus", "This quote cannot be accepted in its current status.")
        });
      }
      quote2.status = "confirmed";
      quote2.statusEntryId = await resolveStatusEntryIdByValue(trx, {
        tenantId: quote2.tenantId,
        organizationId: quote2.organizationId,
        value: "confirmed"
      });
      quote2.updatedAt = now;
      trx.persist(quote2);
      await trx.flush();
      const ctx = {
        container,
        auth: null,
        organizationScope: null,
        selectedOrganizationId: quote2.organizationId,
        organizationIds: [quote2.organizationId],
        request: req,
        transactionalEm: trx
      };
      const result = await commandBus.execute("sales.quotes.convert_to_order", { input: { quoteId: quote2.id }, ctx });
      const orderId2 = result?.result?.orderId ?? result?.orderId ?? quote2.id;
      return { quote: quote2, orderId: orderId2 };
    });
    const order = await findOneWithDecryption(em, SalesOrder, { id: orderId, deletedAt: null }, {}, tenantScope);
    const orderNumber = order?.orderNumber ?? orderId;
    const adminEmail = process.env.ADMIN_EMAIL || "";
    if (adminEmail) {
      try {
        const appUrl = process.env.APP_URL || "";
        const orderUrl = appUrl ? `${appUrl.replace(/\/$/, "")}/backend/sales/orders/${orderId}` : `/backend/sales/orders/${orderId}`;
        const copy = {
          preview: translate("sales.quotes.accept.adminEmail.preview", "Quote {quoteNumber} accepted", { quoteNumber: quote.quoteNumber }),
          heading: translate("sales.quotes.accept.adminEmail.heading", "Quote {quoteNumber} accepted", { quoteNumber: quote.quoteNumber }),
          body: translate("sales.quotes.accept.adminEmail.body", "The customer accepted quote {quoteNumber}. An order has been created: {orderNumber}.", {
            quoteNumber: quote.quoteNumber,
            orderNumber
          }),
          cta: translate("sales.quotes.accept.adminEmail.cta", "View order"),
          footer: translate("sales.quotes.accept.adminEmail.footer", "Open Mercato")
        };
        await sendEmail({
          to: adminEmail,
          subject: translate("sales.quotes.accept.adminSubject", "Quote {quoteNumber} accepted \u2192 Order {orderNumber}", {
            quoteNumber: quote.quoteNumber,
            orderNumber
          }),
          react: QuoteAcceptedAdminEmail({ orderUrl, copy })
        });
      } catch (err) {
        logger.error("sales.quotes.accept.adminEmail failed", { err });
      }
    }
    return NextResponse.json({ orderId, orderNumber });
  } catch (err) {
    if (isCrudHttpError(err)) {
      return NextResponse.json(err.body, { status: err.status });
    }
    const { translate } = await resolveTranslations();
    logger.error("sales.quotes.accept failed", { err });
    return NextResponse.json({ error: translate("sales.quotes.accept.failed", "Failed to accept quote.") }, { status: 400 });
  }
}
const openApi = {
  tag: "Sales",
  summary: "Accept a quote (public)",
  methods: {
    POST: {
      summary: "Accept quote and convert to order",
      requestBody: {
        contentType: "application/json",
        schema: quoteAcceptSchema
      },
      responses: [
        {
          status: 200,
          description: "Quote accepted and order created",
          schema: z.object({ orderId: z.string().uuid(), orderNumber: z.string() })
        },
        { status: 400, description: "Invalid or expired quote", schema: z.object({ error: z.string() }) },
        { status: 403, description: "Cross-site request rejected", schema: z.object({ error: z.string() }) },
        { status: 404, description: "Quote not found", schema: z.object({ error: z.string() }) },
        { status: 429, description: "Too many requests", schema: rateLimitErrorSchema }
      ]
    }
  }
};
export {
  POST,
  metadata,
  openApi
};
//# sourceMappingURL=route.js.map
