import { NextRequest } from "next/server";
import { connectDB } from "@/lib/db";
import { Order, Product } from "@/models";
import { getNextPosOrderNumber } from "@/lib/order-number";
import { DEFAULT_VENDOR_COMMISSION_RATE } from "@/lib/order-settings";
import { createdResponse } from "@/lib/api/response";
import {
  handleApiError,
  AuthenticationError,
  AuthorizationError,
  ValidationError,
} from "@/lib/api/errors";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { USER_ROLES, ORDER_STATUS } from "@/config/app.config";
import { isStaffRole } from "@/lib/staff-role";
import { getSettings } from "@/models";
import {
  getStripeForSecretKey,
  isStripeSecretKeyConfigured,
  toStripeAmount,
} from "@/lib/stripe";
import { resolveStripeCredentials } from "@/lib/credentials";
import {
  decrementInventory,
  restoreInventory,
  InsufficientStockError,
} from "@/lib/inventory";
import { markOrderInventoryReserved } from "@/lib/order-inventory";
import { canAccessPOS } from "@/lib/rbac";
import { requireApprovedVendorByUserId } from "@/lib/vendor-guard";
import { ensureChargeTransaction } from "@/lib/payment-transactions";
import { createAuditContext } from "@/lib/audit";
import { auditOrderPaid, auditOrderPlaced } from "@/lib/audit-order";
import {
  buildVendorSubOrders,
  getOrderItemVendorId,
  groupItemsByOrderVendor,
  resolveOrderVendorContextForItems,
} from "@/lib/order-vendors";
import { notifyOrderCreatedParticipants } from "@/lib/notifications";
import { revalidateProductContent } from "@/lib/cache-invalidation";
import { validatePOSPaymentInput } from "@/lib/pos/payment";
import {
  calculatePOSOrderTotals,
  computePOSLineDiscountAmount,
  type POSOrderDiscountInput,
  type POSOrderItemInput,
} from "@/lib/pos/order-totals";

async function generatePosOrderNumber(prefix?: string) {
  return getNextPosOrderNumber(prefix || "POS");
}

function isDuplicateKeyError(err: unknown): boolean {
  return (
    typeof err === "object" &&
    err !== null &&
    (err as { code?: number }).code === 11000
  );
}

export async function POST(request: NextRequest) {
  try {
    const session = await auth.api.getSession({ headers: await headers() });
    if (!session) throw new AuthenticationError();
    const role = session.user.role;
    if (
      role !== USER_ROLES.ADMIN &&
      role !== USER_ROLES.VENDOR &&
      !isStaffRole(role)
    ) {
      throw new AuthorizationError();
    }

    await connectDB();
    const settings = await getSettings();
    if (!(await canAccessPOS(session.user))) {
      throw new AuthorizationError();
    }

    const body = await request.json();
    const {
      items,
      paymentMethod,
      notes,
      posLocationId,
      customerId,
      cashTendered,
      paymentReference,
      paymentNote,
      stripePaymentIntentId,
      discount,
      clientRequestId,
    }: {
      items: POSOrderItemInput[];
      paymentMethod: string;
      notes?: string;
      posLocationId?: string;
      customerId?: string;
      cashTendered?: number | string;
      paymentReference?: string;
      paymentNote?: string;
      stripePaymentIntentId?: string;
      discount?: POSOrderDiscountInput;
      clientRequestId?: string;
    } = body;

    if (!Array.isArray(items) || items.length === 0) {
      throw new ValidationError("Items are required");
    }
    if (!paymentMethod) {
      throw new ValidationError("Payment method is required");
    }

    // Idempotency: a cashier retry after a network blip must return the order
    // the first attempt already committed, not ring up a second sale.
    const normalizedClientRequestId =
      typeof clientRequestId === "string" &&
      /^[A-Za-z0-9_-]{8,64}$/.test(clientRequestId.trim())
        ? clientRequestId.trim()
        : undefined;
    if (normalizedClientRequestId) {
      const existingSale = await Order.findOne({
        posClientRequestId: normalizedClientRequestId,
        staffId: session.user.id,
      }).lean();
      if (existingSale) {
        return createdResponse(existingSale);
      }
    }

    // Validate item shape before pricing. Quantity must be a positive whole
    // number: fractional/negative quantities would skew the client-computed
    // total while inventory only ever decrements positive lines.
    for (const item of items) {
      if (!item || !String(item.productId || "").trim()) {
        throw new ValidationError("Each item must reference a product");
      }
      if (
        typeof item.quantity !== "number" ||
        !Number.isInteger(item.quantity) ||
        item.quantity < 1
      ) {
        throw new ValidationError(
          "Item quantity must be a positive whole number",
        );
      }
      if (
        item.lineDiscount &&
        (typeof item.lineDiscount.value !== "number" ||
          item.lineDiscount.value < 0)
      ) {
        throw new ValidationError("Line discount must not be negative");
      }
    }
    if (discount && (typeof discount.value !== "number" || discount.value < 0)) {
      throw new ValidationError("Discount must not be negative");
    }

    const productIds = Array.from(
      new Set(
        items.map((item) => String(item.productId || "").trim()).filter(Boolean),
      ),
    );
    if (productIds.length === 0) {
      throw new ValidationError("Items are required");
    }

    // Load authoritative product/variant prices. POS clients MUST NOT be
    // trusted for the price — resolving it server-side is the only thing
    // preventing a $0.01 "sale" that still decrements real stock.
    const affectedProducts = await Product.find({ _id: { $in: productIds } })
      .select("_id slug price vendorId variants")
      .lean();
    const productById = new Map(
      affectedProducts.map((product) => [String(product._id), product]),
    );

    let vendorScopeId: string | undefined;
    if (role === USER_ROLES.VENDOR) {
      const vendor = await requireApprovedVendorByUserId(session.user.id);
      vendorScopeId = vendor._id.toString();
      for (const productId of productIds) {
        const product = productById.get(productId);
        if (!product || String(product.vendorId) !== vendorScopeId) {
          throw new AuthorizationError(
            "Vendors can only sell their own products",
          );
        }
      }
    }

    const resolvePosItemPrice = (item: POSOrderItemInput): number => {
      const product = productById.get(String(item.productId).trim());
      if (!product) {
        throw new ValidationError("A selected product no longer exists");
      }
      const variants = (product.variants || []) as Array<{
        _id: unknown;
        price?: number;
      }>;
      if (item.variantId) {
        const variant = variants.find(
          (candidate) => String(candidate._id) === String(item.variantId),
        );
        if (!variant) {
          throw new ValidationError("A selected variant no longer exists");
        }
        if (typeof variant.price === "number") return variant.price;
      }
      if (typeof product.price === "number") return product.price;
      throw new ValidationError("A selected product has no price");
    };

    const normalizedItems: POSOrderItemInput[] = items.map((item) => ({
      ...item,
      price: resolvePosItemPrice(item),
      ...(vendorScopeId ? { vendorId: vendorScopeId } : {}),
    }));

    const taxRate = settings.orders?.taxRate ?? 0;
    const { subtotal, tax, shippingCost, totalDiscount, total } =
      calculatePOSOrderTotals({
        items: normalizedItems,
        discount,
        taxRate,
      });
    const normalizedStripeIntentId =
      typeof stripePaymentIntentId === "string"
        ? stripePaymentIntentId.trim()
        : "";
    const payment = validatePOSPaymentInput({
      paymentMethod,
      enabledMethods: settings.pos?.checkout?.paymentMethods,
      total,
      cashTendered,
      paymentReference: paymentReference || normalizedStripeIntentId,
      paymentNote,
    });
    if (!payment.ok) {
      throw new ValidationError(payment.message);
    }

    let verifiedStripePaymentIntentId: string | undefined;
    if (payment.metadata.posPayment.cardSubMethod === "card_stripe") {
      if (!normalizedStripeIntentId) {
        throw new ValidationError("Stripe payment intent is required");
      }
      const existingStripeOrder = await Order.findOne({
        stripePaymentIntentId: normalizedStripeIntentId,
      })
        .select("_id orderNumber")
        .lean();
      if (existingStripeOrder) {
        throw new ValidationError("Stripe payment has already been used");
      }

      const stripeSettings = settings.payment?.stripe;
      if (!stripeSettings?.enabled) {
        throw new ValidationError("Stripe is disabled");
      }
      const stripeSecretKey = resolveStripeCredentials(stripeSettings).secretKey;
      if (!isStripeSecretKeyConfigured(stripeSecretKey)) {
        throw new ValidationError("Stripe is not configured");
      }

      const currency = (
        settings.general?.defaultCurrency || "USD"
      ).toLowerCase();
      const expectedAmount = toStripeAmount(total, currency);
      const intent = await getStripeForSecretKey(
        stripeSecretKey,
      ).paymentIntents.retrieve(normalizedStripeIntentId);
      if (intent.status !== "succeeded" && intent.status !== "processing") {
        throw new ValidationError("Stripe payment was not completed");
      }
      if (intent.amount !== expectedAmount || intent.currency !== currency) {
        throw new ValidationError(
          "Stripe payment amount does not match order total",
        );
      }
      verifiedStripePaymentIntentId = intent.id;
    }

    // Order number generated below with retry
    const vendorContext = await resolveOrderVendorContextForItems({
      isMultiVendorEnabled: Boolean(settings.multiVendorMode?.enabled),
      items: normalizedItems,
      getVendorId: (item) => item.vendorId,
      defaultVendorOwnerUserId:
        role === USER_ROLES.ADMIN ? session.user.id : undefined,
    });
    const vendorItems = groupItemsByOrderVendor(
      normalizedItems,
      vendorContext,
      (item) => item.vendorId,
    );
    const subOrders = await buildVendorSubOrders(vendorItems, {
      getProductId: (item) => item.productId,
      getVariantId: (item) => item.variantId,
      getName: (item) => item.name,
      getSku: (item) => item.sku,
      getQuantity: (item) => item.quantity,
      getPrice: (item) => item.price,
      getImage: (item) => item.image,
      getLineDiscount: (item) => item.lineDiscount ?? null,
      getLineNote: (item) =>
        typeof item.lineNote === "string" && item.lineNote.trim().length > 0
          ? item.lineNote.trim()
          : undefined,
      fallbackCommissionPercent:
        settings.orders?.commission?.vendorRate ?? DEFAULT_VENDOR_COMMISSION_RATE,
      status: ORDER_STATUS.DELIVERED,
    });

    // POS placeholder address (not applicable for in-store sales)
    const posAddress = {
      street: "In-store POS",
      city: "POS",
      state: "POS",
      postalCode: "00000",
      country: "POS",
    };

    const orderData = {
      customerId: customerId || session.user.id,
      currency: settings.general?.defaultCurrency || "USD",
      posClientRequestId: normalizedClientRequestId,
      items: normalizedItems.map((item) => {
        const lineDiscountAmount = computePOSLineDiscountAmount(item);
        return {
          productId: item.productId,
          name: item.name,
          sku: item.sku,
          price: item.price,
          quantity: item.quantity,
          image: item.image,
          variantId: item.variantId,
          vendorId: getOrderItemVendorId(item.vendorId, vendorContext),
          lineDiscount: item.lineDiscount
            ? {
                type: item.lineDiscount.type,
                value: item.lineDiscount.value,
                amount: lineDiscountAmount,
              }
            : undefined,
          lineNote:
            typeof item.lineNote === "string" && item.lineNote.trim().length > 0
              ? item.lineNote.trim()
              : undefined,
        };
      }),
      subOrders,
      shippingAddress: posAddress,
      billingAddress: posAddress,
      paymentMethod: payment.method,
      paymentStatus: "paid",
      paymentId:
        verifiedStripePaymentIntentId ||
        (payment.method === "card" && payment.reference
          ? payment.reference
          : undefined),
      stripePaymentIntentId: verifiedStripePaymentIntentId,
      subtotal,
      shippingCost,
      tax,
      // Store the TOTAL discount (line + order-level) so finance reports,
      // sales ledger, and analytics all reflect the full markdown.
      discount: totalDiscount,
      discountMeta: discount
        ? {
            type: discount.type,
            value: discount.value,
            reason: discount.reason,
            note: discount.note,
          }
        : undefined,
      total,
      status: ORDER_STATUS.DELIVERED,
      notes,
      channel: "pos",
      posLocationId,
      staffId: session.user.id,
    };

    const inventoryLines = normalizedItems.map((item) => ({
      productId: String(item.productId),
      variantId: item.variantId ? String(item.variantId) : undefined,
      quantity: item.quantity,
    }));

    // Decrement inventory first so we never end up with a phantom order
    // referencing stock we couldn't actually reserve.
    try {
      await decrementInventory(inventoryLines, {
        channel: "pos",
        locationId: posLocationId,
      });
    } catch (err) {
      if (err instanceof InsufficientStockError) {
        throw new ValidationError("Insufficient stock for one or more items");
      }
      throw err;
    }
    revalidateProductContent({
      slugs: affectedProducts
        .map((p) => p.slug)
        .filter(
          (slug): slug is string =>
            typeof slug === "string" && slug.length > 0,
        ),
    });

    let order;
    try {
      for (let attempt = 0; attempt < 3; attempt++) {
        try {
          order = await Order.create({
            ...orderData,
            orderNumber: await generatePosOrderNumber(
              settings.pos?.orders?.orderNumberPrefix,
            ),
          });
          break;
        } catch (err) {
          // A duplicate on the idempotency key means a concurrent retry of the
          // SAME sale already committed — hand back that order (after undoing
          // this attempt's decrement) instead of retrying with a new number.
          if (
            isDuplicateKeyError(err) &&
            normalizedClientRequestId &&
            String((err as Error).message || "").includes("posClientRequestId")
          ) {
            const committed = await Order.findOne({
              posClientRequestId: normalizedClientRequestId,
              staffId: session.user.id,
            }).lean();
            if (committed) {
              await restoreInventory(inventoryLines, {
                channel: "pos",
                locationId: posLocationId,
              }).catch((restoreErr) =>
                console.error(
                  "Failed to restore duplicate POS attempt inventory:",
                  restoreErr,
                ),
              );
              return createdResponse(committed);
            }
          }
          if (isDuplicateKeyError(err) && attempt < 2) continue;
          throw err;
        }
      }
    } catch (err) {
      await restoreInventory(inventoryLines, {
        channel: "pos",
        locationId: posLocationId,
      }).catch((restoreErr) =>
        console.error("Failed to restore inventory after POS order failure:", restoreErr),
      );
      throw err;
    }

    if (!order) {
      await restoreInventory(inventoryLines, {
        channel: "pos",
        locationId: posLocationId,
      }).catch((restoreErr) =>
        console.error("Failed to restore inventory after POS order failure:", restoreErr),
      );
      throw new Error("Failed to create POS order after retries");
    }

    // Inventory was decremented before the order was created, so the
    // sub-orders should be marked reserved now that we have the order ID.
    await markOrderInventoryReserved(String(order._id)).catch((err) =>
      console.error("Failed to mark inventory reserved on POS order:", err),
    );

    await ensureChargeTransaction({
      _id: String(order._id),
      orderNumber: order.orderNumber,
      paymentMethod: order.paymentMethod,
      paymentStatus: order.paymentStatus,
      paymentId: order.paymentId,
      stripePaymentIntentId: order.stripePaymentIntentId,
      paypalCaptureId: order.paypalCaptureId,
      subtotal: order.subtotal,
      shippingCost: order.shippingCost,
      tax: order.tax,
      discount: order.discount,
      total: order.total,
      currency: settings.general?.defaultCurrency,
      channel: order.channel,
      posLocationId: order.posLocationId ? String(order.posLocationId) : undefined,
      paymentMetadata: payment.metadata,
      createdAt: order.createdAt,
    });

    await notifyOrderCreatedParticipants(order).catch((err) =>
      console.error("Failed to create POS order notifications:", err),
    );

    // A POS sale is born paid and delivered, so it never passes through an
    // admin status transition — which is why every POS order's timeline used
    // to read "No events yet" for its entire life. Record both facts here.
    const posAuditContext = createAuditContext(request, session);
    await auditOrderPlaced(posAuditContext, order, {
      source: "pos",
      total: order.total,
      currency: order.currency,
      itemCount: order.items.length,
      paymentMethod: order.paymentMethod,
    });
    await auditOrderPaid(posAuditContext, order, {
      gateway: order.paymentMethod,
      amount: order.total,
      currency: order.currency,
      transactionId: order.paymentId,
    });

    // Refresh the buyer's denormalized stats (only when a real customer was
    // attached — walk-in sales fall back to the staff user, which isn't a
    // customer profile worth counting).
    if (customerId) {
      import("@/lib/customer")
        .then(({ refreshCustomerStats }) =>
          refreshCustomerStats(String(customerId)),
        )
        .catch((err) =>
          console.error("Failed to refresh customer stats:", err),
        );
    }

    return createdResponse(order);
  } catch (error) {
    return handleApiError(error);
  }
}
