"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDashboardSummary = exports.updateAdminOrder = exports.getAdminOrderById = exports.listAdminOrders = exports.getCustomerOrderByNumber = exports.getCustomerOrderById = exports.listCustomerOrders = exports.createCustomerOrder = void 0;
const crypto_1 = __importDefault(require("crypto"));
const drizzle_orm_1 = require("drizzle-orm");
const index_js_1 = require("../db/index.js");
const schema_js_1 = require("../db/schema.js");
const orderSerializers_js_1 = require("./orderSerializers.js");
const LOW_STOCK_THRESHOLD = 5;
const generateOrderNumber = () => {
    const random = crypto_1.default.randomBytes(3).toString("hex").toUpperCase();
    return `ORD-${Date.now()}-${random}`;
};
const mergeOrderItems = (items) => {
    const map = new Map();
    for (const item of items) {
        const existing = map.get(item.productId);
        if (existing) {
            existing.quantity += item.quantity;
        }
        else {
            map.set(item.productId, {
                quantity: item.quantity,
                lensChoice: item.lensChoice ?? "none",
                prescriptionUrl: item.prescriptionUrl ?? null,
            });
        }
    }
    return [...map.entries()].map(([productId, data]) => ({ productId, ...data }));
};
const getOrderItemsByOrderIds = async (orderIds) => {
    if (!orderIds.length)
        return new Map();
    const rows = await index_js_1.db
        .select({
        orderId: schema_js_1.orderItems.orderId,
        id: schema_js_1.orderItems.id,
        productId: schema_js_1.orderItems.productId,
        productName: schema_js_1.orderItems.productName,
        sku: schema_js_1.orderItems.sku,
        quantity: schema_js_1.orderItems.quantity,
        unitPrice: schema_js_1.orderItems.unitPrice,
        discountAmount: schema_js_1.orderItems.discountAmount,
        lineTotal: schema_js_1.orderItems.lineTotal,
        lensChoice: schema_js_1.orderItems.lensChoice,
        prescriptionUrl: schema_js_1.orderItems.prescriptionUrl,
    })
        .from(schema_js_1.orderItems)
        .where((0, drizzle_orm_1.inArray)(schema_js_1.orderItems.orderId, orderIds))
        .orderBy((0, drizzle_orm_1.asc)(schema_js_1.orderItems.id));
    const map = new Map();
    for (const row of rows) {
        const list = map.get(row.orderId) ?? [];
        list.push({
            id: row.id,
            productId: row.productId,
            productName: row.productName,
            sku: row.sku,
            quantity: row.quantity,
            unitPrice: row.unitPrice,
            discountAmount: row.discountAmount,
            lineTotal: row.lineTotal,
            lensChoice: row.lensChoice,
            prescriptionUrl: row.prescriptionUrl,
        });
        map.set(row.orderId, list);
    }
    return map;
};
const getItemCountsByOrderIds = async (orderIds) => {
    if (!orderIds.length)
        return new Map();
    const rows = await index_js_1.db
        .select({ orderId: schema_js_1.orderItems.orderId, itemCount: (0, drizzle_orm_1.sql) `coalesce(sum(${schema_js_1.orderItems.quantity}), 0)` })
        .from(schema_js_1.orderItems)
        .where((0, drizzle_orm_1.inArray)(schema_js_1.orderItems.orderId, orderIds))
        .groupBy(schema_js_1.orderItems.orderId);
    return new Map(rows.map((row) => [row.orderId, Number(row.itemCount)]));
};
const createCustomerOrder = async (customerId, input) => {
    const [customer] = await index_js_1.db.select().from(schema_js_1.customers).where((0, drizzle_orm_1.eq)(schema_js_1.customers.id, customerId)).limit(1);
    if (!customer)
        throw new Error("Customer not found.");
    const contactPhone = input.contactPhone ?? customer.phoneNumber;
    if (!contactPhone)
        throw new Error("Contact phone is required before placing an order.");
    const mergedItems = mergeOrderItems(input.items);
    const productIds = mergedItems.map((item) => item.productId);
    const productRows = await index_js_1.db.select().from(schema_js_1.products).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.inArray)(schema_js_1.products.id, productIds), (0, drizzle_orm_1.eq)(schema_js_1.products.status, "active")));
    const productMap = new Map(productRows.map((product) => [product.id, product]));
    if (productRows.length !== productIds.length) {
        throw new Error("One or more products are unavailable.");
    }
    const lineItems = mergedItems.map((item) => {
        const product = productMap.get(item.productId);
        if (!product)
            throw new Error("One or more products are unavailable.");
        if (product.stockQuantity < item.quantity) {
            throw new Error(`${product.name} has only ${product.stockQuantity} item(s) in stock.`);
        }
        const baseLineTotal = product.basePrice * item.quantity;
        const lineTotal = product.salePrice * item.quantity;
        const discountAmount = baseLineTotal - lineTotal;
        return {
            productId: product.id,
            productName: product.name,
            sku: product.sku,
            quantity: item.quantity,
            unitPrice: product.salePrice,
            discountAmount,
            lineTotal,
            lensChoice: item.lensChoice ?? "none",
            prescriptionUrl: item.prescriptionUrl ?? null,
        };
    });
    const subtotal = lineItems.reduce((sum, item) => sum + item.lineTotal + item.discountAmount, 0);
    const totalDiscount = lineItems.reduce((sum, item) => sum + item.discountAmount, 0);
    const totalAmount = lineItems.reduce((sum, item) => sum + item.lineTotal, 0);
    const orderNumber = generateOrderNumber();
    await index_js_1.db.transaction(async (tx) => {
        if (input.contactPhone && input.contactPhone !== customer.phoneNumber) {
            await tx.update(schema_js_1.customers).set({ phoneNumber: input.contactPhone }).where((0, drizzle_orm_1.eq)(schema_js_1.customers.id, customer.id));
        }
        await tx.insert(schema_js_1.orders).values({
            orderNumber,
            customerId: customer.id,
            status: "pending",
            fulfillmentMethod: input.fulfillmentMethod,
            contactPhone,
            deliveryAddress: input.fulfillmentMethod === "delivery" ? input.deliveryAddress : null,
            subtotal,
            totalDiscount,
            totalAmount,
            paymentMethod: input.paymentMethod,
            paymentStatus: "pending",
            customerNotes: input.customerNotes,
        });
        const [created] = await tx.select({ id: schema_js_1.orders.id }).from(schema_js_1.orders).where((0, drizzle_orm_1.eq)(schema_js_1.orders.orderNumber, orderNumber)).limit(1);
        if (!created)
            throw new Error("Order creation failed.");
        await tx.insert(schema_js_1.orderItems).values(lineItems.map((item) => ({ orderId: created.id, ...item })));
        for (const item of lineItems) {
            await tx
                .update(schema_js_1.products)
                .set({ stockQuantity: (0, drizzle_orm_1.sql) `${schema_js_1.products.stockQuantity} - ${item.quantity}` })
                .where((0, drizzle_orm_1.eq)(schema_js_1.products.id, item.productId));
        }
    });
    const order = await (0, exports.getCustomerOrderByNumber)(customerId, orderNumber);
    if (!order)
        throw new Error("Order creation failed.");
    return order;
};
exports.createCustomerOrder = createCustomerOrder;
const customerOrderWhere = (customerId, query) => {
    const conditions = [(0, drizzle_orm_1.eq)(schema_js_1.orders.customerId, customerId)];
    if (query.status?.length)
        conditions.push((0, drizzle_orm_1.inArray)(schema_js_1.orders.status, query.status));
    if (query.paymentStatus?.length)
        conditions.push((0, drizzle_orm_1.inArray)(schema_js_1.orders.paymentStatus, query.paymentStatus));
    if (query.activeOnly)
        conditions.push((0, drizzle_orm_1.sql) `${schema_js_1.orders.status} not in ('completed', 'cancelled', 'refunded')`);
    return (0, drizzle_orm_1.and)(...conditions);
};
const listCustomerOrders = async (customerId, query) => {
    const where = customerOrderWhere(customerId, query);
    const offset = (query.page - 1) * query.perPage;
    const [countRows, rows] = await Promise.all([
        index_js_1.db.select({ total: (0, drizzle_orm_1.count)() }).from(schema_js_1.orders).where(where),
        index_js_1.db.select().from(schema_js_1.orders).where(where).orderBy((0, drizzle_orm_1.desc)(schema_js_1.orders.createdAt), (0, drizzle_orm_1.desc)(schema_js_1.orders.id)).limit(query.perPage).offset(offset),
    ]);
    const itemsByOrderId = await getOrderItemsByOrderIds(rows.map((row) => row.id));
    const total = countRows[0]?.total ?? 0;
    const totalPages = total === 0 ? 0 : Math.ceil(total / query.perPage);
    return {
        items: rows.map((row) => (0, orderSerializers_js_1.toPublicOrder)(row, itemsByOrderId.get(row.id) ?? [])),
        pagination: {
            page: query.page,
            perPage: query.perPage,
            total,
            totalPages,
            hasNextPage: query.page < totalPages,
            hasPreviousPage: query.page > 1,
        },
    };
};
exports.listCustomerOrders = listCustomerOrders;
const getCustomerOrderById = async (customerId, orderId) => {
    const [order] = await index_js_1.db.select().from(schema_js_1.orders).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_js_1.orders.customerId, customerId), (0, drizzle_orm_1.eq)(schema_js_1.orders.id, orderId))).limit(1);
    if (!order)
        return null;
    const items = await getOrderItemsByOrderIds([order.id]);
    return (0, orderSerializers_js_1.toPublicOrder)(order, items.get(order.id) ?? []);
};
exports.getCustomerOrderById = getCustomerOrderById;
const getCustomerOrderByNumber = async (customerId, orderNumber) => {
    const [order] = await index_js_1.db.select().from(schema_js_1.orders).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_js_1.orders.customerId, customerId), (0, drizzle_orm_1.eq)(schema_js_1.orders.orderNumber, orderNumber))).limit(1);
    if (!order)
        return null;
    const items = await getOrderItemsByOrderIds([order.id]);
    return (0, orderSerializers_js_1.toPublicOrder)(order, items.get(order.id) ?? []);
};
exports.getCustomerOrderByNumber = getCustomerOrderByNumber;
const adminOrderWhere = (query) => {
    const conditions = [];
    if (query.status?.length)
        conditions.push((0, drizzle_orm_1.inArray)(schema_js_1.orders.status, query.status));
    if (query.paymentStatus?.length)
        conditions.push((0, drizzle_orm_1.inArray)(schema_js_1.orders.paymentStatus, query.paymentStatus));
    if (query.fulfillmentMethod?.length)
        conditions.push((0, drizzle_orm_1.inArray)(schema_js_1.orders.fulfillmentMethod, query.fulfillmentMethod));
    if (query.from)
        conditions.push((0, drizzle_orm_1.gte)(schema_js_1.orders.createdAt, query.from));
    if (query.to)
        conditions.push((0, drizzle_orm_1.lte)(schema_js_1.orders.createdAt, query.to));
    if (query.q) {
        const pattern = `%${query.q}%`;
        conditions.push((0, drizzle_orm_1.or)((0, drizzle_orm_1.like)(schema_js_1.orders.orderNumber, pattern), (0, drizzle_orm_1.like)(schema_js_1.orders.contactPhone, pattern), (0, drizzle_orm_1.like)(schema_js_1.customers.email, pattern), (0, drizzle_orm_1.like)(schema_js_1.customers.name, pattern)));
    }
    return conditions.length ? (0, drizzle_orm_1.and)(...conditions) : undefined;
};
const adminOrderSort = (sort) => {
    switch (sort) {
        case "oldest":
            return [(0, drizzle_orm_1.asc)(schema_js_1.orders.createdAt), (0, drizzle_orm_1.asc)(schema_js_1.orders.id)];
        case "amount_desc":
            return [(0, drizzle_orm_1.desc)(schema_js_1.orders.totalAmount), (0, drizzle_orm_1.desc)(schema_js_1.orders.id)];
        case "amount_asc":
            return [(0, drizzle_orm_1.asc)(schema_js_1.orders.totalAmount), (0, drizzle_orm_1.asc)(schema_js_1.orders.id)];
        case "newest":
        default:
            return [(0, drizzle_orm_1.desc)(schema_js_1.orders.createdAt), (0, drizzle_orm_1.desc)(schema_js_1.orders.id)];
    }
};
const adminOrderSelect = {
    order: schema_js_1.orders,
    customer: {
        id: schema_js_1.customers.id,
        email: schema_js_1.customers.email,
        name: schema_js_1.customers.name,
        phoneNumber: schema_js_1.customers.phoneNumber,
    },
    staff: {
        id: schema_js_1.staffUsers.id,
        name: schema_js_1.staffUsers.name,
        email: schema_js_1.staffUsers.email,
    },
};
const getAdminOrderRows = async (query) => {
    const where = adminOrderWhere(query);
    return index_js_1.db
        .select(adminOrderSelect)
        .from(schema_js_1.orders)
        .innerJoin(schema_js_1.customers, (0, drizzle_orm_1.eq)(schema_js_1.orders.customerId, schema_js_1.customers.id))
        .leftJoin(schema_js_1.staffUsers, (0, drizzle_orm_1.eq)(schema_js_1.orders.managedByStaffId, schema_js_1.staffUsers.id))
        .where(where)
        .orderBy(...adminOrderSort(query.sort))
        .limit(query.perPage)
        .offset((query.page - 1) * query.perPage);
};
const listAdminOrders = async (query) => {
    const where = adminOrderWhere(query);
    const [countRows, rows] = await Promise.all([
        index_js_1.db.select({ total: (0, drizzle_orm_1.count)() }).from(schema_js_1.orders).innerJoin(schema_js_1.customers, (0, drizzle_orm_1.eq)(schema_js_1.orders.customerId, schema_js_1.customers.id)).where(where),
        getAdminOrderRows(query),
    ]);
    const itemCounts = await getItemCountsByOrderIds(rows.map((row) => row.order.id));
    const items = rows.map((row) => (0, orderSerializers_js_1.toAdminOrderListItem)(row.order, row.customer, row.staff?.id ? row.staff : null, itemCounts.get(row.order.id) ?? 0));
    const total = countRows[0]?.total ?? 0;
    const totalPages = total === 0 ? 0 : Math.ceil(total / query.perPage);
    return {
        items,
        pagination: {
            page: query.page,
            perPage: query.perPage,
            total,
            totalPages,
            hasNextPage: query.page < totalPages,
            hasPreviousPage: query.page > 1,
        },
    };
};
exports.listAdminOrders = listAdminOrders;
const getAdminOrderById = async (orderId) => {
    const [row] = await index_js_1.db
        .select(adminOrderSelect)
        .from(schema_js_1.orders)
        .innerJoin(schema_js_1.customers, (0, drizzle_orm_1.eq)(schema_js_1.orders.customerId, schema_js_1.customers.id))
        .leftJoin(schema_js_1.staffUsers, (0, drizzle_orm_1.eq)(schema_js_1.orders.managedByStaffId, schema_js_1.staffUsers.id))
        .where((0, drizzle_orm_1.eq)(schema_js_1.orders.id, orderId))
        .limit(1);
    if (!row)
        return null;
    const [itemsByOrderId, itemCounts] = await Promise.all([
        getOrderItemsByOrderIds([row.order.id]),
        getItemCountsByOrderIds([row.order.id]),
    ]);
    const listItem = (0, orderSerializers_js_1.toAdminOrderListItem)(row.order, row.customer, row.staff?.id ? row.staff : null, itemCounts.get(row.order.id) ?? 0);
    return (0, orderSerializers_js_1.toAdminOrderDetail)(listItem, itemsByOrderId.get(row.order.id) ?? [], row.order.staffNotes);
};
exports.getAdminOrderById = getAdminOrderById;
const updateAdminOrder = async (orderId, input, staffId) => {
    const [existing] = await index_js_1.db.select({ id: schema_js_1.orders.id }).from(schema_js_1.orders).where((0, drizzle_orm_1.eq)(schema_js_1.orders.id, orderId)).limit(1);
    if (!existing)
        return null;
    await index_js_1.db
        .update(schema_js_1.orders)
        .set({
        ...(input.status !== undefined ? { status: input.status } : {}),
        ...(input.paymentStatus !== undefined ? { paymentStatus: input.paymentStatus } : {}),
        ...(input.staffNotes !== undefined ? { staffNotes: input.staffNotes } : {}),
        managedByStaffId: input.managedByStaffId === undefined ? staffId : input.managedByStaffId,
    })
        .where((0, drizzle_orm_1.eq)(schema_js_1.orders.id, orderId));
    return (0, exports.getAdminOrderById)(orderId);
};
exports.updateAdminOrder = updateAdminOrder;
const getDashboardSummary = async () => {
    const recentOrderQuery = { page: 1, perPage: 8, sort: "newest" };
    const [totalOrdersRows, pendingOrdersRows, completedOrdersRows, customersRows, productsRows, activeProductsRows, lowStockRows, revenueRows, statusRows, paymentRows, recentOrders, lowStockProducts, topProducts,] = await Promise.all([
        index_js_1.db.select({ total: (0, drizzle_orm_1.count)() }).from(schema_js_1.orders),
        index_js_1.db.select({ total: (0, drizzle_orm_1.count)() }).from(schema_js_1.orders).where((0, drizzle_orm_1.eq)(schema_js_1.orders.status, "pending")),
        index_js_1.db.select({ total: (0, drizzle_orm_1.count)() }).from(schema_js_1.orders).where((0, drizzle_orm_1.eq)(schema_js_1.orders.status, "completed")),
        index_js_1.db.select({ total: (0, drizzle_orm_1.count)() }).from(schema_js_1.customers),
        index_js_1.db.select({ total: (0, drizzle_orm_1.count)() }).from(schema_js_1.products),
        index_js_1.db.select({ total: (0, drizzle_orm_1.count)() }).from(schema_js_1.products).where((0, drizzle_orm_1.eq)(schema_js_1.products.status, "active")),
        index_js_1.db.select({ total: (0, drizzle_orm_1.count)() }).from(schema_js_1.products).where((0, drizzle_orm_1.lte)(schema_js_1.products.stockQuantity, LOW_STOCK_THRESHOLD)),
        index_js_1.db.select({ revenue: (0, drizzle_orm_1.sql) `coalesce(sum(${schema_js_1.orders.totalAmount}), 0)` }).from(schema_js_1.orders).where((0, drizzle_orm_1.eq)(schema_js_1.orders.status, "completed")),
        index_js_1.db.select({ status: schema_js_1.orders.status, count: (0, drizzle_orm_1.count)() }).from(schema_js_1.orders).groupBy(schema_js_1.orders.status),
        index_js_1.db.select({ status: schema_js_1.orders.paymentStatus, count: (0, drizzle_orm_1.count)() }).from(schema_js_1.orders).groupBy(schema_js_1.orders.paymentStatus),
        (0, exports.listAdminOrders)(recentOrderQuery),
        index_js_1.db
            .select({
            id: schema_js_1.products.id,
            sku: schema_js_1.products.sku,
            name: schema_js_1.products.name,
            slug: schema_js_1.products.slug,
            stockQuantity: schema_js_1.products.stockQuantity,
            status: schema_js_1.products.status,
        })
            .from(schema_js_1.products)
            .where((0, drizzle_orm_1.lte)(schema_js_1.products.stockQuantity, LOW_STOCK_THRESHOLD))
            .orderBy((0, drizzle_orm_1.asc)(schema_js_1.products.stockQuantity), (0, drizzle_orm_1.asc)(schema_js_1.products.name))
            .limit(10),
        index_js_1.db
            .select({
            productId: schema_js_1.orderItems.productId,
            productName: schema_js_1.orderItems.productName,
            sku: schema_js_1.orderItems.sku,
            quantitySold: (0, drizzle_orm_1.sql) `coalesce(sum(${schema_js_1.orderItems.quantity}), 0)`,
            revenue: (0, drizzle_orm_1.sql) `coalesce(sum(${schema_js_1.orderItems.lineTotal}), 0)`,
        })
            .from(schema_js_1.orderItems)
            .groupBy(schema_js_1.orderItems.productId, schema_js_1.orderItems.productName, schema_js_1.orderItems.sku)
            .orderBy((0, drizzle_orm_1.desc)((0, drizzle_orm_1.sql) `sum(${schema_js_1.orderItems.quantity})`))
            .limit(10),
    ]);
    return {
        totals: {
            orders: totalOrdersRows[0]?.total ?? 0,
            pendingOrders: pendingOrdersRows[0]?.total ?? 0,
            completedOrders: completedOrdersRows[0]?.total ?? 0,
            customers: customersRows[0]?.total ?? 0,
            products: productsRows[0]?.total ?? 0,
            activeProducts: activeProductsRows[0]?.total ?? 0,
            lowStockProducts: lowStockRows[0]?.total ?? 0,
            revenue: Number(revenueRows[0]?.revenue ?? 0),
        },
        orderStatusBreakdown: statusRows.map((row) => ({ status: row.status, count: row.count })),
        paymentStatusBreakdown: paymentRows.map((row) => ({ status: row.status, count: row.count })),
        recentOrders: recentOrders.items,
        lowStockProducts,
        topProducts: topProducts.map((row) => ({
            productId: row.productId,
            productName: row.productName,
            sku: row.sku,
            quantitySold: Number(row.quantitySold),
            revenue: Number(row.revenue),
        })),
    };
};
exports.getDashboardSummary = getDashboardSummary;
//# sourceMappingURL=orderService.js.map