const mongoose = require('mongoose');
const { DEFAULT_CURRENCY_CODE } = require('@/config/platformDefaults');
const { toCsv } = require('@/services/platform/exporter');
const { createNotification } = require('@/services/notifications/service');

const getModel = (name) => mongoose.model(name);

const buildMoney = (amount = 0, currency = DEFAULT_CURRENCY_CODE) => ({
  amount: Number(amount || 0),
  currency,
});

const formatDateKey = (value = new Date()) => new Date(value).toISOString().slice(0, 10);

const scope = (req, extra = {}) => ({
  tenant: req.tenantId,
  removed: false,
  ...extra,
});

const ensureBranchId = (req, payload = {}) =>
  payload.branch || req.body?.branch || req.query?.branch || req.admin?.branches?.[0]?._id || req.admin?.branches?.[0];

const currentSession = async (req, res) => {
  const PosSession = getModel('PosSession');
  const branchId = ensureBranchId(req, req.query);
  const result = await PosSession.findOne(
    scope(req, {
      cashier: req.admin?._id,
      branch: branchId,
      status: 'open',
    })
  )
    .sort({ created: -1 })
    .lean();

  return res.status(200).json({ success: true, result, message: 'Current POS session loaded' });
};

const openSession = async (req, res) => {
  const PosSession = getModel('PosSession');
  const branchId = ensureBranchId(req, req.body);
  if (!branchId) {
    return res.status(400).json({ success: false, result: null, message: 'Branch is required to open a POS session' });
  }

  const existing = await PosSession.findOne(
    scope(req, {
      cashier: req.admin?._id,
      branch: branchId,
      status: 'open',
    })
  );

  if (existing) {
    return res.status(200).json({ success: true, result: existing, message: 'POS session already open' });
  }

  const result = await PosSession.create({
    tenant: req.tenantId,
    createdBy: req.admin?._id,
    assignedTo: req.admin?._id,
    cashier: req.admin?._id,
    branch: branchId,
    openingNote: req.body?.openingNote,
    totals: { sales: buildMoney(0, req.body?.currency || DEFAULT_CURRENCY_CODE), transactions: 0 },
  });

  return res.status(201).json({ success: true, result, message: 'POS session opened' });
};

const closeSession = async (req, res) => {
  const PosSession = getModel('PosSession');
  const posSession = await PosSession.findOne(
    scope(req, {
      _id: req.params.id,
      cashier: req.admin?._id,
      status: 'open',
    })
  );

  if (!posSession) {
    return res.status(404).json({ success: false, result: null, message: 'Open POS session not found' });
  }

  posSession.status = 'closed';
  posSession.closingNote = req.body?.closingNote;
  posSession.closedAt = new Date();
  await posSession.save();

  await createNotification({
    tenantId: req.tenantId,
    actorId: req.admin?._id,
    recipientId: req.admin?._id,
    type: 'pos.session.closed',
    title: 'POS session closed',
    message: `Session closed with ${posSession.totals?.transactions || 0} transactions.`,
    severity: 'info',
    entityType: 'PosSession',
    entityId: posSession._id,
    actionUrl: '/pos/reports',
    eventKey: `pos-session-close:${posSession._id}`,
  });

  return res.status(200).json({ success: true, result: posSession, message: 'POS session closed' });
};

const checkout = async (req, res) => {
  const PosSession = getModel('PosSession');
  const PosSale = getModel('PosSale');
  const PosReceipt = getModel('PosReceipt');
  const InventoryStock = getModel('InventoryStock');
  const StockMovement = getModel('StockMovement');
  const Product = getModel('Product');
  const Branch = getModel('Branch');

  const branchId = ensureBranchId(req, req.body);
  const items = Array.isArray(req.body?.items) ? req.body.items.filter((item) => item?.product && Number(item.quantity) > 0) : [];

  if (!branchId) {
    return res.status(400).json({ success: false, result: null, message: 'Branch is required for checkout' });
  }

  if (items.length === 0) {
    return res.status(400).json({ success: false, result: null, message: 'At least one valid cart item is required' });
  }

  const session = await PosSession.findOne(
    scope(req, {
      cashier: req.admin?._id,
      branch: branchId,
      status: 'open',
    })
  );

  if (!session) {
    return res.status(400).json({ success: false, result: null, message: 'Open a cashier session before checkout' });
  }

  const branch = await Branch.findOne(scope(req, { _id: branchId })).lean();
  const productIds = items.map((item) => item.product);
  const [products, stocks] = await Promise.all([
    Product.find({ _id: { $in: productIds }, removed: false }).lean(),
    InventoryStock.find({ tenant: req.tenantId, branch: branchId, product: { $in: productIds }, removed: false }),
  ]);

  const productMap = new Map(products.map((product) => [String(product._id), product]));
  const stockMap = new Map(stocks.map((stock) => [String(stock.product), stock]));

  const normalizedItems = [];
  for (const item of items) {
    const product = productMap.get(String(item.product));
    const stock = stockMap.get(String(item.product));
    const quantity = Number(item.quantity || 0);
    if (!product || !stock) {
      return res.status(400).json({
        success: false,
        result: null,
        message: 'One or more products are unavailable in the selected branch',
      });
    }

    if (Number(stock.onHand || 0) < quantity) {
      await createNotification({
        tenantId: req.tenantId,
        actorId: req.admin?._id,
        recipientId: req.admin?._id,
        type: 'pos.checkout.failed',
        title: 'POS checkout blocked',
        message: `${product.name} is out of stock in ${branch?.name || 'this branch'}.`,
        severity: 'warning',
        entityType: 'Product',
        entityId: product._id,
        actionUrl: '/pos',
        eventKey: `pos-checkout-failed:${branchId}:${product._id}:${formatDateKey()}`,
      });
      return res.status(400).json({
        success: false,
        result: null,
        message: `${product.name} has insufficient stock for this sale`,
      });
    }

    const unitPrice = Number(item.unitPrice || product.salePrice || 0);
    normalizedItems.push({
      product: product._id,
      name: product.name,
      sku: product.sku || product.code || String(product._id).slice(-6),
      quantity,
      unitPrice,
      total: unitPrice * quantity,
    });
  }

  const currency = req.body?.currency || DEFAULT_CURRENCY_CODE;
  const subtotal = normalizedItems.reduce((sum, item) => sum + item.total, 0);
  const total = subtotal;
  const receiptNumber = `POS-${Date.now()}`;

  const sale = await PosSale.create({
    tenant: req.tenantId,
    createdBy: req.admin?._id,
    assignedTo: req.admin?._id,
    session: session._id,
    branch: branchId,
    cashier: req.admin?._id,
    customer: req.body?.customer || undefined,
    receiptNumber,
    items: normalizedItems,
    subtotal: buildMoney(subtotal, currency),
    total: buildMoney(total, currency),
    paymentSummary: {
      mode: req.body?.paymentMode || 'cash',
      reference: req.body?.paymentReference,
      received: buildMoney(req.body?.amountReceived || total, currency),
      change: buildMoney(Math.max(Number(req.body?.amountReceived || total) - total, 0), currency),
    },
    notes: req.body?.notes,
    soldAt: new Date(),
  });

  for (const item of normalizedItems) {
    const stock = stockMap.get(String(item.product));
    const beforeQuantity = Number(stock.onHand || 0);
    const afterQuantity = beforeQuantity - item.quantity;
    stock.onHand = afterQuantity;
    await stock.save();

    await StockMovement.create({
      tenant: req.tenantId,
      createdBy: req.admin?._id,
      assignedTo: req.admin?._id,
      branch: branchId,
      product: item.product,
      type: 'sale',
      quantity: item.quantity,
      beforeQuantity,
      afterQuantity,
      referenceType: 'PosSale',
      referenceId: sale._id,
      value: buildMoney(item.total, currency),
      notes: `POS checkout ${receiptNumber}`,
    });

    if (afterQuantity <= Number(stock.reorderLevel || 0)) {
      await createNotification({
        tenantId: req.tenantId,
        actorId: req.admin?._id,
        recipientId: req.admin?._id,
        type: 'inventory.low_stock',
        title: 'Low stock alert',
        message: `${item.name} is at ${afterQuantity} units for ${branch?.name || 'this branch'}.`,
        severity: 'warning',
        entityType: 'InventoryStock',
        entityId: stock._id,
        actionUrl: '/products',
        eventKey: `low-stock:${stock._id}:${formatDateKey()}`,
      });
    }
  }

  session.totals = {
    sales: buildMoney(Number(session.totals?.sales?.amount || 0) + total, currency),
    transactions: Number(session.totals?.transactions || 0) + 1,
  };
  await session.save();

  const receipt = await PosReceipt.create({
    tenant: req.tenantId,
    createdBy: req.admin?._id,
    assignedTo: req.admin?._id,
    sale: sale._id,
    receiptNumber,
    printable: {
      title: branch?.name || 'Store Receipt',
      branch: branch?.name,
      cashier: req.admin?.name,
      items: normalizedItems,
      total: buildMoney(total, currency),
      paymentSummary: sale.paymentSummary,
    },
    barcodeValue: normalizedItems[0]?.sku || receiptNumber,
  });

  return res.status(201).json({
    success: true,
    result: { sale, receipt, session },
    message: 'POS checkout completed',
  });
};

const saleHistory = async (req, res) => {
  const PosSale = getModel('PosSale');
  const limit = Math.min(Number(req.query?.items || 100), 250);
  const result = await PosSale.find(
    scope(req, {
      ...(req.query?.branch ? { branch: req.query.branch } : {}),
    })
  )
    .sort({ soldAt: -1 })
    .limit(limit)
    .lean();

  return res.status(200).json({ success: true, result, message: 'POS sales history loaded' });
};

const summary = async (req, res) => {
  const PosSale = getModel('PosSale');
  const startOfDay = new Date();
  startOfDay.setHours(0, 0, 0, 0);
  const sales = await PosSale.find(scope(req, { soldAt: { $gte: startOfDay } })).lean();
  const total = sales.reduce((sum, sale) => sum + Number(sale.total?.amount || 0), 0);
  const topProducts = [...sales.flatMap((sale) => sale.items || [])]
    .reduce((acc, item) => {
      const key = item.name || item.sku;
      acc[key] = (acc[key] || 0) + Number(item.quantity || 0);
      return acc;
    }, {});

  const result = {
    dailySalesTotal: buildMoney(total),
    transactionCount: sales.length,
    topProducts: Object.entries(topProducts)
      .map(([name, quantity]) => ({ name, quantity }))
      .sort((a, b) => b.quantity - a.quantity)
      .slice(0, 5),
  };

  return res.status(200).json({ success: true, result, message: 'POS summary loaded' });
};

const report = async (req, res) => {
  const PosSale = getModel('PosSale');
  const InventoryStock = getModel('InventoryStock');
  const now = new Date();
  const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
  const sales = await PosSale.find(scope(req, { soldAt: { $gte: startOfMonth } })).lean();
  const lowStock = await InventoryStock.find(
    scope(req, {
      $expr: { $lte: ['$onHand', '$reorderLevel'] },
    })
  ).lean();

  const byCashier = sales.reduce((acc, sale) => {
    const key = sale.cashier?.name || sale.cashier?.toString() || 'Unknown';
    acc[key] = acc[key] || { cashier: key, transactions: 0, total: 0 };
    acc[key].transactions += 1;
    acc[key].total += Number(sale.total?.amount || 0);
    return acc;
  }, {});

  const result = {
    monthlySalesTotal: buildMoney(sales.reduce((sum, sale) => sum + Number(sale.total?.amount || 0), 0)),
    averageBasketSize: buildMoney(
      sales.length ? sales.reduce((sum, sale) => sum + Number(sale.total?.amount || 0), 0) / sales.length : 0
    ),
    cashierPerformance: Object.values(byCashier),
    lowStockSignals: lowStock.map((item) => ({
      branch: item.branch,
      product: item.product,
      onHand: item.onHand,
      reorderLevel: item.reorderLevel,
    })),
  };

  return res.status(200).json({ success: true, result, message: 'POS report loaded' });
};

const reprintReceipt = async (req, res) => {
  const PosReceipt = getModel('PosReceipt');
  const result = await PosReceipt.findOneAndUpdate(
    scope(req, { _id: req.params.id }),
    { printedAt: new Date() },
    { new: true }
  );
  return res.status(200).json({ success: true, result, message: 'Receipt ready for reprint' });
};

const barcode = async (req, res) => {
  const Product = getModel('Product');
  const product = await Product.findOne({ _id: req.params.productId, removed: false }).lean();
  if (!product) {
    return res.status(404).json({ success: false, result: null, message: 'Product not found' });
  }

  const result = {
    productId: product._id,
    name: product.name,
    value: product.sku || product.code || String(product._id),
    printableLabel: `${product.name} (${product.sku || product.code || String(product._id).slice(-6)})`,
  };

  return res.status(200).json({ success: true, result, message: 'Barcode generated' });
};

const exportSales = async (req, res) => {
  const PosSale = getModel('PosSale');
  const rows = await PosSale.find(scope(req)).sort({ soldAt: -1 }).limit(1000).lean();
  const csv = toCsv(
    rows.map((sale) => ({
      receiptNumber: sale.receiptNumber,
      soldAt: sale.soldAt,
      cashier: sale.cashier?.name || sale.cashier,
      branch: sale.branch?.name || sale.branch,
      items: (sale.items || []).map((item) => `${item.name} x${item.quantity}`).join('; '),
      total: sale.total?.amount,
      currency: sale.total?.currency,
      paymentMode: sale.paymentSummary?.mode,
    }))
  );

  res.setHeader('Content-Type', 'text/csv');
  res.setHeader('Content-Disposition', 'attachment; filename="pos-sales.csv"');
  return res.status(200).send(csv);
};

module.exports = {
  currentSession,
  openSession,
  closeSession,
  checkout,
  saleHistory,
  summary,
  report,
  reprintReceipt,
  barcode,
  exportSales,
};
