import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";

const cleanString = (value) => String(value ?? "").trim();
const nullableString = (value) => {
  const cleaned = cleanString(value);
  return cleaned || null;
};
const nullableNumber = (value) => {
  if (value === "" || value === null || value === undefined) return null;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
};
const toNumber = (value, fallback = 0) => {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : fallback;
};
const formatDzdAmount = (value) => {
  const amount = toNumber(value);
  const sign = amount < 0 ? "-" : "";
  const [integerPart, decimalPart] = Math.abs(amount).toFixed(2).split(".");
  return `${sign}${integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, " ")},${decimalPart} DZD`;
};
const getCompanyId = (req) => {
  const companyId = Number(req.query.company_id || req.body.company_id || req.user?.companyId);
  if (!companyId) throw new ApiError(400, "Société requise.");
  return companyId;
};
const mapRef = (prefix, row) => row[`${prefix}_id`] ? ({
  id: Number(row[`${prefix}_id`]),
  code: row[`${prefix}_code`],
  label: row[`${prefix}_label`],
  color: row[`${prefix}_color`],
  icon: row[`${prefix}_icon`],
  metadata: row[`${prefix}_metadata`] || {},
}) : null;

const logMgxAudit = async (db, req, { companyId, action, entityType, entityId, oldData, newData }) => {
  await db.query(
    `
      INSERT INTO mgx_audit_log (
        company_id, user_id, action, entity_type, entity_id,
        old_data, new_data, ip_address
      )
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
    `,
    [
      companyId,
      req.user?.sub || null,
      action,
      entityType,
      entityId,
      oldData ? JSON.stringify(oldData) : null,
      newData ? JSON.stringify(newData) : null,
      req.ip,
    ],
  );
};

const itemSelect = `
  SELECT
    i.*,
    category.id AS category_id,
    category.code AS category_code,
    category.label AS category_label,
    category.color AS category_color,
    category.icon AS category_icon,
    category.metadata AS category_metadata,
    unit.id AS unit_id,
    unit.code AS unit_code,
    unit.label AS unit_label,
    unit.color AS unit_color,
    unit.icon AS unit_icon,
    unit.metadata AS unit_metadata
  FROM mgx_consumable_items i
  LEFT JOIN mgx_referentiels category ON category.id = i.category_ref_id
  LEFT JOIN mgx_referentiels unit ON unit.id = i.unit_ref_id
`;

const movementSelect = `
  SELECT
    m.*,
    i.code AS item_code,
    i.label AS item_label,
    i.min_stock_qty,
    i.current_stock_qty,
    category.id AS category_id,
    category.code AS category_code,
    category.label AS category_label,
    category.color AS category_color,
    category.icon AS category_icon,
    category.metadata AS category_metadata,
    unit.id AS unit_id,
    unit.code AS unit_code,
    unit.label AS unit_label,
    unit.color AS unit_color,
    unit.icon AS unit_icon,
    unit.metadata AS unit_metadata,
    d.code AS depot_code,
    d.label AS depot_label,
    rec.reception_number,
    pret.return_number AS purchase_return_number,
    pret.credit_note_number AS purchase_return_credit_note_number,
    intervention.invoice_number AS intervention_invoice_number,
    intervention.label AS intervention_label,
    u.full_name AS created_by_name
  FROM mgx_consumable_movements m
  JOIN mgx_consumable_items i ON i.id = m.item_id
  LEFT JOIN mgx_referentiels category ON category.id = i.category_ref_id
  LEFT JOIN mgx_referentiels unit ON unit.id = i.unit_ref_id
  LEFT JOIN mgx_depots d ON d.id = m.depot_id
  LEFT JOIN mgx_receptions rec
    ON m.reference_type = 'reception'
    AND rec.id = m.reference_id
  LEFT JOIN mgx_purchase_returns pret
    ON m.reference_type = 'purchase_return'
    AND pret.id = m.reference_id
  LEFT JOIN mgx_interventions intervention
    ON m.reference_type = 'maintenance_intervention'
    AND intervention.id = m.reference_id
  LEFT JOIN users u ON u.id = m.created_by
`;

const movementSignedQuantity = (row) => {
  const note = String(row.notes || "");
  if (row.movement_type === "in") return toNumber(row.quantity);
  if (row.movement_type === "out") return -toNumber(row.quantity);
  if (row.movement_type === "transfer") {
    if (note.startsWith("TRANSFER_IN")) return toNumber(row.quantity);
    if (note.startsWith("TRANSFER_OUT")) return -toNumber(row.quantity);
    return 0;
  }
  if (row.movement_type === "adjustment") {
    const match = note.match(/ADJUSTMENT_DIFF:([+-]?[0-9.]+)/);
    return match ? toNumber(match[1]) : 0;
  }
  return 0;
};

const movementDirectionTone = (row) => {
  const quantity = movementSignedQuantity(row);
  if (quantity > 0) return "in";
  if (quantity < 0) return "out";
  return "adjustment";
};

const stockStatus = (qty, minQty) => {
  const current = toNumber(qty);
  const threshold = toNumber(minQty);
  if (current <= 0) return { key: "rupture", label: "Rupture", tone: "danger" };
  if (current < threshold) return { key: "low", label: "Faible", tone: "warning" };
  return { key: "normal", label: "Normal", tone: "success" };
};

const mapItem = (row, overrides = {}) => {
  const qty = overrides.stock_qty ?? row.current_stock_qty;
  const itemValue = toNumber(qty) * toNumber(row.unit_price);
  return {
    id: Number(row.id),
    company_id: Number(row.company_id),
    code: row.code,
    label: row.label,
    category_ref_id: row.category_ref_id ? Number(row.category_ref_id) : null,
    category: mapRef("category", row),
    unit_ref_id: row.unit_ref_id ? Number(row.unit_ref_id) : null,
    unit: mapRef("unit", row),
    is_consumable: row.is_consumable !== false,
    unit_price: toNumber(row.unit_price),
    unit_price_formatted: formatDzdAmount(row.unit_price),
    min_stock_qty: toNumber(row.min_stock_qty),
    current_stock_qty: toNumber(qty),
    stock_value: itemValue,
    stock_value_formatted: formatDzdAmount(itemValue),
    is_active: Boolean(row.is_active),
    status: stockStatus(qty, row.min_stock_qty),
    created_at: row.created_at,
    updated_at: row.updated_at,
  };
};

const mapMovement = (row) => {
  const signedQty = movementSignedQuantity(row);
  const referenceLabel = (() => {
    if (row.reference_type === "reception") return row.reception_number || `Réception #${row.reference_id}`;
    if (row.reference_type === "purchase_return") {
      return row.purchase_return_credit_note_number || row.purchase_return_number || `Retour fournisseur #${row.reference_id}`;
    }
    if (row.reference_type === "maintenance_intervention") {
      return row.intervention_invoice_number || row.intervention_label || `Intervention #${row.reference_id}`;
    }
    if (row.reference_type === "transfer") return `Transfert #${row.reference_id}`;
    return row.reference_type || null;
  })();
  return {
    id: Number(row.id),
    company_id: Number(row.company_id),
    item_id: Number(row.item_id),
    item: {
      id: Number(row.item_id),
      code: row.item_code,
      label: row.item_label,
      min_stock_qty: toNumber(row.min_stock_qty),
      current_stock_qty: toNumber(row.current_stock_qty),
      category: mapRef("category", row),
      unit: mapRef("unit", row),
    },
    depot_id: row.depot_id ? Number(row.depot_id) : null,
    depot: row.depot_id ? { id: Number(row.depot_id), code: row.depot_code, label: row.depot_label } : null,
    movement_type: row.movement_type,
    movement_date: row.movement_date,
    quantity: toNumber(row.quantity),
    signed_quantity: signedQty,
    unit_price: row.unit_price === null ? null : toNumber(row.unit_price),
    total_value: row.total_value === null ? null : toNumber(row.total_value),
    total_value_formatted: formatDzdAmount(row.total_value),
    reference_type: row.reference_type,
    reference_id: row.reference_id ? Number(row.reference_id) : null,
    reference_label: referenceLabel,
    notes: row.notes,
    created_by: row.created_by ? { id: Number(row.created_by), full_name: row.created_by_name } : null,
    direction_tone: movementDirectionTone(row),
    created_at: row.created_at,
  };
};

const fetchItem = async (db, id, companyId) => {
  const { rows } = await db.query(
    `
      ${itemSelect}
      WHERE i.id = $1
        AND i.company_id = $2
      LIMIT 1
    `,
    [id, companyId],
  );
  return rows[0] ? mapItem(rows[0]) : null;
};

const fetchMovement = async (db, id, companyId) => {
  const { rows } = await db.query(
    `
      ${movementSelect}
      WHERE m.id = $1
        AND m.company_id = $2
      LIMIT 1
    `,
    [id, companyId],
  );
  return rows[0] ? mapMovement(rows[0]) : null;
};

const buildItemsList = async (db, companyId, { search = "", category = null, depotId = null, lowStockOnly = false } = {}) => {
  const params = [companyId];
  const conditions = ["i.company_id = $1"];
  if (search) {
    params.push(`%${search}%`);
    conditions.push(`(lower(i.code) LIKE $${params.length} OR lower(i.label) LIKE $${params.length})`);
  }
  if (category) {
    params.push(category);
    conditions.push(`i.category_ref_id = $${params.length}`);
  }
  const { rows } = await db.query(
    `
      ${itemSelect}
      WHERE ${conditions.join(" AND ")}
      ORDER BY i.label ASC
    `,
    params,
  );
  const itemIds = rows.map((row) => Number(row.id));
  const depotStockMap = depotId ? await computeDepotStockMap(db, companyId, itemIds, Number(depotId)) : new Map();
  return rows
    .map((row) => mapItem(row, depotId ? { stock_qty: depotStockMap.get(Number(row.id)) || 0 } : {}))
    .filter((item) => (lowStockOnly ? item.current_stock_qty < item.min_stock_qty : true));
};

const computeDepotStockMap = async (db, companyId, itemIds = [], depotId = null) => {
  const params = [companyId];
  const conditions = ["m.company_id = $1"];
  if (itemIds.length) {
    params.push(itemIds);
    conditions.push(`m.item_id = ANY($${params.length}::bigint[])`);
  }
  if (depotId) {
    params.push(depotId);
    conditions.push(`m.depot_id = $${params.length}`);
  }
  const { rows } = await db.query(
    `
      SELECT m.*
      FROM mgx_consumable_movements m
      WHERE ${conditions.join(" AND ")}
    `,
    params,
  );
  const stockMap = new Map();
  for (const row of rows) {
    const key = depotId ? Number(row.item_id) : `${row.depot_id || "none"}:${row.item_id}`;
    stockMap.set(key, (stockMap.get(key) || 0) + movementSignedQuantity(row));
  }
  return stockMap;
};

const nextConsumableCode = async (db, companyId) => {
  const { rows } = await db.query(
    "SELECT COUNT(*)::int AS count FROM mgx_consumable_items WHERE company_id = $1",
    [companyId],
  );
  return `CONS-${String((rows[0]?.count || 0) + 1).padStart(4, "0")}`;
};

const createMovementRow = async (db, {
  companyId,
  itemId,
  depotId,
  movementType,
  movementDate,
  quantity,
  unitPrice,
  totalValue,
  referenceType,
  referenceId,
  notes,
  createdBy,
}) => {
  const { rows } = await db.query(
    `
      INSERT INTO mgx_consumable_movements (
        company_id, item_id, depot_id, movement_type, movement_date,
        quantity, unit_price, total_value, reference_type, reference_id, notes, created_by
      )
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
      RETURNING id
    `,
    [
      companyId,
      itemId,
      depotId,
      movementType,
      movementDate,
      quantity,
      unitPrice,
      totalValue,
      referenceType,
      referenceId,
      notes,
      createdBy,
    ],
  );
  return Number(rows[0].id);
};

export const mgxConsumablesController = {
  async listItems(req, res) {
    const companyId = getCompanyId(req);
    const search = cleanString(req.query.search).toLowerCase();
    const category = nullableString(req.query.category_ref_id || req.query.category);
    const depotId = nullableString(req.query.depot_id);
    const lowStockOnly = req.query.low_stock_only === "true";
    const items = await buildItemsList(pool, companyId, { search, category, depotId, lowStockOnly });
    res.json({ items });
  },

  async getLowStock(req, res) {
    req.query.low_stock_only = "true";
    return this.listItems(req, res);
  },

  async getItem(req, res) {
    const companyId = getCompanyId(req);
    const item = await fetchItem(pool, req.params.id, companyId);
    if (!item) throw new ApiError(404, "Article consommable introuvable.");
    res.json({ item });
  },

  async saveItem(req, res) {
    const companyId = getCompanyId(req);
    const payload = {
      code: nullableString(req.body.code),
      label: nullableString(req.body.label),
      category_ref_id: nullableNumber(req.body.category_ref_id),
      unit_ref_id: nullableNumber(req.body.unit_ref_id),
      is_consumable: req.body.is_consumable !== false,
      unit_price: nullableNumber(req.body.unit_price) ?? 0,
      min_stock_qty: nullableNumber(req.body.min_stock_qty) ?? 0,
      is_active: req.body.is_active !== false,
    };
    if (!payload.label) throw new ApiError(400, "Le libellé est requis.");
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = req.params.id ? await fetchItem(db, req.params.id, companyId) : null;
      let itemId = existing?.id;
      const code = payload.code || existing?.code || await nextConsumableCode(db, companyId);
      if (existing) {
        await db.query(
          `
            UPDATE mgx_consumable_items
            SET code = $2,
                label = $3,
                category_ref_id = $4,
                unit_ref_id = $5,
                is_consumable = $6,
                unit_price = $7,
                min_stock_qty = $8,
                is_active = $9,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = $1 AND company_id = $10
          `,
          [existing.id, code, payload.label, payload.category_ref_id, payload.unit_ref_id, payload.is_consumable, payload.unit_price, payload.min_stock_qty, payload.is_active, companyId],
        );
      } else {
        const { rows } = await db.query(
          `
            INSERT INTO mgx_consumable_items (
              company_id, code, label, category_ref_id, unit_ref_id,
              is_consumable, unit_price, min_stock_qty, is_active
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
            RETURNING id
          `,
          [companyId, code, payload.label, payload.category_ref_id, payload.unit_ref_id, payload.is_consumable, payload.unit_price, payload.min_stock_qty, payload.is_active],
        );
        itemId = Number(rows[0].id);
      }
      const item = await fetchItem(db, itemId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: existing ? "UPDATE" : "CREATE",
        entityType: "consumable_item",
        entityId: itemId,
        oldData: existing,
        newData: item,
      });
      await db.query("COMMIT");
      res.status(existing ? 200 : 201).json({ item });
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async deleteItem(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = await fetchItem(db, req.params.id, companyId);
      if (!existing) throw new ApiError(404, "Article consommable introuvable.");
      await db.query(
        `
          UPDATE mgx_consumable_items
          SET is_active = false, updated_at = CURRENT_TIMESTAMP
          WHERE id = $1 AND company_id = $2
        `,
        [req.params.id, companyId],
      );
      const item = await fetchItem(db, req.params.id, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: "DELETE",
        entityType: "consumable_item",
        entityId: Number(req.params.id),
        oldData: existing,
        newData: item,
      });
      await db.query("COMMIT");
      res.status(204).send();
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async listMovements(req, res) {
    const companyId = getCompanyId(req);
    const params = [companyId];
    const conditions = ["m.company_id = $1"];
    if (req.query.item_id) {
      params.push(req.query.item_id);
      conditions.push(`m.item_id = $${params.length}`);
    }
    if (req.query.depot_id) {
      params.push(req.query.depot_id);
      conditions.push(`m.depot_id = $${params.length}`);
    }
    if (req.query.type) {
      params.push(req.query.type);
      conditions.push(`m.movement_type = $${params.length}`);
    }
    if (req.query.from) {
      params.push(req.query.from);
      conditions.push(`m.movement_date >= $${params.length}`);
    }
    if (req.query.to) {
      params.push(req.query.to);
      conditions.push(`m.movement_date <= $${params.length}`);
    }
    const { rows } = await pool.query(
      `
        ${movementSelect}
        WHERE ${conditions.join(" AND ")}
        ORDER BY m.movement_date DESC, m.id DESC
      `,
      params,
    );
    res.json({ items: rows.map(mapMovement) });
  },

  async getMovement(req, res) {
    const companyId = getCompanyId(req);
    const item = await fetchMovement(pool, req.params.id, companyId);
    if (!item) throw new ApiError(404, "Mouvement introuvable.");
    res.json({ item });
  },

  async createMovement(req, res) {
    const companyId = getCompanyId(req);
    const movementType = cleanString(req.body.movement_type).toLowerCase();
    const itemId = nullableNumber(req.body.item_id);
    const depotId = nullableNumber(req.body.depot_id);
    const movementDate = nullableString(req.body.movement_date) || new Date().toISOString().slice(0, 10);
    const quantity = toNumber(req.body.quantity);
    const unitPrice = nullableNumber(req.body.unit_price);
    const totalValue = nullableNumber(req.body.total_value) ?? toNumber(quantity) * toNumber(unitPrice);
    const referenceType = nullableString(req.body.reference_type);
    const referenceId = nullableNumber(req.body.reference_id);
    const notes = nullableString(req.body.notes);
    const destinationDepotId = nullableNumber(req.body.destination_depot_id);
    const realQuantity = nullableNumber(req.body.real_quantity);
    const reason = nullableString(req.body.adjustment_reason || req.body.reason);

    if (!itemId) throw new ApiError(400, "Article requis.");
    if (!["in", "out", "adjustment", "transfer"].includes(movementType)) {
      throw new ApiError(400, "Type de mouvement invalide.");
    }
    if (movementType !== "adjustment" && quantity <= 0) {
      throw new ApiError(400, "La quantité doit être supérieure à zéro.");
    }
    if (movementType === "adjustment" && realQuantity !== null && realQuantity < 0) {
      throw new ApiError(400, "La quantité réelle ne peut pas être négative.");
    }

    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const item = await fetchItem(db, itemId, companyId);
      if (!item) throw new ApiError(404, "Article consommable introuvable.");
      if (!item.is_consumable) {
        throw new ApiError(400, "Cet article n'est pas marqué comme consommable.");
      }
      const currentStock = item.current_stock_qty;

      if (movementType === "out" && quantity > currentStock) {
        throw new ApiError(400, "La quantité ne peut pas dépasser le stock actuel.");
      }

      const createdIds = [];
      if (movementType === "in") {
        await db.query(
          `
            UPDATE mgx_consumable_items
            SET current_stock_qty = current_stock_qty + $2,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = $1
          `,
          [itemId, quantity],
        );
        createdIds.push(await createMovementRow(db, {
          companyId,
          itemId,
          depotId,
          movementType,
          movementDate,
          quantity,
          unitPrice,
          totalValue,
          referenceType,
          referenceId,
          notes,
          createdBy: req.user?.sub || null,
        }));
      } else if (movementType === "out") {
        await db.query(
          `
            UPDATE mgx_consumable_items
            SET current_stock_qty = current_stock_qty - $2,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = $1
          `,
          [itemId, quantity],
        );
        createdIds.push(await createMovementRow(db, {
          companyId,
          itemId,
          depotId,
          movementType,
          movementDate,
          quantity,
          unitPrice: unitPrice ?? item.unit_price,
          totalValue: totalValue || quantity * item.unit_price,
          referenceType,
          referenceId,
          notes,
          createdBy: req.user?.sub || null,
        }));
      } else if (movementType === "adjustment") {
        if (realQuantity === null || !reason) {
          throw new ApiError(400, "La quantité réelle et la raison sont requises.");
        }
        const diff = realQuantity - currentStock;
        if (diff === 0) {
          throw new ApiError(400, "Aucun écart de stock à enregistrer.");
        }
        await db.query(
          `
            UPDATE mgx_consumable_items
            SET current_stock_qty = $2,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = $1
          `,
          [itemId, realQuantity],
        );
        createdIds.push(await createMovementRow(db, {
          companyId,
          itemId,
          depotId,
          movementType,
          movementDate,
          quantity: Math.abs(diff),
          unitPrice: unitPrice ?? item.unit_price,
          totalValue: Math.abs(diff) * item.unit_price,
          referenceType,
          referenceId,
          notes: `ADJUSTMENT_DIFF:${diff >= 0 ? "+" : ""}${diff}|${reason}${notes ? `|${notes}` : ""}`,
          createdBy: req.user?.sub || null,
        }));
      } else if (movementType === "transfer") {
        if (!depotId || !destinationDepotId || depotId === destinationDepotId) {
          throw new ApiError(400, "Dépôts source et destination valides requis.");
        }
        const sourceStockMap = await computeDepotStockMap(db, companyId, [itemId], depotId);
        const sourceAvailable = sourceStockMap.get(itemId) || 0;
        if (quantity > sourceAvailable) {
          throw new ApiError(400, "La quantité transférée ne peut pas dépasser le stock du dépôt source.");
        }
        const transferReferenceId = Date.now();
        createdIds.push(await createMovementRow(db, {
          companyId,
          itemId,
          depotId,
          movementType,
          movementDate,
          quantity,
          unitPrice: unitPrice ?? item.unit_price,
          totalValue: (unitPrice ?? item.unit_price) * quantity,
          referenceType: "transfer",
          referenceId: transferReferenceId,
          notes: `TRANSFER_OUT:${destinationDepotId}${notes ? `|${notes}` : ""}`,
          createdBy: req.user?.sub || null,
        }));
        createdIds.push(await createMovementRow(db, {
          companyId,
          itemId,
          depotId: destinationDepotId,
          movementType,
          movementDate,
          quantity,
          unitPrice: unitPrice ?? item.unit_price,
          totalValue: (unitPrice ?? item.unit_price) * quantity,
          referenceType: "transfer",
          referenceId: transferReferenceId,
          notes: `TRANSFER_IN:${depotId}${notes ? `|${notes}` : ""}`,
          createdBy: req.user?.sub || null,
        }));
      }

      const newItem = await fetchItem(db, itemId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: "CREATE",
        entityType: "consumable_movement",
        entityId: createdIds[0],
        newData: { created_ids: createdIds, movement_type: movementType, item: newItem },
      });
      await db.query("COMMIT");
      res.status(201).json({
        item: createdIds[0] ? await fetchMovement(pool, createdIds[0], companyId) : null,
        created_ids: createdIds,
        updated_item: newItem,
      });
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async getStockByDepot(req, res) {
    const companyId = getCompanyId(req);
    const movementRows = await pool.query(
      `
        ${movementSelect}
        WHERE m.company_id = $1
      `,
      [companyId],
    );
    const grouped = new Map();
    for (const row of movementRows.rows) {
      const key = `${row.depot_id || "none"}:${row.item_id}`;
      const current = grouped.get(key) || {
        depot_id: row.depot_id ? Number(row.depot_id) : null,
        depot_label: row.depot_label || "Sans dépôt",
        item_id: Number(row.item_id),
        item_code: row.item_code,
        item_label: row.item_label,
        category: mapRef("category", row),
        unit: mapRef("unit", row),
        stock_qty: 0,
        unit_price: toNumber(row.unit_price),
      };
      current.stock_qty += movementSignedQuantity(row);
      grouped.set(key, current);
    }
    const items = [...grouped.values()].map((item) => ({
      ...item,
      stock_value: item.stock_qty * toNumber(item.unit_price),
      stock_value_formatted: formatDzdAmount(item.stock_qty * toNumber(item.unit_price)),
    }));
    res.json({ items });
  },

  async getReports(req, res) {
    const companyId = getCompanyId(req);
    const params = [companyId];
    const conditions = ["m.company_id = $1"];
    if (req.query.depot_id) {
      params.push(req.query.depot_id);
      conditions.push(`m.depot_id = $${params.length}`);
    }
    if (req.query.from) {
      params.push(req.query.from);
      conditions.push(`m.movement_date >= $${params.length}`);
    }
    if (req.query.to) {
      params.push(req.query.to);
      conditions.push(`m.movement_date <= $${params.length}`);
    }
    const { rows } = await pool.query(
      `
        ${movementSelect}
        WHERE ${conditions.join(" AND ")}
      `,
      params,
    );
    const movements = rows.map(mapMovement);
    const byDepot = new Map();
    const byCategory = new Map();
    const byItemOut = new Map();
    for (const movement of movements) {
      const depotKey = movement.depot?.label || "Sans dépôt";
      const depotCurrent = byDepot.get(depotKey) || { depot_label: depotKey, net_qty: 0, stock_value: 0, in_value: 0, out_value: 0 };
      depotCurrent.net_qty += movement.signed_quantity;
      if (movement.signed_quantity > 0) depotCurrent.in_value += toNumber(movement.total_value);
      if (movement.signed_quantity < 0) depotCurrent.out_value += Math.abs(toNumber(movement.total_value));
      byDepot.set(depotKey, depotCurrent);

      const categoryKey = movement.item.category?.label || "Sans catégorie";
      byCategory.set(categoryKey, (byCategory.get(categoryKey) || 0) + Math.abs(movement.signed_quantity));

      if (movement.signed_quantity < 0) {
        const itemCurrent = byItemOut.get(movement.item_id) || {
          item_id: movement.item_id,
          code: movement.item.code,
          label: movement.item.label,
          quantity: 0,
        };
        itemCurrent.quantity += Math.abs(movement.signed_quantity);
        byItemOut.set(movement.item_id, itemCurrent);
      }
    }

    const lowStockItems = await buildItemsList(pool, companyId, {
      search: "",
      category: nullableString(req.query.category_ref_id || req.query.category),
      depotId: nullableString(req.query.depot_id),
      lowStockOnly: true,
    });

    const stockByDepotResponse = await pool.query(
      `
        ${movementSelect}
        WHERE m.company_id = $1
      `,
      [companyId],
    );
    const stockByDepotGrouped = new Map();
    for (const row of stockByDepotResponse.rows) {
      const key = `${row.depot_id || "none"}:${row.item_id}`;
      const current = stockByDepotGrouped.get(key) || {
        depot_id: row.depot_id ? Number(row.depot_id) : null,
        depot_label: row.depot_label || "Sans dépôt",
        item_id: Number(row.item_id),
        item_label: row.item_label,
        stock_qty: 0,
        unit_price: toNumber(row.unit_price),
      };
      current.stock_qty += movementSignedQuantity(row);
      stockByDepotGrouped.set(key, current);
    }

    res.json({
      stock_by_depot: [...stockByDepotGrouped.values()].map((item) => ({
        ...item,
        stock_value: item.stock_qty * item.unit_price,
        stock_value_formatted: formatDzdAmount(item.stock_qty * item.unit_price),
      })),
      movements_summary: {
        total_in: movements.filter((item) => item.signed_quantity > 0).reduce((sum, item) => sum + item.signed_quantity, 0),
        total_out: movements.filter((item) => item.signed_quantity < 0).reduce((sum, item) => sum + Math.abs(item.signed_quantity), 0),
        net: movements.reduce((sum, item) => sum + item.signed_quantity, 0),
      },
      consumption_by_category: [...byCategory.entries()].map(([label, value]) => ({ label, value })),
      top_consumed_items: [...byItemOut.values()].sort((a, b) => b.quantity - a.quantity).slice(0, 10),
      low_stock_items: lowStockItems,
      valuation_by_depot: [...byDepot.entries()].map(([label, value]) => ({
        depot_label: label,
        total_in_formatted: formatDzdAmount(value.in_value),
        total_out_formatted: formatDzdAmount(value.out_value),
        net_qty: value.net_qty,
      })),
    });
  },
};
