import { pool } from "../db/pool.js";
import { runMgxAlertEngine } from "../services/mgxAlertEngine.js";
import { ApiError } from "../utils/ApiError.js";
import { ensureMgxStaffMobileTables } from "./mgxStaffMobileController.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 resolveActorUserId = (req) => (
  req.user?.accountType === "mgx_mobile_account" ? null : req.user?.sub || null
);
const resolveMobileAccountId = (req) => (
  req.user?.accountType === "mgx_mobile_account" ? req.user?.sub || null : null
);
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 mapDepot = (row) => row.depot_id ? ({
  id: Number(row.depot_id),
  code: row.depot_code,
  label: row.depot_label,
}) : null;
const mapSupplier = (row) => row.supplier_id ? ({
  id: Number(row.supplier_id),
  code: row.supplier_code,
  name: row.supplier_name,
}) : null;
const mapVehicle = (row) => row.vehicle_id ? ({
  id: Number(row.vehicle_id),
  reference: row.vehicle_reference,
  plate_number: row.plate_number,
  brand: row.vehicle_brand,
  model: row.vehicle_model,
}) : 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,
      resolveActorUserId(req),
      action,
      entityType,
      entityId,
      oldData ? JSON.stringify(oldData) : null,
      newData ? JSON.stringify(newData) : null,
      req.ip,
    ],
  );
};

const ensurePurchaseRequestColumns = async (db = pool) => {
  await ensureMgxStaffMobileTables(db);
  await db.query(`
    ALTER TABLE mgx_purchase_requests
      ADD COLUMN IF NOT EXISTS vehicle_id BIGINT REFERENCES mgx_vehicles (id) ON DELETE SET NULL,
      ADD COLUMN IF NOT EXISTS supplier_id BIGINT REFERENCES mgx_suppliers (id) ON DELETE SET NULL,
      ADD COLUMN IF NOT EXISTS desired_date DATE,
      ADD COLUMN IF NOT EXISTS file_url VARCHAR(500),
      ADD COLUMN IF NOT EXISTS requested_by_mobile_account_id BIGINT REFERENCES mgx_mobile_accounts (id) ON DELETE SET NULL,
      ADD COLUMN IF NOT EXISTS validated_by_mobile_account_id BIGINT REFERENCES mgx_mobile_accounts (id) ON DELETE SET NULL,
      ADD COLUMN IF NOT EXISTS rejected_by_mobile_account_id BIGINT REFERENCES mgx_mobile_accounts (id) ON DELETE SET NULL
  `);
  await db.query(`
    CREATE INDEX IF NOT EXISTS idx_mgx_purchase_requests_vehicle_id
      ON mgx_purchase_requests (vehicle_id)
  `);
  await db.query(`
    CREATE INDEX IF NOT EXISTS idx_mgx_purchase_requests_supplier_id
      ON mgx_purchase_requests (supplier_id)
  `);
};

const requestSelect = `
  SELECT
    r.*,
    d.code AS depot_code,
    d.label AS depot_label,
    c.id AS category_id,
    c.code AS category_code,
    c.label AS category_label,
    c.color AS category_color,
    c.icon AS category_icon,
    c.metadata AS category_metadata,
    v.reference AS vehicle_reference,
    v.plate_number,
    v.brand AS vehicle_brand,
    v.model AS vehicle_model,
    s.code AS supplier_code,
    s.name AS supplier_name,
    u.full_name AS requested_by_name,
    validator.full_name AS validated_by_name,
    COUNT(rl.id) AS lines_count,
    COALESCE(SUM(rl.estimated_total), 0) AS estimated_amount
  FROM mgx_purchase_requests r
  LEFT JOIN mgx_depots d ON d.id = r.depot_id
  LEFT JOIN mgx_referentiels c ON c.id = r.category_ref_id
  LEFT JOIN mgx_vehicles v ON v.id = r.vehicle_id
  LEFT JOIN mgx_suppliers s ON s.id = r.supplier_id
  LEFT JOIN users u ON u.id = r.requested_by
  LEFT JOIN users validator ON validator.id = r.validated_by
  LEFT JOIN mgx_purchase_request_lines rl ON rl.request_id = r.id
`;

const orderSelect = `
  SELECT
    o.*,
    d.code AS depot_code,
    d.label AS depot_label,
    c.id AS category_id,
    c.code AS category_code,
    c.label AS category_label,
    c.color AS category_color,
    c.icon AS category_icon,
    c.metadata AS category_metadata,
    s.code AS supplier_code,
    s.name AS supplier_name,
    status.id AS status_id,
    status.code AS status_code,
    status.label AS status_label,
    status.color AS status_color,
    status.icon AS status_icon,
    status.metadata AS status_metadata,
    r.id AS request_ref_id,
    r.status AS request_status,
    COUNT(ol.id) AS lines_count,
    COALESCE(SUM(ol.quantity_ordered), 0) AS quantity_ordered_total,
    COALESCE(SUM(ol.quantity_received), 0) AS quantity_received_total
  FROM mgx_purchase_orders o
  LEFT JOIN mgx_depots d ON d.id = o.depot_id
  LEFT JOIN mgx_referentiels c ON c.id = o.category_ref_id
  LEFT JOIN mgx_suppliers s ON s.id = o.supplier_id
  LEFT JOIN mgx_referentiels status ON status.id = o.status_ref_id
  LEFT JOIN mgx_purchase_requests r ON r.id = o.request_id
  LEFT JOIN mgx_purchase_order_lines ol ON ol.order_id = o.id
`;

const receptionSelect = `
  SELECT
    rec.*,
    o.order_number,
    o.supplier_id,
    o.payment_terms,
    sup.code AS supplier_code,
    sup.name AS supplier_name,
    COALESCE((
      SELECT SUM(rl.total)
      FROM mgx_reception_lines rl
      WHERE rl.reception_id = rec.id
    ), 0) AS lines_total,
    COALESCE(rec.invoice_amount, COALESCE((
      SELECT SUM(rl.total)
      FROM mgx_reception_lines rl
      WHERE rl.reception_id = rec.id
    ), 0)) AS payable_amount,
    d.code AS depot_code,
    d.label AS depot_label
  FROM mgx_receptions rec
  LEFT JOIN mgx_purchase_orders o ON o.id = rec.order_id
  LEFT JOIN mgx_suppliers sup ON sup.id = o.supplier_id
  LEFT JOIN mgx_depots d ON d.id = rec.depot_id
`;

const returnSelect = `
  SELECT
    ret.*,
    rec.reception_number,
    rec.invoice_number,
    o.order_number,
    o.supplier_id,
    sup.code AS supplier_code,
    sup.name AS supplier_name,
    d.code AS depot_code,
    d.label AS depot_label,
    COALESCE((
      SELECT SUM(rl.total)
      FROM mgx_purchase_return_lines rl
      WHERE rl.return_id = ret.id
    ), 0) AS lines_total,
    COALESCE(ret.credit_amount, COALESCE((
      SELECT SUM(rl.total)
      FROM mgx_purchase_return_lines rl
      WHERE rl.return_id = ret.id
    ), 0)) AS payable_credit_amount
  FROM mgx_purchase_returns ret
  LEFT JOIN mgx_receptions rec ON rec.id = ret.reception_id
  LEFT JOIN mgx_purchase_orders o ON o.id = rec.order_id
  LEFT JOIN mgx_suppliers sup ON sup.id = o.supplier_id
  LEFT JOIN mgx_depots d ON d.id = ret.depot_id
`;

const serviceSelect = `
  SELECT
    s.*,
    sup.code AS supplier_code,
    sup.name AS supplier_name,
    d.code AS depot_code,
    d.label AS depot_label,
    ref.id AS service_type_id,
    ref.code AS service_type_code,
    ref.label AS service_type_label,
    ref.color AS service_type_color,
    ref.icon AS service_type_icon,
    ref.metadata AS service_type_metadata,
    v.reference AS vehicle_reference,
    v.plate_number,
    v.brand AS vehicle_brand,
    v.model AS vehicle_model
  FROM mgx_services s
  LEFT JOIN mgx_suppliers sup ON sup.id = s.supplier_id
  LEFT JOIN mgx_depots d ON d.id = s.depot_id
  LEFT JOIN mgx_referentiels ref ON ref.id = s.service_type_ref_id
  LEFT JOIN mgx_vehicles v ON v.id = s.vehicle_id
`;

const rentalSelect = `
  SELECT
    r.*,
    sup.code AS supplier_code,
    sup.name AS supplier_name,
    status.id AS status_id,
    status.code AS status_code,
    status.label AS status_label,
    status.color AS status_color,
    status.icon AS status_icon,
    status.metadata AS status_metadata,
    d.code AS depot_code,
    d.label AS depot_label,
    v.reference AS vehicle_reference,
    v.plate_number,
    v.brand AS vehicle_brand,
    v.model AS vehicle_model
  FROM mgx_rentals r
  LEFT JOIN mgx_suppliers sup ON sup.id = r.supplier_id
  LEFT JOIN mgx_depots d ON d.id = r.depot_id
  LEFT JOIN mgx_referentiels status ON status.id = r.status_ref_id
  LEFT JOIN mgx_vehicles v ON v.id = r.vehicle_id
`;

const normalizeRequestLines = (lines = []) => lines
  .map((line) => {
    const quantity = toNumber(line.quantity, 1);
    const unitPrice = toNumber(line.estimated_unit_price);
    return {
      item_id: nullableNumber(line.item_id),
      description: nullableString(line.description),
      quantity,
      estimated_unit_price: unitPrice,
      estimated_total: line.estimated_total === "" || line.estimated_total === undefined || line.estimated_total === null
        ? quantity * unitPrice
        : toNumber(line.estimated_total),
      unit_ref_id: nullableNumber(line.unit_ref_id),
      notes: nullableString(line.notes),
    };
  })
  .filter((line) => line.description);

const normalizeOrderLines = (lines = []) => lines
  .map((line) => {
    const quantity = toNumber(line.quantity_ordered, 1);
    const unitPrice = toNumber(line.unit_price);
    return {
      id: nullableNumber(line.id),
      item_id: nullableNumber(line.item_id),
      description: nullableString(line.description),
      quantity_ordered: quantity,
      quantity_received: nullableNumber(line.quantity_received) ?? 0,
      unit_price: unitPrice,
      total: line.total === "" || line.total === undefined || line.total === null
        ? quantity * unitPrice
        : toNumber(line.total),
      unit_ref_id: nullableNumber(line.unit_ref_id),
      vehicle_id: nullableNumber(line.vehicle_id),
      notes: nullableString(line.notes),
    };
  })
  .filter((line) => line.description);

const normalizeReceptionLines = (lines = []) => lines
  .map((line) => {
    const quantityReceived = toNumber(line.quantity_received);
    const unitPrice = toNumber(line.unit_price);
    return {
      order_line_id: nullableNumber(line.order_line_id),
      item_id: nullableNumber(line.item_id),
      description: nullableString(line.description),
      quantity_received: quantityReceived,
      unit_price: unitPrice,
      total: line.total === "" || line.total === undefined || line.total === null
        ? quantityReceived * unitPrice
        : toNumber(line.total),
      is_conforming: line.is_conforming !== false,
      notes: nullableString(line.notes),
      lot_number: nullableString(line.lot_number),
    };
  })
  .filter((line) => line.description && line.quantity_received > 0);

const normalizeReturnLines = (lines = []) => lines
  .map((line) => {
    const quantityReturned = toNumber(line.quantity_returned);
    const unitPrice = toNumber(line.unit_price);
    return {
      reception_line_id: nullableNumber(line.reception_line_id),
      description: nullableString(line.description),
      quantity_returned: quantityReturned,
      unit_price: unitPrice,
      total: line.total === "" || line.total === undefined || line.total === null
        ? quantityReturned * unitPrice
        : toNumber(line.total),
      notes: nullableString(line.notes),
    };
  })
  .filter((line) => line.reception_line_id && line.description && line.quantity_returned > 0);

const ensureNonNegativeAmount = (value, message) => {
  if (toNumber(value) < 0) {
    throw new ApiError(400, message);
  }
};

const requestShape = (row, lines = []) => ({
  id: Number(row.id),
  company_id: Number(row.company_id),
  depot_id: row.depot_id ? Number(row.depot_id) : null,
  depot: mapDepot(row),
  request_date: row.request_date,
  vehicle_id: row.vehicle_id ? Number(row.vehicle_id) : null,
  vehicle: mapVehicle(row),
  supplier_id: row.supplier_id ? Number(row.supplier_id) : null,
  supplier: mapSupplier(row),
  desired_date: row.desired_date,
  file_url: row.file_url,
  requested_by: row.requested_by ? { id: Number(row.requested_by), full_name: row.requested_by_name } : null,
  requested_by_mobile_account_id: row.requested_by_mobile_account_id ? Number(row.requested_by_mobile_account_id) : null,
  category_ref_id: row.category_ref_id ? Number(row.category_ref_id) : null,
  category: mapRef("category", row),
  urgency: row.urgency,
  justification: row.justification,
  status: row.status,
  validated_by: row.validated_by ? { id: Number(row.validated_by), full_name: row.validated_by_name } : null,
  validated_by_mobile_account_id: row.validated_by_mobile_account_id ? Number(row.validated_by_mobile_account_id) : null,
  rejected_by_mobile_account_id: row.rejected_by_mobile_account_id ? Number(row.rejected_by_mobile_account_id) : null,
  validated_at: row.validated_at,
  rejected_reason: row.rejected_reason,
  lines_count: Number(row.lines_count || 0),
  estimated_amount: toNumber(row.estimated_amount),
  estimated_amount_formatted: formatDzdAmount(row.estimated_amount),
  lines,
  created_at: row.created_at,
  updated_at: row.updated_at,
});

const orderShape = (row, lines = []) => ({
  id: Number(row.id),
  company_id: Number(row.company_id),
  request_id: row.request_id ? Number(row.request_id) : null,
  request: row.request_ref_id ? { id: Number(row.request_ref_id), status: row.request_status } : null,
  depot_id: row.depot_id ? Number(row.depot_id) : null,
  depot: mapDepot(row),
  supplier_id: row.supplier_id ? Number(row.supplier_id) : null,
  supplier: mapSupplier(row),
  order_number: row.order_number,
  order_date: row.order_date,
  delivery_date_expected: row.delivery_date_expected,
  payment_terms: row.payment_terms === null ? null : Number(row.payment_terms),
  category_ref_id: row.category_ref_id ? Number(row.category_ref_id) : null,
  category: mapRef("category", row),
  total_ht: toNumber(row.total_ht),
  total_ttc: toNumber(row.total_ttc),
  total_ht_formatted: formatDzdAmount(row.total_ht),
  total_ttc_formatted: formatDzdAmount(row.total_ttc),
  status_ref_id: row.status_ref_id ? Number(row.status_ref_id) : null,
  status: mapRef("status", row),
  notes: row.notes,
  lines_count: Number(row.lines_count || 0),
  quantity_ordered_total: toNumber(row.quantity_ordered_total),
  quantity_received_total: toNumber(row.quantity_received_total),
  lines,
  created_at: row.created_at,
  updated_at: row.updated_at,
});

const receptionShape = (row, lines = []) => ({
  id: Number(row.id),
  company_id: Number(row.company_id),
  order_id: row.order_id ? Number(row.order_id) : null,
  order_number: row.order_number,
  supplier_id: row.supplier_id ? Number(row.supplier_id) : null,
  supplier: mapSupplier(row),
  payment_terms: row.payment_terms === null ? null : Number(row.payment_terms),
  depot_id: row.depot_id ? Number(row.depot_id) : null,
  depot: mapDepot(row),
  reception_date: row.reception_date,
  reception_number: row.reception_number,
  received_by: row.received_by ? Number(row.received_by) : null,
  invoice_number: row.invoice_number,
  invoice_amount: row.invoice_amount === null ? null : toNumber(row.invoice_amount),
  lines_total: toNumber(row.lines_total),
  payable_amount: toNumber(row.payable_amount),
  payable_amount_formatted: formatDzdAmount(row.payable_amount),
  file_url: row.file_url,
  notes: row.notes,
  lines,
  created_at: row.created_at,
});

const returnShape = (row, lines = []) => ({
  id: Number(row.id),
  company_id: Number(row.company_id),
  reception_id: row.reception_id ? Number(row.reception_id) : null,
  reception_number: row.reception_number,
  invoice_number: row.invoice_number,
  order_number: row.order_number,
  supplier_id: row.supplier_id ? Number(row.supplier_id) : null,
  supplier: mapSupplier(row),
  depot_id: row.depot_id ? Number(row.depot_id) : null,
  depot: mapDepot(row),
  return_date: row.return_date,
  return_number: row.return_number,
  reason: row.reason,
  credit_note_number: row.credit_note_number,
  credit_amount: row.credit_amount === null ? null : toNumber(row.credit_amount),
  lines_total: toNumber(row.lines_total),
  payable_credit_amount: toNumber(row.payable_credit_amount),
  payable_credit_amount_formatted: formatDzdAmount(row.payable_credit_amount),
  file_url: row.file_url,
  notes: row.notes,
  lines,
  created_at: row.created_at,
});

const serviceShape = (row) => ({
  id: Number(row.id),
  company_id: Number(row.company_id),
  supplier_id: row.supplier_id ? Number(row.supplier_id) : null,
  supplier: mapSupplier(row),
  vehicle_id: row.vehicle_id ? Number(row.vehicle_id) : null,
  vehicle: mapVehicle(row),
  depot_id: row.depot_id ? Number(row.depot_id) : null,
  depot: mapDepot(row),
  service_type_ref_id: row.service_type_ref_id ? Number(row.service_type_ref_id) : null,
  service_type: mapRef("service_type", row),
  label: row.label,
  service_date: row.service_date,
  amount: toNumber(row.amount),
  amount_formatted: formatDzdAmount(row.amount),
  invoice_number: row.invoice_number,
  file_url: row.file_url,
  payment_status: row.payment_status,
  notes: row.notes,
  created_at: row.created_at,
});

const rentalShape = (row) => {
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const endDate = row.end_date ? new Date(row.end_date) : null;
  const daysRemaining = endDate ? Math.ceil((endDate.getTime() - today.getTime()) / 86400000) : null;
  return {
    id: Number(row.id),
    company_id: Number(row.company_id),
    depot_id: row.depot_id ? Number(row.depot_id) : null,
    depot: mapDepot(row),
    vehicle_id: row.vehicle_id ? Number(row.vehicle_id) : null,
    vehicle: mapVehicle(row),
    supplier_id: row.supplier_id ? Number(row.supplier_id) : null,
    supplier: mapSupplier(row),
    rental_type: row.rental_type,
    start_date: row.start_date,
    end_date: row.end_date,
    daily_rate: row.daily_rate === null ? null : toNumber(row.daily_rate),
    total_amount: row.total_amount === null ? null : toNumber(row.total_amount),
    total_amount_formatted: formatDzdAmount(row.total_amount),
    conditions: row.conditions,
    file_url: row.file_url,
    status_ref_id: row.status_ref_id ? Number(row.status_ref_id) : null,
    status: mapRef("status", row),
    alert_days_before: Number(row.alert_days_before || 15),
    notes: row.notes,
    days_remaining: daysRemaining,
    expiring_soon: daysRemaining !== null && daysRemaining >= 0 && daysRemaining <= Number(row.alert_days_before || 15),
    created_at: row.created_at,
    updated_at: row.updated_at,
  };
};

const fetchRequest = async (db, id, companyId) => {
  const { rows } = await db.query(
    `
      ${requestSelect}
      WHERE r.id = $1
        AND r.company_id = $2
      GROUP BY r.id, d.id, c.id, v.id, s.id, u.id, validator.id
      LIMIT 1
    `,
    [id, companyId],
  );
  if (!rows[0]) return null;
  const { rows: lineRows } = await db.query(
    `
      SELECT
        l.*,
        unit_ref.id AS unit_id,
        unit_ref.code AS unit_code,
        unit_ref.label AS unit_label,
        unit_ref.color AS unit_color,
        unit_ref.icon AS unit_icon,
        unit_ref.metadata AS unit_metadata,
        item.id AS item_id,
        item.code AS item_code,
        item.label AS item_label
      FROM mgx_purchase_request_lines l
      LEFT JOIN mgx_referentiels unit_ref ON unit_ref.id = l.unit_ref_id
      LEFT JOIN mgx_consumable_items item ON item.id = l.item_id
      WHERE l.request_id = $1
      ORDER BY l.id ASC
    `,
    [id],
  );
  const lines = lineRows.map((line) => ({
    id: Number(line.id),
    item_id: line.item_id ? Number(line.item_id) : null,
    item: line.item_id ? { id: Number(line.item_id), code: line.item_code, label: line.item_label } : null,
    description: line.description,
    quantity: toNumber(line.quantity),
    estimated_unit_price: toNumber(line.estimated_unit_price),
    estimated_total: toNumber(line.estimated_total),
    unit_ref_id: line.unit_ref_id ? Number(line.unit_ref_id) : null,
    unit: mapRef("unit", line),
    notes: line.notes,
  }));
  return requestShape(rows[0], lines);
};

const fetchOrder = async (db, id, companyId) => {
  const { rows } = await db.query(
    `
      ${orderSelect}
      WHERE o.id = $1
        AND o.company_id = $2
      GROUP BY o.id, d.id, c.id, s.id, status.id, r.id
      LIMIT 1
    `,
    [id, companyId],
  );
  if (!rows[0]) return null;
  const { rows: lineRows } = await db.query(
    `
      SELECT
        l.*,
        unit_ref.id AS unit_id,
        unit_ref.code AS unit_code,
        unit_ref.label AS unit_label,
        unit_ref.color AS unit_color,
        unit_ref.icon AS unit_icon,
        unit_ref.metadata AS unit_metadata,
        item.id AS item_id,
        item.code AS item_code,
        item.label AS item_label,
        v.reference AS vehicle_reference,
        v.plate_number,
        v.brand AS vehicle_brand,
        v.model AS vehicle_model
      FROM mgx_purchase_order_lines l
      LEFT JOIN mgx_referentiels unit_ref ON unit_ref.id = l.unit_ref_id
      LEFT JOIN mgx_consumable_items item ON item.id = l.item_id
      LEFT JOIN mgx_vehicles v ON v.id = l.vehicle_id
      WHERE l.order_id = $1
      ORDER BY l.id ASC
    `,
    [id],
  );
  const lines = lineRows.map((line) => ({
    id: Number(line.id),
    item_id: line.item_id ? Number(line.item_id) : null,
    item: line.item_id ? { id: Number(line.item_id), code: line.item_code, label: line.item_label } : null,
    description: line.description,
    quantity_ordered: toNumber(line.quantity_ordered),
    quantity_received: toNumber(line.quantity_received),
    unit_price: toNumber(line.unit_price),
    total: toNumber(line.total),
    unit_ref_id: line.unit_ref_id ? Number(line.unit_ref_id) : null,
    unit: mapRef("unit", line),
    vehicle_id: line.vehicle_id ? Number(line.vehicle_id) : null,
    vehicle: mapVehicle(line),
    notes: line.notes,
  }));
  return orderShape(rows[0], lines);
};

const fetchReception = async (db, id, companyId) => {
  const { rows } = await db.query(
    `
      ${receptionSelect}
      WHERE rec.id = $1
        AND rec.company_id = $2
      LIMIT 1
    `,
    [id, companyId],
  );
  if (!rows[0]) return null;
  const { rows: lineRows } = await db.query(
    `
      SELECT
        l.*,
        COALESCE((
          SELECT SUM(prl.quantity_returned)
          FROM mgx_purchase_return_lines prl
          WHERE prl.reception_line_id = l.id
        ), 0) AS quantity_returned_total
      FROM mgx_reception_lines l
      WHERE l.reception_id = $1
      ORDER BY l.id ASC
    `,
    [id],
  );
  const lines = lineRows.map((line) => ({
    id: Number(line.id),
    order_line_id: line.order_line_id ? Number(line.order_line_id) : null,
    item_id: line.item_id ? Number(line.item_id) : null,
    description: line.description,
    quantity_received: toNumber(line.quantity_received),
    quantity_returned_total: toNumber(line.quantity_returned_total),
    quantity_returnable: Math.max(toNumber(line.quantity_received) - toNumber(line.quantity_returned_total), 0),
    unit_price: toNumber(line.unit_price),
    total: toNumber(line.total),
    is_conforming: Boolean(line.is_conforming),
    notes: line.notes,
  }));
  return receptionShape(rows[0], lines);
};

const fetchReturn = async (db, id, companyId) => {
  const { rows } = await db.query(
    `
      ${returnSelect}
      WHERE ret.id = $1
        AND ret.company_id = $2
      LIMIT 1
    `,
    [id, companyId],
  );
  if (!rows[0]) return null;
  const { rows: lineRows } = await db.query(
    `
      SELECT *
      FROM mgx_purchase_return_lines
      WHERE return_id = $1
      ORDER BY id ASC
    `,
    [id],
  );
  const lines = lineRows.map((line) => ({
    id: Number(line.id),
    reception_line_id: line.reception_line_id ? Number(line.reception_line_id) : null,
    item_id: line.item_id ? Number(line.item_id) : null,
    description: line.description,
    quantity_returned: toNumber(line.quantity_returned),
    unit_price: toNumber(line.unit_price),
    total: toNumber(line.total),
    notes: line.notes,
  }));
  return returnShape(rows[0], lines);
};

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

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

const replaceRequestLines = async (db, requestId, lines) => {
  await db.query("DELETE FROM mgx_purchase_request_lines WHERE request_id = $1", [requestId]);
  for (const line of lines) {
    await db.query(
      `
        INSERT INTO mgx_purchase_request_lines (
          request_id, item_id, description, quantity, estimated_unit_price,
          estimated_total, unit_ref_id, notes
        )
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
      `,
      [
        requestId,
        line.item_id,
        line.description,
        line.quantity,
        line.estimated_unit_price,
        line.estimated_total,
        line.unit_ref_id,
        line.notes,
      ],
    );
  }
};

const replaceOrderLines = async (db, orderId, lines) => {
  await db.query("DELETE FROM mgx_purchase_order_lines WHERE order_id = $1", [orderId]);
  for (const line of lines) {
    await db.query(
      `
        INSERT INTO mgx_purchase_order_lines (
          order_id, item_id, description, quantity_ordered, quantity_received,
          unit_price, total, unit_ref_id, vehicle_id, notes
        )
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
      `,
      [
        orderId,
        line.item_id,
        line.description,
        line.quantity_ordered,
        line.quantity_received,
        line.unit_price,
        line.total,
        line.unit_ref_id,
        line.vehicle_id,
        line.notes,
      ],
    );
  }
};

const nextSequentialNumber = async (db, tableName, companyId, prefix) => {
  const { rows } = await db.query(`SELECT COUNT(*)::int AS count FROM ${tableName} WHERE company_id = $1`, [companyId]);
  return `${prefix}-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}-${String((rows[0]?.count || 0) + 1).padStart(3, "0")}`;
};

const createConsumableMovementRow = async (db, {
  companyId,
  itemId,
  depotId,
  movementDate,
  quantity,
  unitPrice,
  totalValue,
  movementType = "in",
  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);
};

const getDefaultPaymentTerms = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT default_payment_terms
      FROM mgx_settings
      WHERE company_id = $1
      LIMIT 1
    `,
    [companyId],
  );
  return Number(rows[0]?.default_payment_terms || 30);
};

const emitRentalAlertIfNeeded = (req, companyId, rental) => {
  if (rental.expiring_soon) {
    req.app.get("io")?.to(`company:${companyId}`).emit("rental:expiring_soon", {
      rental_id: rental.id,
      vehicle: rental.vehicle?.plate_number,
      supplier: rental.supplier?.name,
      days_remaining: rental.days_remaining,
    });
  }
};

const triggerAlertEngine = (req, companyId) => {
  runMgxAlertEngine({
    companyId,
    io: req.app.get("io"),
  }).catch((error) => {
    console.error("MGX alert engine trigger failed after rental event", error);
  });
};

export const mgxProcurementController = {
  async listPurchaseRequests(req, res) {
    const companyId = getCompanyId(req);
    await ensurePurchaseRequestColumns();
    const params = [companyId];
    const filters = ["r.company_id = $1"];
    const page = Math.max(1, Number(req.query.page || 1));
    const limit = Math.min(100, Math.max(1, Number(req.query.limit || 50)));
    const offset = (page - 1) * limit;
    if (req.query.status) {
      if (req.query.status === "pending_backoffice") {
        params.push(["pending_backoffice", "pending"]);
        filters.push(`r.status = ANY($${params.length}::text[])`);
      } else {
        params.push(req.query.status);
        filters.push(`r.status = $${params.length}`);
      }
    }
    if (req.query.depot_id) {
      params.push(req.query.depot_id);
      filters.push(`r.depot_id = $${params.length}`);
    }

    const countResult = await pool.query(
      `
        SELECT COUNT(*)::int AS total
        FROM mgx_purchase_requests r
        WHERE ${filters.join(" AND ")}
      `,
      params,
    );

    const queryParams = [...params, limit, offset];
    const { rows } = await pool.query(
      `
        ${requestSelect}
        WHERE ${filters.join(" AND ")}
        GROUP BY r.id, d.id, c.id, v.id, s.id, u.id, validator.id
        ORDER BY r.request_date DESC, r.id DESC
        LIMIT $${queryParams.length - 1}
        OFFSET $${queryParams.length}
      `,
      queryParams,
    );

    res.json({
      items: rows.map((row) => requestShape(row)),
      pagination: {
        page,
        limit,
        total: Number(countResult.rows[0]?.total || 0),
      },
    });
  },

  async listPurchaseRequestHistory(req, res) {
    const companyId = getCompanyId(req);
    await ensurePurchaseRequestColumns();
    const params = [companyId];
    const filters = ["r.company_id = $1", "r.status IN ('validated', 'rejected')"];
    const page = Math.max(1, Number(req.query.page || 1));
    const limit = Math.min(100, Math.max(1, Number(req.query.limit || 50)));
    const offset = (page - 1) * limit;

    if (req.query.validated_by && req.user?.accountType !== "mgx_mobile_account") {
      params.push(Number(req.query.validated_by));
      filters.push(`r.validated_by = $${params.length}`);
    }

    const countResult = await pool.query(
      `
        SELECT COUNT(*)::int AS total
        FROM mgx_purchase_requests r
        WHERE ${filters.join(" AND ")}
      `,
      params,
    );

    const queryParams = [...params, limit, offset];
    const { rows } = await pool.query(
      `
        ${requestSelect}
        WHERE ${filters.join(" AND ")}
        GROUP BY r.id, d.id, c.id, v.id, s.id, u.id, validator.id
        ORDER BY COALESCE(r.validated_at, r.updated_at, r.request_date::timestamptz) DESC, r.id DESC
        LIMIT $${queryParams.length - 1}
        OFFSET $${queryParams.length}
      `,
      queryParams,
    );

    res.json({
      items: rows.map((row) => requestShape(row)),
      pagination: {
        page,
        limit,
        total: Number(countResult.rows[0]?.total || 0),
      },
    });
  },

  async getPurchaseRequest(req, res) {
    const companyId = getCompanyId(req);
    await ensurePurchaseRequestColumns();
    const item = await fetchRequest(pool, req.params.id, companyId);
    if (!item) throw new ApiError(404, "Demande d'achat introuvable.");
    res.json({ item });
  },

  async savePurchaseRequest(req, res) {
    const companyId = getCompanyId(req);
    await ensurePurchaseRequestColumns();
    const payload = {
      company_id: companyId,
      depot_id: nullableNumber(req.body.depot_id),
      vehicle_id: nullableNumber(req.body.vehicle_id),
      supplier_id: nullableNumber(req.body.supplier_id),
      request_date: nullableString(req.body.request_date) || new Date().toISOString().slice(0, 10),
      desired_date: nullableString(req.body.desired_date),
      category_ref_id: nullableNumber(req.body.category_ref_id),
      urgency: cleanString(req.body.urgency || "normal").toLowerCase(),
      justification: nullableString(req.body.justification),
      file_url: nullableString(req.body.file_url),
      status: cleanString(req.body.status || "draft").toLowerCase(),
      lines: normalizeRequestLines(req.body.lines || []),
    };
    if (!payload.depot_id) throw new ApiError(400, "Le dépôt est requis.");
    if (!payload.lines.length) throw new ApiError(400, "Au moins une ligne est requise.");

    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = req.params.id ? await fetchRequest(db, req.params.id, companyId) : null;
      let requestId = existing?.id;

      if (existing) {
        await db.query(
          `
            UPDATE mgx_purchase_requests
            SET depot_id = $2,
                request_date = $3,
                category_ref_id = $4,
                urgency = $5,
                justification = $6,
                status = $7,
                vehicle_id = $8,
                supplier_id = $9,
                desired_date = $10,
                file_url = $11,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = $1
          `,
          [
            requestId,
            payload.depot_id,
            payload.request_date,
            payload.category_ref_id,
            payload.urgency,
            payload.justification,
            payload.status,
            payload.vehicle_id,
            payload.supplier_id,
            payload.desired_date,
            payload.file_url,
          ],
        );
      } else {
        const { rows } = await db.query(
          `
            INSERT INTO mgx_purchase_requests (
              company_id, depot_id, requested_by, request_date,
              category_ref_id, urgency, justification, status,
              vehicle_id, supplier_id, desired_date, file_url, requested_by_mobile_account_id
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
            RETURNING id
          `,
          [
            companyId,
            payload.depot_id,
            resolveActorUserId(req),
            payload.request_date,
            payload.category_ref_id,
            payload.urgency,
            payload.justification,
            payload.status,
            payload.vehicle_id,
            payload.supplier_id,
            payload.desired_date,
            payload.file_url,
            resolveMobileAccountId(req),
          ],
        );
        requestId = Number(rows[0].id);
      }

      await replaceRequestLines(db, requestId, payload.lines);
      const item = await fetchRequest(db, requestId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: existing ? "UPDATE" : "CREATE",
        entityType: "purchase_request",
        entityId: requestId,
        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 deletePurchaseRequest(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = await fetchRequest(db, req.params.id, companyId);
      if (!existing) throw new ApiError(404, "Demande d'achat introuvable.");
      await db.query("DELETE FROM mgx_purchase_requests WHERE id = $1 AND company_id = $2", [req.params.id, companyId]);
      await logMgxAudit(db, req, {
        companyId,
        action: "DELETE",
        entityType: "purchase_request",
        entityId: Number(req.params.id),
        oldData: existing,
      });
      await db.query("COMMIT");
      res.status(204).send();
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async validatePurchaseRequest(req, res) {
    const companyId = getCompanyId(req);
    await ensurePurchaseRequestColumns();
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = await fetchRequest(db, req.params.id, companyId);
      if (!existing) throw new ApiError(404, "Demande d'achat introuvable.");
      await db.query(
        `
          UPDATE mgx_purchase_requests
          SET status = 'validated',
              validated_by = $2,
              validated_by_mobile_account_id = $4,
              rejected_by_mobile_account_id = NULL,
              validated_at = CURRENT_TIMESTAMP,
              rejected_reason = NULL,
              updated_at = CURRENT_TIMESTAMP
          WHERE id = $1 AND company_id = $3
        `,
        [req.params.id, resolveActorUserId(req), companyId, resolveMobileAccountId(req)],
      );
      const item = await fetchRequest(db, req.params.id, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: "VALIDATE",
        entityType: "purchase_request",
        entityId: Number(req.params.id),
        oldData: existing,
        newData: item,
      });
      await db.query("COMMIT");
      req.app.get("io")?.to(`company:${companyId}`).emit("purchase:validated", {
        request_id: item.id,
        depot: item.depot?.label,
        amount: item.estimated_amount,
      });
      res.json({ item });
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async rejectPurchaseRequest(req, res) {
    const companyId = getCompanyId(req);
    await ensurePurchaseRequestColumns();
    const reason = nullableString(req.body.reason || req.body.rejected_reason);
    if (!reason) throw new ApiError(400, "Le motif de rejet est requis.");
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = await fetchRequest(db, req.params.id, companyId);
      if (!existing) throw new ApiError(404, "Demande d'achat introuvable.");
      await db.query(
        `
          UPDATE mgx_purchase_requests
          SET status = 'rejected',
              rejected_reason = $2,
              validated_by = NULL,
              validated_by_mobile_account_id = NULL,
              rejected_by_mobile_account_id = $4,
              validated_at = NULL,
              updated_at = CURRENT_TIMESTAMP
          WHERE id = $1 AND company_id = $3
        `,
        [req.params.id, reason, companyId, resolveMobileAccountId(req)],
      );
      const item = await fetchRequest(db, req.params.id, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: "REJECT",
        entityType: "purchase_request",
        entityId: Number(req.params.id),
        oldData: existing,
        newData: item,
      });
      await db.query("COMMIT");
      req.app.get("io")?.to(`company:${companyId}`).emit("purchase:rejected", {
        request_id: item.id,
        reason,
        depot: item.depot?.label,
        amount: item.estimated_amount,
      });
      res.json({ item });
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async listPurchaseOrders(req, res) {
    const companyId = getCompanyId(req);
    const { rows } = await pool.query(
      `
        ${orderSelect}
        WHERE o.company_id = $1
        GROUP BY o.id, d.id, c.id, s.id, status.id, r.id
        ORDER BY o.order_date DESC, o.id DESC
      `,
      [companyId],
    );
    res.json({ items: rows.map((row) => orderShape(row)) });
  },

  async getPurchaseOrder(req, res) {
    const companyId = getCompanyId(req);
    const item = await fetchOrder(pool, req.params.id, companyId);
    if (!item) throw new ApiError(404, "Bon de commande introuvable.");
    res.json({ item });
  },

  async savePurchaseOrder(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = req.params.id ? await fetchOrder(db, req.params.id, companyId) : null;
      const lines = normalizeOrderLines(req.body.lines || []);
      if (!lines.length) throw new ApiError(400, "Au moins une ligne est requise.");
      const defaultPaymentTerms = await getDefaultPaymentTerms(db, companyId);
      const totalHt = lines.reduce((sum, line) => sum + line.total, 0);
      const totalTtc = req.body.total_ttc === "" || req.body.total_ttc === undefined || req.body.total_ttc === null
        ? totalHt
        : toNumber(req.body.total_ttc);
      const orderNumber = nullableString(req.body.order_number)
        || existing?.order_number
        || await nextSequentialNumber(db, "mgx_purchase_orders", companyId, "BC");

      let orderId = existing?.id;
      if (existing) {
        await db.query(
          `
            UPDATE mgx_purchase_orders
            SET request_id = $2,
                depot_id = $3,
                supplier_id = $4,
                order_number = $5,
                order_date = $6,
                delivery_date_expected = $7,
                payment_terms = $8,
                category_ref_id = $9,
                total_ht = $10,
                total_ttc = $11,
                status_ref_id = $12,
                notes = $13,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = $1
          `,
          [
            existing.id,
            nullableNumber(req.body.request_id),
            nullableNumber(req.body.depot_id),
            nullableNumber(req.body.supplier_id),
            orderNumber,
            nullableString(req.body.order_date) || new Date().toISOString().slice(0, 10),
            nullableString(req.body.delivery_date_expected),
            nullableNumber(req.body.payment_terms) ?? defaultPaymentTerms,
            nullableNumber(req.body.category_ref_id),
            totalHt,
            totalTtc,
            nullableNumber(req.body.status_ref_id),
            nullableString(req.body.notes),
          ],
        );
      } else {
        const { rows } = await db.query(
          `
            INSERT INTO mgx_purchase_orders (
              company_id, request_id, depot_id, supplier_id, order_number,
              order_date, delivery_date_expected, payment_terms, category_ref_id,
              total_ht, total_ttc, status_ref_id, notes, created_by
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
            RETURNING id
          `,
          [
            companyId,
            nullableNumber(req.body.request_id),
            nullableNumber(req.body.depot_id),
            nullableNumber(req.body.supplier_id),
            orderNumber,
            nullableString(req.body.order_date) || new Date().toISOString().slice(0, 10),
            nullableString(req.body.delivery_date_expected),
            nullableNumber(req.body.payment_terms) ?? defaultPaymentTerms,
            nullableNumber(req.body.category_ref_id),
            totalHt,
            totalTtc,
            nullableNumber(req.body.status_ref_id),
            nullableString(req.body.notes),
            resolveActorUserId(req),
          ],
        );
        orderId = Number(rows[0].id);
      }

      await replaceOrderLines(db, orderId, lines);
      const item = await fetchOrder(db, orderId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: existing ? "UPDATE" : "CREATE",
        entityType: "purchase_order",
        entityId: orderId,
        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 deletePurchaseOrder(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = await fetchOrder(db, req.params.id, companyId);
      if (!existing) throw new ApiError(404, "Bon de commande introuvable.");
      const { rows: receptionRows } = await db.query("SELECT COUNT(*)::int AS count FROM mgx_receptions WHERE order_id = $1", [req.params.id]);
      if (Number(receptionRows[0]?.count || 0) > 0) {
        throw new ApiError(400, "Impossible de supprimer un bon de commande déjà réceptionné.");
      }
      await db.query("DELETE FROM mgx_purchase_orders WHERE id = $1 AND company_id = $2", [req.params.id, companyId]);
      await logMgxAudit(db, req, {
        companyId,
        action: "DELETE",
        entityType: "purchase_order",
        entityId: Number(req.params.id),
        oldData: existing,
      });
      await db.query("COMMIT");
      res.status(204).send();
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async createReception(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const order = await fetchOrder(db, req.body.order_id || req.params.id, companyId);
      if (!order) throw new ApiError(404, "Bon de commande introuvable.");
      const lines = normalizeReceptionLines(req.body.lines || []);
      if (!lines.length) throw new ApiError(400, "Au moins une ligne réceptionnée est requise.");
      const receptionNumber = nullableString(req.body.reception_number)
        || await nextSequentialNumber(db, "mgx_receptions", companyId, "REC");
      const { rows } = await db.query(
        `
          INSERT INTO mgx_receptions (
            company_id, order_id, depot_id, reception_date, reception_number,
            received_by, invoice_number, invoice_amount, file_url, notes
          )
          VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
          RETURNING id
        `,
        [
          companyId,
          order.id,
          nullableNumber(req.body.depot_id) || order.depot_id,
          nullableString(req.body.reception_date) || new Date().toISOString().slice(0, 10),
          receptionNumber,
          resolveActorUserId(req),
          nullableString(req.body.invoice_number || req.body.delivery_note_number),
          nullableNumber(req.body.invoice_amount),
          nullableString(req.body.file_url),
          nullableString(req.body.notes),
        ],
      );
      const receptionId = Number(rows[0].id);

      for (const line of lines) {
        if (line.unit_price < 0 || line.total < 0) {
          throw new ApiError(400, "Les montants de réception doivent être positifs.");
        }

        await db.query(
          `
            INSERT INTO mgx_reception_lines (
              reception_id, order_line_id, item_id, description,
              quantity_received, unit_price, total, is_conforming, notes
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
          `,
          [
            receptionId,
            line.order_line_id,
            line.item_id,
            line.description,
            line.quantity_received,
            line.unit_price,
            line.total,
            line.is_conforming,
            [line.notes, line.lot_number ? `Lot: ${line.lot_number}` : null].filter(Boolean).join(" | ") || null,
          ],
        );

        if (line.order_line_id) {
          const { rows: orderLineRows } = await db.query(
            `
              SELECT quantity_ordered, quantity_received
              FROM mgx_purchase_order_lines
              WHERE id = $1
                AND order_id = $2
              LIMIT 1
            `,
            [line.order_line_id, order.id],
          );
          const orderLine = orderLineRows[0];
          if (!orderLine) {
            throw new ApiError(400, "La ligne de commande liée est introuvable.");
          }

          const ordered = toNumber(orderLine.quantity_ordered);
          const received = toNumber(orderLine.quantity_received);
          if (received + line.quantity_received > ordered) {
            throw new ApiError(
              400,
              `La quantité réceptionnée dépasse la quantité commandée pour "${line.description}".`,
            );
          }

          await db.query(
            `
              UPDATE mgx_purchase_order_lines
              SET quantity_received = COALESCE(quantity_received, 0) + $2
              WHERE id = $1
            `,
            [line.order_line_id, line.quantity_received],
          );
        }

        if (line.item_id && line.quantity_received > 0) {
          const { rows: itemRows } = await db.query(
            `
              SELECT is_consumable
              FROM mgx_consumable_items
              WHERE id = $1
                AND company_id = $2
              LIMIT 1
            `,
            [line.item_id, companyId],
          );
          const item = itemRows[0];
          if (!item?.is_consumable) continue;

          await db.query(
            `
              UPDATE mgx_consumable_items
              SET current_stock_qty = current_stock_qty + $2,
                  updated_at = CURRENT_TIMESTAMP
              WHERE id = $1
            `,
            [line.item_id, line.quantity_received],
          );

          await createConsumableMovementRow(db, {
            companyId,
            itemId: line.item_id,
            depotId: nullableNumber(req.body.depot_id) || order.depot_id,
            movementDate: nullableString(req.body.reception_date) || new Date().toISOString().slice(0, 10),
            quantity: line.quantity_received,
            unitPrice: line.unit_price,
            totalValue: line.total,
            referenceType: "reception",
            referenceId: receptionId,
            notes: [line.description, line.lot_number ? `Lot: ${line.lot_number}` : null].filter(Boolean).join(" | "),
            createdBy: resolveActorUserId(req),
          });
        }
      }

      const item = await fetchReception(db, receptionId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: "CREATE",
        entityType: "reception",
        entityId: receptionId,
        newData: item,
      });
      await db.query("COMMIT");
      res.status(201).json({ item });
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async listReceptions(req, res) {
    const companyId = getCompanyId(req);
    const { rows } = await pool.query(
      `
        ${receptionSelect}
        WHERE rec.company_id = $1
        ORDER BY rec.reception_date DESC, rec.id DESC
      `,
      [companyId],
    );
    res.json({ items: rows.map((row) => receptionShape(row)) });
  },

  async getReception(req, res) {
    const companyId = getCompanyId(req);
    const item = await fetchReception(pool, req.params.id, companyId);
    if (!item) throw new ApiError(404, "Réception introuvable.");
    res.json({ item });
  },

  async listPurchaseReturns(req, res) {
    const companyId = getCompanyId(req);
    const { rows } = await pool.query(
      `
        ${returnSelect}
        WHERE ret.company_id = $1
        ORDER BY ret.return_date DESC, ret.id DESC
      `,
      [companyId],
    );
    res.json({ items: rows.map((row) => returnShape(row)) });
  },

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

  async createPurchaseReturn(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const reception = await fetchReception(db, req.body.reception_id, companyId);
      if (!reception) throw new ApiError(404, "Réception introuvable.");
      const lines = normalizeReturnLines(req.body.lines || []);
      if (!lines.length) throw new ApiError(400, "Au moins une ligne retournée est requise.");

      const returnNumber = nullableString(req.body.return_number)
        || await nextSequentialNumber(db, "mgx_purchase_returns", companyId, "RET");
      const returnDate = nullableString(req.body.return_date) || new Date().toISOString().slice(0, 10);
      const depotId = nullableNumber(req.body.depot_id) || reception.depot_id;
      const linesTotal = lines.reduce((sum, line) => sum + toNumber(line.total), 0);
      const creditAmount = nullableNumber(req.body.credit_amount) ?? linesTotal;
      if (creditAmount < 0) throw new ApiError(400, "Le montant de l'avoir doit être positif.");

      const { rows } = await db.query(
        `
          INSERT INTO mgx_purchase_returns (
            company_id, reception_id, depot_id, return_date, return_number,
            reason, credit_note_number, credit_amount, file_url, notes, created_by
          )
          VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
          RETURNING id
        `,
        [
          companyId,
          reception.id,
          depotId,
          returnDate,
          returnNumber,
          nullableString(req.body.reason),
          nullableString(req.body.credit_note_number),
          creditAmount,
          nullableString(req.body.file_url),
          nullableString(req.body.notes),
          resolveActorUserId(req),
        ],
      );
      const returnId = Number(rows[0].id);

      for (const line of lines) {
        const { rows: receptionLineRows } = await db.query(
          `
            SELECT
              l.*,
              COALESCE((
                SELECT SUM(prl.quantity_returned)
                FROM mgx_purchase_return_lines prl
                WHERE prl.reception_line_id = l.id
              ), 0) AS quantity_returned_total
            FROM mgx_reception_lines l
            WHERE l.id = $1
              AND l.reception_id = $2
            LIMIT 1
          `,
          [line.reception_line_id, reception.id],
        );
        const receptionLine = receptionLineRows[0];
        if (!receptionLine) throw new ApiError(400, "Ligne de réception introuvable.");

        const alreadyReturned = toNumber(receptionLine.quantity_returned_total);
        const received = toNumber(receptionLine.quantity_received);
        const available = Math.max(received - alreadyReturned, 0);
        if (line.quantity_returned > available + 0.0001) {
          throw new ApiError(
            400,
            `La quantité retournée dépasse la quantité disponible pour "${receptionLine.description}".`,
          );
        }

        const itemId = receptionLine.item_id ? Number(receptionLine.item_id) : null;
        const unitPrice = line.unit_price || toNumber(receptionLine.unit_price);
        const total = line.total || line.quantity_returned * unitPrice;

        await db.query(
          `
            INSERT INTO mgx_purchase_return_lines (
              return_id, reception_line_id, item_id, description,
              quantity_returned, unit_price, total, notes
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
          `,
          [
            returnId,
            receptionLine.id,
            itemId,
            line.description || receptionLine.description,
            line.quantity_returned,
            unitPrice,
            total,
            line.notes,
          ],
        );

        if (itemId && line.quantity_returned > 0) {
          const { rows: itemRows } = await db.query(
            `
              SELECT label, current_stock_qty, unit_price, is_consumable
              FROM mgx_consumable_items
              WHERE id = $1
                AND company_id = $2
              LIMIT 1
            `,
            [itemId, companyId],
          );
          const item = itemRows[0];
          if (!item?.is_consumable) continue;
          if (toNumber(item.current_stock_qty) < line.quantity_returned) {
            throw new ApiError(400, `Stock insuffisant pour retourner "${item.label || receptionLine.description}".`);
          }

          await db.query(
            `
              UPDATE mgx_consumable_items
              SET current_stock_qty = current_stock_qty - $2,
                  updated_at = CURRENT_TIMESTAMP
              WHERE id = $1
            `,
            [itemId, line.quantity_returned],
          );

          await createConsumableMovementRow(db, {
            companyId,
            itemId,
            depotId,
            movementType: "out",
            movementDate: returnDate,
            quantity: line.quantity_returned,
            unitPrice,
            totalValue: total,
            referenceType: "purchase_return",
            referenceId: returnId,
            notes: line.description || receptionLine.description,
            createdBy: resolveActorUserId(req),
          });
        }
      }

      const item = await fetchReturn(db, returnId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: "CREATE",
        entityType: "purchase_return",
        entityId: returnId,
        newData: item,
      });
      await db.query("COMMIT");
      res.status(201).json({ item });
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async listServices(req, res) {
    const companyId = getCompanyId(req);
    const params = [companyId];
    const filters = ["s.company_id = $1"];
    if (req.query.supplier_id) {
      params.push(req.query.supplier_id);
      filters.push(`s.supplier_id = $${params.length}`);
    }
    if (req.query.vehicle_id) {
      params.push(req.query.vehicle_id);
      filters.push(`s.vehicle_id = $${params.length}`);
    }
    if (req.query.depot_id) {
      params.push(req.query.depot_id);
      filters.push(`s.depot_id = $${params.length}`);
    }
    if (req.query.service_type_ref_id || req.query.type) {
      params.push(req.query.service_type_ref_id || req.query.type);
      filters.push(`s.service_type_ref_id = $${params.length}`);
    }
    if (req.query.from) {
      params.push(req.query.from);
      filters.push(`s.service_date >= $${params.length}`);
    }
    if (req.query.to) {
      params.push(req.query.to);
      filters.push(`s.service_date <= $${params.length}`);
    }
    const { rows } = await pool.query(
      `
        ${serviceSelect}
        WHERE ${filters.join(" AND ")}
        ORDER BY s.service_date DESC, s.id DESC
      `,
      params,
    );
    res.json({ items: rows.map(serviceShape) });
  },

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

  async saveService(req, res) {
    const companyId = getCompanyId(req);
    const payload = {
      supplier_id: nullableNumber(req.body.supplier_id),
      vehicle_id: nullableNumber(req.body.vehicle_id),
      depot_id: nullableNumber(req.body.depot_id),
      service_type_ref_id: nullableNumber(req.body.service_type_ref_id),
      label: nullableString(req.body.label),
      service_date: nullableString(req.body.service_date) || new Date().toISOString().slice(0, 10),
      amount: toNumber(req.body.amount),
      invoice_number: nullableString(req.body.invoice_number),
      file_url: nullableString(req.body.file_url),
      payment_status: cleanString(req.body.payment_status || "pending").toLowerCase(),
      notes: nullableString(req.body.notes),
    };
    if (!payload.supplier_id) throw new ApiError(400, "Le prestataire est requis.");
    if (!payload.service_type_ref_id) throw new ApiError(400, "Le type de prestation est requis.");
    ensureNonNegativeAmount(payload.amount, "Le montant de la prestation doit être positif.");

    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = req.params.id ? await fetchService(db, req.params.id, companyId) : null;
      let itemId = existing?.id;
      if (existing) {
        await db.query(
          `
            UPDATE mgx_services
            SET supplier_id = $2,
                vehicle_id = $3,
                depot_id = $4,
                service_type_ref_id = $5,
                label = $6,
                service_date = $7,
                amount = $8,
                invoice_number = $9,
                file_url = $10,
                payment_status = $11,
                notes = $12
            WHERE id = $1 AND company_id = $13
          `,
          [
            existing.id,
            payload.supplier_id,
            payload.vehicle_id,
            payload.depot_id,
            payload.service_type_ref_id,
            payload.label,
            payload.service_date,
            payload.amount,
            payload.invoice_number,
            payload.file_url,
            payload.payment_status,
            payload.notes,
            companyId,
          ],
        );
      } else {
        const { rows } = await db.query(
          `
            INSERT INTO mgx_services (
              company_id, supplier_id, vehicle_id, depot_id, service_type_ref_id,
              label, service_date, amount, invoice_number, file_url, payment_status,
              notes, created_by
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
            RETURNING id
          `,
          [
            companyId,
            payload.supplier_id,
            payload.vehicle_id,
            payload.depot_id,
            payload.service_type_ref_id,
            payload.label,
            payload.service_date,
            payload.amount,
            payload.invoice_number,
            payload.file_url,
            payload.payment_status,
            payload.notes,
            resolveActorUserId(req),
          ],
        );
        itemId = Number(rows[0].id);
      }
      const item = await fetchService(db, itemId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: existing ? "UPDATE" : "CREATE",
        entityType: "service",
        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 deleteService(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = await fetchService(db, req.params.id, companyId);
      if (!existing) throw new ApiError(404, "Prestation introuvable.");
      await db.query("DELETE FROM mgx_services WHERE id = $1 AND company_id = $2", [req.params.id, companyId]);
      await logMgxAudit(db, req, {
        companyId,
        action: "DELETE",
        entityType: "service",
        entityId: Number(req.params.id),
        oldData: existing,
      });
      await db.query("COMMIT");
      res.status(204).send();
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async listRentals(req, res) {
    const companyId = getCompanyId(req);
    const { rows } = await pool.query(
      `
        ${rentalSelect}
        WHERE r.company_id = $1
        ORDER BY r.start_date DESC, r.id DESC
      `,
      [companyId],
    );
    res.json({ items: rows.map(rentalShape) });
  },

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

  async saveRental(req, res) {
    const companyId = getCompanyId(req);
    const startDate = nullableString(req.body.start_date) || new Date().toISOString().slice(0, 10);
    const endDate = nullableString(req.body.end_date);
    const dailyRate = nullableNumber(req.body.daily_rate);
    const computedDays = endDate ? Math.max(1, Math.ceil((new Date(endDate).getTime() - new Date(startDate).getTime()) / 86400000) + 1) : 1;
    const totalAmount = req.body.total_amount === "" || req.body.total_amount === undefined || req.body.total_amount === null
      ? toNumber(dailyRate) * computedDays
      : toNumber(req.body.total_amount);
    const payload = {
      vehicle_id: nullableNumber(req.body.vehicle_id),
      depot_id: nullableNumber(req.body.depot_id),
      supplier_id: nullableNumber(req.body.supplier_id),
      rental_type: cleanString(req.body.rental_type || "external").toLowerCase(),
      start_date: startDate,
      end_date: endDate,
      daily_rate: dailyRate,
      total_amount: totalAmount,
      conditions: nullableString(req.body.conditions),
      file_url: nullableString(req.body.file_url),
      status_ref_id: nullableNumber(req.body.status_ref_id),
      alert_days_before: nullableNumber(req.body.alert_days_before) ?? 15,
      notes: nullableString(req.body.notes),
    };
    if (!payload.vehicle_id) throw new ApiError(400, "Le véhicule est requis.");
    if (!payload.supplier_id) throw new ApiError(400, "Le loueur est requis.");
    if (endDate && new Date(endDate).getTime() < new Date(startDate).getTime()) {
      throw new ApiError(400, "La date de fin doit être postérieure ou égale à la date de début.");
    }
    ensureNonNegativeAmount(payload.daily_rate, "Le tarif journalier doit être positif.");
    ensureNonNegativeAmount(payload.total_amount, "Le montant total de la location doit être positif.");

    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = req.params.id ? await fetchRental(db, req.params.id, companyId) : null;
      let itemId = existing?.id;
      if (existing) {
        await db.query(
          `
            UPDATE mgx_rentals
            SET vehicle_id = $2,
                depot_id = $3,
                supplier_id = $4,
                rental_type = $5,
                start_date = $6,
                end_date = $7,
                daily_rate = $8,
                total_amount = $9,
                conditions = $10,
                file_url = $11,
                status_ref_id = $12,
                alert_days_before = $13,
                notes = $14,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = $1 AND company_id = $15
          `,
          [
            existing.id,
            payload.vehicle_id,
            payload.depot_id,
            payload.supplier_id,
            payload.rental_type,
            payload.start_date,
            payload.end_date,
            payload.daily_rate,
            payload.total_amount,
            payload.conditions,
            payload.file_url,
            payload.status_ref_id,
            payload.alert_days_before,
            payload.notes,
            companyId,
          ],
        );
      } else {
        const { rows } = await db.query(
          `
            INSERT INTO mgx_rentals (
              company_id, vehicle_id, depot_id, supplier_id, rental_type, start_date,
              end_date, daily_rate, total_amount, conditions, file_url,
              status_ref_id, alert_days_before, notes, created_by
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
            RETURNING id
          `,
          [
            companyId,
            payload.vehicle_id,
            payload.depot_id,
            payload.supplier_id,
            payload.rental_type,
            payload.start_date,
            payload.end_date,
            payload.daily_rate,
            payload.total_amount,
            payload.conditions,
            payload.file_url,
            payload.status_ref_id,
            payload.alert_days_before,
            payload.notes,
            resolveActorUserId(req),
          ],
        );
        itemId = Number(rows[0].id);
      }
      const item = await fetchRental(db, itemId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: existing ? "UPDATE" : "CREATE",
        entityType: "rental",
        entityId: itemId,
        oldData: existing,
        newData: item,
      });
      await db.query("COMMIT");
      emitRentalAlertIfNeeded(req, companyId, item);
      triggerAlertEngine(req, companyId);
      res.status(existing ? 200 : 201).json({ item });
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async deleteRental(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = await fetchRental(db, req.params.id, companyId);
      if (!existing) throw new ApiError(404, "Location introuvable.");
      await db.query("DELETE FROM mgx_rentals WHERE id = $1 AND company_id = $2", [req.params.id, companyId]);
      await logMgxAudit(db, req, {
        companyId,
        action: "DELETE",
        entityType: "rental",
        entityId: Number(req.params.id),
        oldData: existing,
      });
      await db.query("COMMIT");
      triggerAlertEngine(req, companyId);
      res.status(204).send();
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },
};
