// @WHO:   vps-deploy/api/kds/src/routes/purchasing.ts
// @WHAT:  Supplier + PurchaseOrder CRUD routes — procurement workflow
// @PART:  api
// @ENTRY: purchasingRouter

import { Router } from 'express';
import { prisma } from '../lib/prisma';
import { authMiddleware } from '../middleware/auth';
import { getIO } from '../lib/socket';

export const purchasingRouter = Router();

// All purchasing routes require auth
purchasingRouter.use(authMiddleware);

// ── Suppliers ──

// @FLOW:     SUPPLIER_LIST
// @CALLS:    DB:suppliers SELECT
// @MUTATES:  none
purchasingRouter.get('/outlets/:id/suppliers', async (req, res) => {
  try {
    const suppliers = await prisma.supplier.findMany({
      where: { outletId: req.params.id },
      orderBy: { name: 'asc' },
      include: { _count: { select: { purchaseOrders: true } } },
    });

    res.json(suppliers);
  } catch (err: any) {
    res.status(500).json({ error: 'Failed to fetch suppliers', message: err.message });
  }
});

// @FLOW:     SUPPLIER_CREATE
// @CALLS:    DB:suppliers INSERT
// @MUTATES:  DB:suppliers (INSERT)
purchasingRouter.post('/outlets/:id/suppliers', async (req, res) => {
  try {
    const { name, contact, address, isActive } = req.body;
    if (!name) return res.status(400).json({ error: 'name is required' });

    const supplier = await prisma.supplier.create({
      data: {
        outletId: req.params.id,
        name,
        contact: contact || null,
        address: address || null,
        isActive: isActive !== false,
      },
    });

    res.status(201).json(supplier);
  } catch (err: any) {
    res.status(500).json({ error: 'Failed to create supplier', message: err.message });
  }
});

// @FLOW:     SUPPLIER_UPDATE
// @CALLS:    DB:suppliers UPDATE
// @MUTATES:  DB:suppliers (UPDATE)
purchasingRouter.put('/outlets/:id/suppliers/:supplierId', async (req, res) => {
  try {
    const { name, contact, address, isActive } = req.body;

    const supplier = await prisma.supplier.update({
      where: { id: req.params.supplierId },
      data: {
        ...(name !== undefined && { name }),
        ...(contact !== undefined && { contact }),
        ...(address !== undefined && { address }),
        ...(isActive !== undefined && { isActive }),
      },
    });

    res.json(supplier);
  } catch (err: any) {
    res.status(500).json({ error: 'Failed to update supplier', message: err.message });
  }
});

// @FLOW:     SUPPLIER_DELETE
// @CALLS:    DB:suppliers DELETE
// @MUTATES:  DB:suppliers (DELETE)
purchasingRouter.delete('/outlets/:id/suppliers/:supplierId', async (req, res) => {
  try {
    await prisma.supplier.delete({ where: { id: req.params.supplierId } });
    res.json({ deleted: true });
  } catch (err: any) {
    res.status(500).json({ error: 'Failed to delete supplier', message: err.message });
  }
});

// ── Purchase Orders ──

// @FLOW:     PO_LIST
// @CALLS:    DB:purchase_orders SELECT (filtered by status)
// @MUTATES:  none
purchasingRouter.get('/outlets/:id/purchase-orders', async (req, res) => {
  try {
    const { status } = req.query;
    const where: any = { outletId: req.params.id };
    if (status) where.status = status as string;

    const purchaseOrders = await prisma.purchaseOrder.findMany({
      where,
      orderBy: { createdAt: 'desc' },
      include: {
        supplier: { select: { id: true, name: true, contact: true } },
        items: { include: { ingredient: { select: { id: true, name: true, unit: true } } } },
      },
    });

    res.json(purchaseOrders);
  } catch (err: any) {
    res.status(500).json({ error: 'Failed to fetch purchase orders', message: err.message });
  }
});

// @FLOW:     PO_CREATE
// @CALLS:    DB:purchase_orders INSERT, DB:po_items INSERT (nested)
// @MUTATES:  DB:purchase_orders (INSERT), DB:po_items (INSERT)
purchasingRouter.post('/outlets/:id/purchase-orders', async (req, res) => {
  try {
    const { supplierId, notes, items } = req.body;

    if (!Array.isArray(items) || items.length === 0) {
      return res.status(400).json({ error: 'items array with at least one item is required' });
    }

    // Validate each item
    for (let i = 0; i < items.length; i++) {
      const item = items[i];
      if (!item.ingredientId || item.quantity === undefined || item.quantity <= 0) {
        return res.status(400).json({
          error: `Item at index ${i} is invalid: ingredientId and positive quantity are required`,
        });
      }
      if (item.pricePerUnit === undefined || item.pricePerUnit < 0) {
        return res.status(400).json({
          error: `Item at index ${i} is invalid: pricePerUnit is required and must be non-negative`,
        });
      }
    }

    // Calculate total cost
    const totalCost = items.reduce(
      (sum: number, item: any) => sum + item.quantity * item.pricePerUnit,
      0,
    );

    const purchaseOrder = await prisma.purchaseOrder.create({
      data: {
        outletId: req.params.id,
        supplierId: supplierId || null,
        notes: notes || null,
        totalCost,
        items: {
          create: items.map((item: any) => ({
            ingredientId: item.ingredientId,
            quantity: item.quantity,
            pricePerUnit: item.pricePerUnit,
            receivedQty: 0,
          })),
        },
      },
      include: {
        supplier: { select: { id: true, name: true, contact: true } },
        items: { include: { ingredient: { select: { id: true, name: true, unit: true } } } },
      },
    });

    res.status(201).json(purchaseOrder);
  } catch (err: any) {
    res.status(500).json({ error: 'Failed to create purchase order', message: err.message });
  }
});

// @FLOW:     PO_UPDATE
// @CALLS:    DB:purchase_orders UPDATE, DB:po_items UPDATE,
//            DB:outlet_ingredients UPSERT, DB:stock_logs INSERT (if RECEIVED)
// @MUTATES:  DB:purchase_orders (UPDATE status), DB:po_items (UPDATE receivedQty),
//            DB:outlet_ingredients (UPSERT stock/lastCostPerUnit), DB:stock_logs (INSERT)
// @BEHAVIOR: When status changes to RECEIVED, auto-stock-in each POItem:
//            upserts OutletIngredient (increment stock by receivedQty),
//            creates StockLog entries (type: IN, source: PO_RECEIVED),
//            updates POItem.receivedQty and OutletIngredient.lastCostPerUnit,
//            emits stock:updated via Socket.IO
purchasingRouter.put('/outlets/:id/purchase-orders/:poId', async (req, res) => {
  try {
    const outletId = req.params.id;
    const poId = req.params.poId;
    const { status, notes, items } = req.body;

    // Fetch existing PO
    const existingPO = await prisma.purchaseOrder.findUnique({
      where: { id: poId },
      include: {
        items: { include: { ingredient: true } },
      },
    });

    if (!existingPO) {
      return res.status(404).json({ error: 'Purchase order not found' });
    }

    if (existingPO.outletId !== outletId) {
      return res.status(403).json({ error: 'Purchase order does not belong to this outlet' });
    }

    // If status is changing to RECEIVED, perform auto-stock-in
    if (status === 'RECEIVED' && existingPO.status !== 'RECEIVED') {
      // Items payload from request (contains receivedQty per item)
      // If items not provided, default receivedQty = ordered quantity
      const itemUpdates = Array.isArray(items) ? items : [];

      const itemUpdateMap = new Map(
        itemUpdates.map((item: any) => [item.id, item]),
      );

      // Run auto-stock-in in a transaction
      const result = await prisma.$transaction(async (tx) => {
        // Update PO status
        const updatedPO = await tx.purchaseOrder.update({
          where: { id: poId },
          data: {
            status: 'RECEIVED',
            ...(notes !== undefined && { notes }),
          },
          include: {
            supplier: { select: { id: true, name: true, contact: true } },
            items: { include: { ingredient: { select: { id: true, name: true, unit: true } } } },
          },
        });

        const stockLogs = [];

        // Process each PO item
        for (const poItem of existingPO.items) {
          const update = itemUpdateMap.get(poItem.id);
          const receivedQty = update?.receivedQty ?? poItem.quantity;

          if (receivedQty <= 0) continue;

          // Upsert OutletIngredient — increment stock
          const outletIngredient = await tx.outletIngredient.upsert({
            where: {
              outletId_ingredientId: {
                outletId,
                ingredientId: poItem.ingredientId,
              },
            },
            create: {
              outletId,
              ingredientId: poItem.ingredientId,
              stock: receivedQty,
              minStock: 0,
              parLevel: 0,
              lastCostPerUnit: poItem.pricePerUnit,
              autoDeduct: false,
            },
            update: {
              stock: { increment: receivedQty },
              lastCostPerUnit: poItem.pricePerUnit,
            },
          });

          // Create StockLog entry
          const stockLog = await tx.stockLog.create({
            data: {
              outletId,
              ingredientId: poItem.ingredientId,
              type: 'IN',
              quantity: receivedQty,
              note: `PO ${existingPO.id} received`,
              source: 'PO_RECEIVED',
              costImpact: receivedQty * poItem.pricePerUnit,
            },
          });

          stockLogs.push({ outletIngredient, stockLog });

          // Update POItem receivedQty
          await tx.pOItem.update({
            where: { id: poItem.id },
            data: { receivedQty },
          });
        }

        return { updatedPO, stockLogs };
      });

      // Emit stock:updated via Socket.IO for each ingredient
      const io = getIO();
      if (io) {
        for (const { outletIngredient, stockLog } of result.stockLogs) {
          io.to(`outlet:${outletId}:stock`).emit('stock:updated', {
            outletId,
            ingredientId: outletIngredient.ingredientId,
            newStock: outletIngredient.stock,
            type: 'IN',
            quantity: stockLog.quantity,
            source: 'PO_RECEIVED',
            stockLogId: stockLog.id,
            purchaseOrderId: poId,
          });

          // Check for low-stock alert (unlikely on stock-in, but possible if still below min)
          if (outletIngredient.stock <= outletIngredient.minStock) {
            const ingredient = await prisma.ingredient.findUnique({
              where: { id: outletIngredient.ingredientId },
              select: { name: true, unit: true },
            });
            if (ingredient) {
              io.to(`outlet:${outletId}:stock`).emit('stock:alert', {
                outletId,
                ingredientId: outletIngredient.ingredientId,
                ingredientName: ingredient.name,
                currentStock: outletIngredient.stock,
                minStock: outletIngredient.minStock,
                unit: ingredient.unit,
                type: 'LOW_STOCK',
              });
            }
          }
        }
      }

      return res.json(result.updatedPO);
    }

    // Non-RECEIVED update — just update status/notes
    const updatedPO = await prisma.purchaseOrder.update({
      where: { id: poId },
      data: {
        ...(status !== undefined && { status }),
        ...(notes !== undefined && { notes }),
      },
      include: {
        supplier: { select: { id: true, name: true, contact: true } },
        items: { include: { ingredient: { select: { id: true, name: true, unit: true } } } },
      },
    });

    res.json(updatedPO);
  } catch (err: any) {
    res.status(500).json({ error: 'Failed to update purchase order', message: err.message });
  }
});
