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 supplierSelect = `
  SELECT
    s.*,
    type_ref.id AS supplier_type_id,
    type_ref.code AS supplier_type_code,
    type_ref.label AS supplier_type_label,
    type_ref.color AS supplier_type_color,
    type_ref.icon AS supplier_type_icon,
    type_ref.metadata AS supplier_type_metadata,
    payment_ref.id AS payment_method_id,
    payment_ref.code AS payment_method_code,
    payment_ref.label AS payment_method_label,
    payment_ref.color AS payment_method_color,
    payment_ref.icon AS payment_method_icon,
    payment_ref.metadata AS payment_method_metadata
  FROM mgx_suppliers s
  LEFT JOIN mgx_referentiels type_ref ON type_ref.id = s.supplier_type_ref_id
  LEFT JOIN mgx_referentiels payment_ref ON payment_ref.id = s.payment_method_ref_id
`;

const treasurySelect = `
  SELECT
    tm.*,
    d.code AS depot_code,
    d.label AS depot_label,
    expense.id AS expense_type_id,
    expense.code AS expense_type_code,
    expense.label AS expense_type_label,
    expense.color AS expense_type_color,
    expense.icon AS expense_type_icon,
    expense.metadata AS expense_type_metadata,
    pay.id AS payment_method_id,
    pay.code AS payment_method_code,
    pay.label AS payment_method_label,
    pay.color AS payment_method_color,
    pay.icon AS payment_method_icon,
    pay.metadata AS payment_method_metadata,
    s.code AS supplier_code,
    s.name AS supplier_name,
    v.reference AS vehicle_reference,
    v.plate_number,
    v.brand AS vehicle_brand,
    v.model AS vehicle_model
  FROM mgx_treasury_movements tm
  LEFT JOIN mgx_depots d ON d.id = tm.depot_id
  LEFT JOIN mgx_referentiels expense ON expense.id = tm.expense_type_ref_id
  LEFT JOIN mgx_referentiels pay ON pay.id = tm.payment_method_ref_id
  LEFT JOIN mgx_suppliers s ON s.id = tm.supplier_id
  LEFT JOIN mgx_vehicles v ON v.id = tm.vehicle_id
`;

const mapSupplier = (row) => ({
  id: Number(row.id),
  company_id: Number(row.company_id),
  supplier_type_ref_id: row.supplier_type_ref_id ? Number(row.supplier_type_ref_id) : null,
  supplier_type: mapRef("supplier_type", row),
  code: row.code,
  name: row.name,
  nif: row.nif,
  rc: row.rc,
  address: row.address,
  city: row.city,
  wilaya: row.wilaya,
  phone: row.phone,
  email: row.email,
  fax: row.fax,
  contact_person: row.contact_person,
  payment_terms: Number(row.payment_terms || 0),
  payment_method_ref_id: row.payment_method_ref_id ? Number(row.payment_method_ref_id) : null,
  payment_method: mapRef("payment_method", row),
  credit_limit: row.credit_limit === null ? null : toNumber(row.credit_limit),
  current_balance: toNumber(row.current_balance),
  current_balance_formatted: formatDzdAmount(row.current_balance),
  is_active: Boolean(row.is_active),
  notes: row.notes,
  created_at: row.created_at,
  updated_at: row.updated_at,
});

const mapTreasury = (row) => ({
  id: Number(row.id),
  company_id: Number(row.company_id),
  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,
  treasury_type: row.treasury_type,
  account_name: row.account_name,
  direction: row.direction,
  movement_date: row.movement_date,
  amount: toNumber(row.amount),
  amount_formatted: formatDzdAmount(row.amount),
  label: row.label,
  expense_type_ref_id: row.expense_type_ref_id ? Number(row.expense_type_ref_id) : null,
  expense_type: mapRef("expense_type", row),
  reference_type: row.reference_type,
  reference_id: row.reference_id ? Number(row.reference_id) : null,
  supplier_id: row.supplier_id ? Number(row.supplier_id) : null,
  supplier: row.supplier_id ? { id: Number(row.supplier_id), code: row.supplier_code, name: row.supplier_name } : null,
  vehicle_id: row.vehicle_id ? Number(row.vehicle_id) : null,
  vehicle: 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,
  payment_method_ref_id: row.payment_method_ref_id ? Number(row.payment_method_ref_id) : null,
  payment_method: mapRef("payment_method", row),
  receipt_number: row.receipt_number,
  file_url: row.file_url,
  notes: row.notes,
  created_at: row.created_at,
});

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

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

const buildSupplierOperationRows = async (db, companyId, { supplierId = null, from = null, to = null } = {}) => {
  const params = [companyId];
  const supplierCondition = supplierId ? (() => { params.push(supplierId); return `AND supplier_id = $${params.length}`; })() : "";

  const receptionParams = [companyId];
  let receptionSupplierCondition = "";
  let receptionDateConditions = "";
  if (supplierId) {
    receptionParams.push(supplierId);
    receptionSupplierCondition = `AND o.supplier_id = $${receptionParams.length}`;
  }
  if (from) {
    receptionParams.push(from);
    receptionDateConditions += ` AND rec.reception_date >= $${receptionParams.length}`;
  }
  if (to) {
    receptionParams.push(to);
    receptionDateConditions += ` AND rec.reception_date <= $${receptionParams.length}`;
  }

  const maintenanceParams = [companyId];
  let maintenanceSupplierCondition = "";
  let maintenanceDateConditions = "";
  if (supplierId) {
    maintenanceParams.push(supplierId);
    maintenanceSupplierCondition = `AND i.supplier_id = $${maintenanceParams.length}`;
  }
  if (from) {
    maintenanceParams.push(from);
    maintenanceDateConditions += ` AND COALESCE(i.end_date, i.start_date) >= $${maintenanceParams.length}`;
  }
  if (to) {
    maintenanceParams.push(to);
    maintenanceDateConditions += ` AND COALESCE(i.end_date, i.start_date) <= $${maintenanceParams.length}`;
  }

  const serviceParams = [...params];
  let serviceDateConditions = "";
  if (from) {
    serviceParams.push(from);
    serviceDateConditions += ` AND service_date >= $${serviceParams.length}`;
  }
  if (to) {
    serviceParams.push(to);
    serviceDateConditions += ` AND service_date <= $${serviceParams.length}`;
  }

  const rentalParams = [...params];
  let rentalDateConditions = "";
  if (from) {
    rentalParams.push(from);
    rentalDateConditions += ` AND start_date >= $${rentalParams.length}`;
  }
  if (to) {
    rentalParams.push(to);
    rentalDateConditions += ` AND start_date <= $${rentalParams.length}`;
  }

  const [purchaseRows, maintenanceRows, serviceRows, rentalRows] = await Promise.all([
    db.query(
      `
        SELECT
          o.supplier_id,
          rec.id AS operation_id,
          rec.reception_date AS operation_date,
          (rec.reception_date + (COALESCE(o.payment_terms, s.payment_terms, 0) || ' days')::interval)::date AS due_date,
          'Facture achat' AS operation_type,
          COALESCE(NULLIF(rec.invoice_number, ''), rec.reception_number, o.order_number) AS reference,
          COALESCE(rec.invoice_amount, COALESCE(SUM(rl.total), 0)) AS amount
        FROM mgx_receptions rec
        JOIN mgx_purchase_orders o ON o.id = rec.order_id
        LEFT JOIN mgx_suppliers s ON s.id = o.supplier_id
        LEFT JOIN mgx_reception_lines rl ON rl.reception_id = rec.id
        WHERE rec.company_id = $1
          AND o.supplier_id IS NOT NULL
          ${receptionSupplierCondition}
          ${receptionDateConditions}
        GROUP BY
          o.supplier_id,
          rec.id,
          rec.reception_date,
          o.payment_terms,
          s.payment_terms,
          rec.invoice_number,
          rec.reception_number,
          o.order_number,
          rec.invoice_amount
        HAVING COALESCE(rec.invoice_amount, COALESCE(SUM(rl.total), 0)) > 0
      `,
      receptionParams,
    ),
    db.query(
      `
        SELECT
          i.supplier_id,
          i.id AS operation_id,
          COALESCE(i.end_date, i.start_date) AS operation_date,
          (COALESCE(i.end_date, i.start_date) + (COALESCE(s.payment_terms, 0) || ' days')::interval)::date AS due_date,
          'Entretien' AS operation_type,
          COALESCE(NULLIF(i.invoice_number, ''), 'INT-' || i.id::text) AS reference,
          i.total_cost AS amount
        FROM mgx_interventions i
        LEFT JOIN mgx_suppliers s ON s.id = i.supplier_id
        LEFT JOIN mgx_referentiels status ON status.id = i.status_ref_id
        WHERE i.company_id = $1
          AND i.supplier_id IS NOT NULL
          AND i.end_date IS NOT NULL
          AND i.total_cost > 0
          AND LOWER(COALESCE(status.code, '')) <> 'cancel'
          AND LOWER(COALESCE(status.label, '')) NOT LIKE '%annul%'
          ${maintenanceSupplierCondition}
          ${maintenanceDateConditions}
      `,
      maintenanceParams,
    ),
    db.query(
      `
        SELECT
          supplier_id,
          id AS operation_id,
          service_date AS operation_date,
          (service_date + (COALESCE((SELECT payment_terms FROM mgx_suppliers s WHERE s.id = mgx_services.supplier_id), 0) || ' days')::interval)::date AS due_date,
          'Prestation' AS operation_type,
          COALESCE(invoice_number, 'SRV-' || id::text) AS reference,
          amount
        FROM mgx_services
        WHERE company_id = $1
          ${supplierCondition}
          ${serviceDateConditions}
      `,
      serviceParams,
    ),
    db.query(
      `
        SELECT
          supplier_id,
          id AS operation_id,
          start_date AS operation_date,
          COALESCE(end_date, start_date) AS due_date,
          'Location' AS operation_type,
          ('LOC-' || id::text) AS reference,
          COALESCE(total_amount, 0) AS amount
        FROM mgx_rentals
        WHERE company_id = $1
          ${supplierCondition}
          ${rentalDateConditions}
      `,
      rentalParams,
    ),
  ]);

  return [
    ...purchaseRows.rows.map((row) => ({ ...row, reference_type: "reception" })),
    ...maintenanceRows.rows.map((row) => ({ ...row, reference_type: "maintenance" })),
    ...serviceRows.rows.map((row) => ({ ...row, reference_type: "service" })),
    ...rentalRows.rows.map((row) => ({ ...row, reference_type: "rental" })),
  ].map((row) => ({
    supplier_id: Number(row.supplier_id),
    operation_id: Number(row.operation_id),
    operation_date: row.operation_date,
    due_date: row.due_date,
    operation_type: row.operation_type,
    reference: row.reference,
    amount: toNumber(row.amount),
    reference_type: row.reference_type,
  }));
};

const buildSupplierPaymentRows = async (db, companyId, { supplierId = null, from = null, to = null } = {}) => {
  const params = [companyId];
  const filters = ["tm.company_id = $1", "tm.supplier_id IS NOT NULL"];
  if (supplierId) {
    params.push(supplierId);
    filters.push(`tm.supplier_id = $${params.length}`);
  }
  if (from) {
    params.push(from);
    filters.push(`tm.movement_date >= $${params.length}`);
  }
  if (to) {
    params.push(to);
    filters.push(`tm.movement_date <= $${params.length}`);
  }
  const returnParams = [companyId];
  const returnFilters = ["ret.company_id = $1", "o.supplier_id IS NOT NULL"];
  if (supplierId) {
    returnParams.push(supplierId);
    returnFilters.push(`o.supplier_id = $${returnParams.length}`);
  }
  if (from) {
    returnParams.push(from);
    returnFilters.push(`ret.return_date >= $${returnParams.length}`);
  }
  if (to) {
    returnParams.push(to);
    returnFilters.push(`ret.return_date <= $${returnParams.length}`);
  }

  const [treasuryResult, returnsResult] = await Promise.all([
    db.query(
      `
        SELECT
          tm.supplier_id,
          tm.id AS movement_id,
          tm.movement_date,
          tm.direction,
          tm.amount,
          tm.label,
          tm.reference_type,
          tm.reference_id,
          tm.receipt_number
        FROM mgx_treasury_movements tm
        WHERE ${filters.join(" AND ")}
      `,
      params,
    ),
    db.query(
      `
        SELECT
          o.supplier_id,
          ret.id AS movement_id,
          ret.return_date AS movement_date,
          'in' AS direction,
          COALESCE(ret.credit_amount, COALESCE(SUM(rl.total), 0)) AS amount,
          ('Retour fournisseur ' || ret.return_number) AS label,
          'reception' AS reference_type,
          ret.reception_id AS reference_id,
          COALESCE(NULLIF(ret.credit_note_number, ''), ret.return_number) AS receipt_number
        FROM mgx_purchase_returns ret
        JOIN mgx_receptions rec ON rec.id = ret.reception_id
        JOIN mgx_purchase_orders o ON o.id = rec.order_id
        LEFT JOIN mgx_purchase_return_lines rl ON rl.return_id = ret.id
        WHERE ${returnFilters.join(" AND ")}
        GROUP BY
          o.supplier_id,
          ret.id,
          ret.return_date,
          ret.credit_amount,
          ret.return_number,
          ret.credit_note_number,
          ret.reception_id
        HAVING COALESCE(ret.credit_amount, COALESCE(SUM(rl.total), 0)) > 0
      `,
      returnParams,
    ),
  ]);

  return [...treasuryResult.rows, ...returnsResult.rows].map((row) => ({
    supplier_id: Number(row.supplier_id),
    movement_id: Number(row.movement_id),
    movement_date: row.movement_date,
    direction: row.direction,
    amount: toNumber(row.amount),
    label: row.label,
    reference_type: row.reference_type,
    reference_id: row.reference_id ? Number(row.reference_id) : null,
    receipt_number: row.receipt_number,
  }));
};

const getSupplierStatementRows = async (db, companyId, supplierId, { from = null, to = null } = {}) => {
  const [supplier, operations, payments] = await Promise.all([
    fetchSupplier(db, supplierId, companyId),
    buildSupplierOperationRows(db, companyId, { supplierId, from, to }),
    buildSupplierPaymentRows(db, companyId, { supplierId, from, to }),
  ]);

  const rows = [
    ...(supplier && Math.abs(supplier.current_balance) > 0.01 ? [{
      supplier_id: supplier.id,
      date: from || supplier.created_at,
      piece_number: "SOLDE-INITIAL",
      operation_type: "Solde initial",
      reference: "Solde initial fournisseur",
      debit: supplier.current_balance > 0 ? supplier.current_balance : 0,
      credit: supplier.current_balance < 0 ? Math.abs(supplier.current_balance) : 0,
      sort_key: "0000-0-solde-initial",
    }] : []),
    ...operations.map((row) => ({
      supplier_id: row.supplier_id,
      date: row.operation_date,
      piece_number: row.reference,
      operation_type: row.operation_type,
      reference: row.reference,
      debit: row.amount,
      credit: 0,
      sort_key: `${row.operation_date}-1-${row.operation_id}`,
    })),
    ...payments.map((row) => ({
      supplier_id: row.supplier_id,
      date: row.movement_date,
      piece_number: row.receipt_number || `TRS-${row.movement_id}`,
      operation_type: row.direction === "out" ? "Règlement" : "Avoir",
      reference: row.label || row.receipt_number || `TRS-${row.movement_id}`,
      debit: 0,
      credit: row.amount,
      sort_key: `${row.movement_date}-2-${row.movement_id}`,
    })),
  ].sort((left, right) => left.sort_key.localeCompare(right.sort_key));

  let runningBalance = 0;
  return rows.map((row) => {
    runningBalance += row.debit - row.credit;
    return {
      ...row,
      running_balance: runningBalance,
      debit_formatted: formatDzdAmount(row.debit),
      credit_formatted: formatDzdAmount(row.credit),
      running_balance_formatted: formatDzdAmount(runningBalance),
    };
  });
};

const getSupplierScheduleRows = async (db, companyId, { supplierId = null } = {}) => {
  const supplierParams = [companyId];
  const supplierCondition = supplierId ? (() => {
    supplierParams.push(supplierId);
    return `AND s.id = $${supplierParams.length}`;
  })() : "";
  const [initialBalances, operations, payments] = await Promise.all([
    db.query(
      `
        SELECT s.id, s.current_balance, s.created_at
        FROM mgx_suppliers s
        WHERE s.company_id = $1
          ${supplierCondition}
          AND s.current_balance > 0
      `,
      supplierParams,
    ),
    buildSupplierOperationRows(db, companyId, { supplierId }),
    buildSupplierPaymentRows(db, companyId, { supplierId }),
  ]);
  const initialOperations = initialBalances.rows.map((row) => ({
    supplier_id: Number(row.id),
    reference_type: "initial_balance",
    operation_id: Number(row.id),
    operation_date: row.created_at,
    due_date: row.created_at,
    operation_type: "Solde initial",
    reference: "Solde initial fournisseur",
    amount: toNumber(row.current_balance),
  }));
  const allOperations = [...initialOperations, ...operations];
  const operationKeys = new Set(allOperations.map((operation) => `${operation.reference_type}:${operation.operation_id}`));
  const paidByReference = new Map();
  const unallocatedBySupplier = new Map();
  for (const payment of payments) {
    const key = payment.reference_type && payment.reference_id ? `${payment.reference_type}:${payment.reference_id}` : null;
    if (key && operationKeys.has(key)) {
      paidByReference.set(key, (paidByReference.get(key) || 0) + payment.amount);
    } else {
      unallocatedBySupplier.set(payment.supplier_id, (unallocatedBySupplier.get(payment.supplier_id) || 0) + payment.amount);
    }
  }
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  return allOperations.sort((left, right) => {
    const leftDate = left.due_date || left.operation_date || "";
    const rightDate = right.due_date || right.operation_date || "";
    return String(leftDate).localeCompare(String(rightDate)) || String(left.reference || "").localeCompare(String(right.reference || ""));
  }).map((operation) => {
    const key = `${operation.reference_type}:${operation.operation_id}`;
    const directPaid = paidByReference.get(key) || 0;
    const availableUnallocated = unallocatedBySupplier.get(operation.supplier_id) || 0;
    const allocatedPayment = Math.min(Math.max(operation.amount - directPaid, 0), availableUnallocated);
    if (allocatedPayment > 0) {
      unallocatedBySupplier.set(operation.supplier_id, availableUnallocated - allocatedPayment);
    }
    const paid = directPaid + allocatedPayment;
    const outstanding = Math.max(operation.amount - paid, 0);
    const dueDate = operation.due_date || operation.operation_date;
    const due = dueDate ? new Date(dueDate) : null;
    const isOverdue = due ? due < today : false;
    const isDueSoon = due ? Math.ceil((due.getTime() - today.getTime()) / 86400000) <= 7 && due >= today : false;
    return {
      supplier_id: operation.supplier_id,
      reference_type: operation.reference_type,
      reference_id: operation.operation_id,
      date_echeance: dueDate,
      type: operation.operation_type,
      reference: operation.reference,
      amount: operation.amount,
      amount_formatted: formatDzdAmount(operation.amount),
      paid_amount: paid,
      outstanding_amount: outstanding,
      outstanding_amount_formatted: formatDzdAmount(outstanding),
      status: outstanding <= 0.01 ? "Payé" : isOverdue ? "En retard" : "En attente",
      is_overdue: isOverdue,
      is_due_soon: isDueSoon,
    };
  }).filter((row) => row.outstanding_amount > 0.01);
};

const withRunningBalances = (items) => {
  const balances = new Map();
  return [...items]
    .sort((left, right) => {
      const leftKey = `${left.account_name || ""}-${left.movement_date}-${left.id}`;
      const rightKey = `${right.account_name || ""}-${right.movement_date}-${right.id}`;
      return leftKey.localeCompare(rightKey);
    })
    .map((item) => {
      const accountKey = `${item.treasury_type}:${item.account_name}`;
      const current = balances.get(accountKey) || 0;
      const next = current + (item.direction === "in" ? item.amount : -item.amount);
      balances.set(accountKey, next);
      return {
        ...item,
        running_balance: next,
        running_balance_formatted: formatDzdAmount(next),
      };
    });
};

export const mgxSuppliersTreasuryController = {
  async listSuppliers(req, res) {
    const companyId = getCompanyId(req);
    const search = cleanString(req.query.search).toLowerCase();
    const active = req.query.active;
    const type = req.query.type;
    const params = [companyId];
    const conditions = ["s.company_id = $1"];
    if (search) {
      params.push(`%${search}%`);
      conditions.push(`(lower(s.name) LIKE $${params.length} OR lower(COALESCE(s.code, '')) LIKE $${params.length})`);
    }
    if (type) {
      params.push(type);
      conditions.push(`s.supplier_type_ref_id = $${params.length}`);
    }
    if (active === "true" || active === "false") {
      params.push(active === "true");
      conditions.push(`s.is_active = $${params.length}`);
    }

    const { rows } = await pool.query(
      `
        ${supplierSelect}
        WHERE ${conditions.join(" AND ")}
        ORDER BY s.name ASC
      `,
      params,
    );

    const operations = await buildSupplierOperationRows(pool, companyId);
    const payments = await buildSupplierPaymentRows(pool, companyId);
    const lastPurchaseMap = new Map();
    const dueMap = new Map();
    for (const operation of operations) {
      const last = lastPurchaseMap.get(operation.supplier_id);
      if (!last || String(operation.operation_date) > String(last)) lastPurchaseMap.set(operation.supplier_id, operation.operation_date);
      dueMap.set(operation.supplier_id, (dueMap.get(operation.supplier_id) || 0) + operation.amount);
    }
    for (const payment of payments) {
      dueMap.set(payment.supplier_id, (dueMap.get(payment.supplier_id) || 0) - payment.amount);
    }

    res.json({
      items: rows.map((row) => {
        const item = mapSupplier(row);
        const balanceDue = item.current_balance + (dueMap.get(item.id) || 0);
        return {
          ...item,
          balance_due: balanceDue,
          balance_due_formatted: formatDzdAmount(balanceDue),
          last_purchase_date: lastPurchaseMap.get(item.id) || null,
        };
      }),
    });
  },

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

  async saveSupplier(req, res) {
    const companyId = getCompanyId(req);
    const payload = {
      supplier_type_ref_id: nullableNumber(req.body.supplier_type_ref_id),
      code: nullableString(req.body.code),
      name: nullableString(req.body.name),
      nif: nullableString(req.body.nif),
      rc: nullableString(req.body.rc),
      address: nullableString(req.body.address),
      city: nullableString(req.body.city),
      wilaya: nullableString(req.body.wilaya),
      phone: nullableString(req.body.phone),
      email: nullableString(req.body.email),
      fax: nullableString(req.body.fax),
      contact_person: nullableString(req.body.contact_person),
      payment_terms: nullableNumber(req.body.payment_terms) ?? 30,
      payment_method_ref_id: nullableNumber(req.body.payment_method_ref_id),
      credit_limit: nullableNumber(req.body.credit_limit),
      current_balance: toNumber(req.body.current_balance),
      notes: nullableString(req.body.notes),
      is_active: req.body.is_active !== false,
    };
    if (!payload.code || !payload.name) throw new ApiError(400, "Code et nom sont requis.");
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = req.params.id ? await fetchSupplier(db, req.params.id, companyId) : null;
      let supplierId = existing?.id;
      if (existing) {
        await db.query(
          `
            UPDATE mgx_suppliers
            SET supplier_type_ref_id = $2,
                code = $3,
                name = $4,
                nif = $5,
                rc = $6,
                address = $7,
                city = $8,
                wilaya = $9,
                phone = $10,
                email = $11,
                fax = $12,
                contact_person = $13,
                payment_terms = $14,
                payment_method_ref_id = $15,
                credit_limit = $16,
                current_balance = $17,
                notes = $18,
                is_active = $19,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = $1 AND company_id = $20
          `,
          [
            existing.id,
            payload.supplier_type_ref_id,
            payload.code,
            payload.name,
            payload.nif,
            payload.rc,
            payload.address,
            payload.city,
            payload.wilaya,
            payload.phone,
            payload.email,
            payload.fax,
            payload.contact_person,
            payload.payment_terms,
            payload.payment_method_ref_id,
            payload.credit_limit,
            payload.current_balance,
            payload.notes,
            payload.is_active,
            companyId,
          ],
        );
      } else {
        const { rows } = await db.query(
          `
            INSERT INTO mgx_suppliers (
              company_id, supplier_type_ref_id, code, name, nif, rc, address,
              city, wilaya, phone, email, fax, contact_person, payment_terms,
              payment_method_ref_id, credit_limit, current_balance, notes, is_active
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
            RETURNING id
          `,
          [
            companyId,
            payload.supplier_type_ref_id,
            payload.code,
            payload.name,
            payload.nif,
            payload.rc,
            payload.address,
            payload.city,
            payload.wilaya,
            payload.phone,
            payload.email,
            payload.fax,
            payload.contact_person,
            payload.payment_terms,
            payload.payment_method_ref_id,
            payload.credit_limit,
            payload.current_balance,
            payload.notes,
            payload.is_active,
          ],
        );
        supplierId = Number(rows[0].id);
      }
      const item = await fetchSupplier(db, supplierId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: existing ? "UPDATE" : "CREATE",
        entityType: "supplier",
        entityId: supplierId,
        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 deleteSupplier(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = await fetchSupplier(db, req.params.id, companyId);
      if (!existing) throw new ApiError(404, "Fournisseur introuvable.");
      await db.query(
        `
          UPDATE mgx_suppliers
          SET is_active = false, updated_at = CURRENT_TIMESTAMP
          WHERE id = $1 AND company_id = $2
        `,
        [req.params.id, companyId],
      );
      const item = await fetchSupplier(db, req.params.id, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: "DELETE",
        entityType: "supplier",
        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 getSupplierStatement(req, res) {
    const companyId = getCompanyId(req);
    const item = await fetchSupplier(pool, req.params.id, companyId);
    if (!item) throw new ApiError(404, "Fournisseur introuvable.");
    const rows = await getSupplierStatementRows(pool, companyId, Number(req.params.id), {
      from: nullableString(req.query.from),
      to: nullableString(req.query.to),
    });
    const totals = rows.reduce((accumulator, row) => ({
      debit: accumulator.debit + row.debit,
      credit: accumulator.credit + row.credit,
      balance: row.running_balance,
    }), { debit: 0, credit: 0, balance: 0 });
    res.json({
      item,
      rows,
      totals: {
        ...totals,
        debit_formatted: formatDzdAmount(totals.debit),
        credit_formatted: formatDzdAmount(totals.credit),
        balance_formatted: formatDzdAmount(totals.balance),
      },
    });
  },

  async getSupplierBalance(req, res) {
    const companyId = getCompanyId(req);
    const asOf = nullableString(req.query.as_of) || nullableString(req.query.to);
    const from = nullableString(req.query.from);
    const type = nullableString(req.query.type);
    const suppliersResponse = await pool.query(
      `
        ${supplierSelect}
        WHERE s.company_id = $1
          ${type ? "AND s.supplier_type_ref_id = $2" : ""}
        ORDER BY s.name ASC
      `,
      type ? [companyId, type] : [companyId],
    );
    const schedules = await getSupplierScheduleRows(pool, companyId);
    const scheduleMap = new Map();
    for (const row of schedules) {
      const current = scheduleMap.get(row.supplier_id);
      if (!current || String(row.date_echeance) < String(current.date_echeance)) {
        scheduleMap.set(row.supplier_id, row);
      }
    }
    const items = await Promise.all(suppliersResponse.rows.map(async (row) => {
      const supplier = mapSupplier(row);
      const statementRows = await getSupplierStatementRows(pool, companyId, supplier.id, { from, to: asOf });
      const periodRows = await getSupplierStatementRows(pool, companyId, supplier.id, { from, to: asOf });
      const totalPurchases = periodRows.filter((item) => !["Règlement", "Avoir", "Solde initial"].includes(item.operation_type)).reduce((sum, item) => sum + item.debit, 0);
      const totalPayments = periodRows.filter((item) => item.operation_type === "Règlement" || item.operation_type === "Avoir").reduce((sum, item) => sum + item.credit, 0);
      const balance = statementRows.at(-1)?.running_balance || 0;
      return {
        ...supplier,
        total_purchases: totalPurchases,
        total_payments: totalPayments,
        balance_due: balance,
        next_due_date: scheduleMap.get(supplier.id)?.date_echeance || null,
      };
    }));
    const totals = items.reduce((accumulator, item) => ({
      total_purchases: accumulator.total_purchases + item.total_purchases,
      total_payments: accumulator.total_payments + item.total_payments,
      balance_due: accumulator.balance_due + item.balance_due,
    }), { total_purchases: 0, total_payments: 0, balance_due: 0 });
    res.json({
      items: items.map((item) => ({
        ...item,
        total_purchases_formatted: formatDzdAmount(item.total_purchases),
        total_payments_formatted: formatDzdAmount(item.total_payments),
        balance_due_formatted: formatDzdAmount(item.balance_due),
      })),
      totals: {
        ...totals,
        total_purchases_formatted: formatDzdAmount(totals.total_purchases),
        total_payments_formatted: formatDzdAmount(totals.total_payments),
        balance_due_formatted: formatDzdAmount(totals.balance_due),
      },
    });
  },

  async getSupplierGrandLivre(req, res) {
    const companyId = getCompanyId(req);
    const ids = cleanString(req.query.supplier_ids).split(",").map((value) => Number(value)).filter(Boolean);
    const suppliersResult = await pool.query(
      `
        ${supplierSelect}
        WHERE s.company_id = $1
          ${ids.length ? `AND s.id = ANY($2::bigint[])` : ""}
        ORDER BY s.name ASC
      `,
      ids.length ? [companyId, ids] : [companyId],
    );
    const items = await Promise.all(suppliersResult.rows.map(async (row) => {
      const supplier = mapSupplier(row);
      const rows = await getSupplierStatementRows(pool, companyId, supplier.id, {
        from: nullableString(req.query.from),
        to: nullableString(req.query.to),
      });
      const totals = rows.reduce((accumulator, current) => ({
        debit: accumulator.debit + current.debit,
        credit: accumulator.credit + current.credit,
      }), { debit: 0, credit: 0 });
      return {
        supplier,
        rows,
        totals: {
          ...totals,
          balance: rows.at(-1)?.running_balance || 0,
          debit_formatted: formatDzdAmount(totals.debit),
          credit_formatted: formatDzdAmount(totals.credit),
          balance_formatted: formatDzdAmount(rows.at(-1)?.running_balance || 0),
        },
      };
    }));
    res.json({ items });
  },

  async getSupplierSchedule(req, res) {
    const companyId = getCompanyId(req);
    const suppliers = await pool.query(
      `
        ${supplierSelect}
        WHERE s.company_id = $1
      `,
      [companyId],
    );
    const supplierMap = new Map(suppliers.rows.map((row) => [Number(row.id), mapSupplier(row)]));
    const items = await getSupplierScheduleRows(pool, companyId, {});
    res.json({
      items: items
        .map((item) => ({
          ...item,
          supplier: supplierMap.get(item.supplier_id),
        }))
        .sort((left, right) => String(left.date_echeance).localeCompare(String(right.date_echeance))),
    });
  },

  async listTreasury(req, res) {
    const companyId = getCompanyId(req);
    const params = [companyId];
    const conditions = ["tm.company_id = $1"];
    if (req.query.depot_id) {
      params.push(req.query.depot_id);
      conditions.push(`tm.depot_id = $${params.length}`);
    }
    if (req.query.type) {
      params.push(req.query.type);
      conditions.push(`tm.treasury_type = $${params.length}`);
    }
    if (req.query.direction) {
      params.push(req.query.direction);
      conditions.push(`tm.direction = $${params.length}`);
    }
    if (req.query.expense_type_ref_id) {
      params.push(req.query.expense_type_ref_id);
      conditions.push(`tm.expense_type_ref_id = $${params.length}`);
    }
    if (req.query.from) {
      params.push(req.query.from);
      conditions.push(`tm.movement_date >= $${params.length}`);
    }
    if (req.query.to) {
      params.push(req.query.to);
      conditions.push(`tm.movement_date <= $${params.length}`);
    }
    const { rows } = await pool.query(
      `
        ${treasurySelect}
        WHERE ${conditions.join(" AND ")}
        ORDER BY tm.account_name ASC, tm.movement_date ASC, tm.id ASC
      `,
      params,
    );
    res.json({ items: withRunningBalances(rows.map(mapTreasury)) });
  },

  async getTreasury(req, res) {
    const companyId = getCompanyId(req);
    const item = await fetchTreasury(pool, req.params.id, companyId);
    if (!item) throw new ApiError(404, "Mouvement de trésorerie introuvable.");
    res.json({ item });
  },

  async saveTreasury(req, res) {
    const companyId = getCompanyId(req);
    const payload = {
      depot_id: nullableNumber(req.body.depot_id),
      treasury_type: cleanString(req.body.treasury_type || "cash"),
      account_name: nullableString(req.body.account_name),
      direction: cleanString(req.body.direction || "out"),
      movement_date: nullableString(req.body.movement_date) || new Date().toISOString().slice(0, 10),
      amount: toNumber(req.body.amount),
      label: nullableString(req.body.label),
      expense_type_ref_id: nullableNumber(req.body.expense_type_ref_id),
      reference_type: nullableString(req.body.reference_type),
      reference_id: nullableNumber(req.body.reference_id),
      supplier_id: nullableNumber(req.body.supplier_id),
      vehicle_id: nullableNumber(req.body.vehicle_id),
      payment_method_ref_id: nullableNumber(req.body.payment_method_ref_id),
      receipt_number: nullableString(req.body.receipt_number),
      file_url: nullableString(req.body.file_url),
      notes: nullableString(req.body.notes),
    };
    if (!payload.account_name || !payload.label) throw new ApiError(400, "Compte et libellé sont requis.");
    if (!["cash", "bank"].includes(payload.treasury_type)) {
      throw new ApiError(400, "Type de trésorerie invalide.");
    }
    if (!["in", "out"].includes(payload.direction)) {
      throw new ApiError(400, "Sens du mouvement invalide.");
    }
    if (payload.amount <= 0) throw new ApiError(400, "Le montant du mouvement doit être supérieur à zéro.");
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      if (payload.reference_type === "reception" && payload.reference_id) {
        const { rows: referenceRows } = await db.query(
          `
            SELECT
              rec.id,
              rec.depot_id,
              o.supplier_id
            FROM mgx_receptions rec
            JOIN mgx_purchase_orders o ON o.id = rec.order_id
            WHERE rec.id = $1
              AND rec.company_id = $2
            LIMIT 1
          `,
          [payload.reference_id, companyId],
        );
        const reference = referenceRows[0];
        if (!reference) throw new ApiError(400, "Réception liée introuvable.");
        if (!reference.supplier_id) throw new ApiError(400, "La réception liée n'a pas de fournisseur.");
        if (payload.supplier_id && Number(payload.supplier_id) !== Number(reference.supplier_id)) {
          throw new ApiError(400, "Le fournisseur ne correspond pas à la réception liée.");
        }
        payload.supplier_id = Number(reference.supplier_id);
        payload.depot_id = payload.depot_id || (reference.depot_id ? Number(reference.depot_id) : null);
      }
      const existing = req.params.id ? await fetchTreasury(db, req.params.id, companyId) : null;
      let movementId = existing?.id;
      if (existing) {
        await db.query(
          `
            UPDATE mgx_treasury_movements
            SET depot_id = $2,
                treasury_type = $3,
                account_name = $4,
                direction = $5,
                movement_date = $6,
                amount = $7,
                label = $8,
                expense_type_ref_id = $9,
                reference_type = $10,
                reference_id = $11,
                supplier_id = $12,
                vehicle_id = $13,
                payment_method_ref_id = $14,
                receipt_number = $15,
                file_url = $16,
                notes = $17
            WHERE id = $1 AND company_id = $18
          `,
          [
            existing.id,
            payload.depot_id,
            payload.treasury_type,
            payload.account_name,
            payload.direction,
            payload.movement_date,
            payload.amount,
            payload.label,
            payload.expense_type_ref_id,
            payload.reference_type,
            payload.reference_id,
            payload.supplier_id,
            payload.vehicle_id,
            payload.payment_method_ref_id,
            payload.receipt_number,
            payload.file_url,
            payload.notes,
            companyId,
          ],
        );
      } else {
        const { rows } = await db.query(
          `
            INSERT INTO mgx_treasury_movements (
              company_id, depot_id, treasury_type, account_name, direction,
              movement_date, amount, label, expense_type_ref_id, reference_type,
              reference_id, supplier_id, vehicle_id, payment_method_ref_id,
              receipt_number, file_url, notes, created_by
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
            RETURNING id
          `,
          [
            companyId,
            payload.depot_id,
            payload.treasury_type,
            payload.account_name,
            payload.direction,
            payload.movement_date,
            payload.amount,
            payload.label,
            payload.expense_type_ref_id,
            payload.reference_type,
            payload.reference_id,
            payload.supplier_id,
            payload.vehicle_id,
            payload.payment_method_ref_id,
            payload.receipt_number,
            payload.file_url,
            payload.notes,
            req.user?.sub || null,
          ],
        );
        movementId = Number(rows[0].id);
      }
      const item = await fetchTreasury(db, movementId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: existing ? "UPDATE" : "CREATE",
        entityType: "treasury_movement",
        entityId: movementId,
        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 deleteTreasury(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const existing = await fetchTreasury(db, req.params.id, companyId);
      if (!existing) throw new ApiError(404, "Mouvement introuvable.");
      await db.query("DELETE FROM mgx_treasury_movements WHERE id = $1 AND company_id = $2", [req.params.id, companyId]);
      await logMgxAudit(db, req, {
        companyId,
        action: "DELETE",
        entityType: "treasury_movement",
        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 getTreasuryReports(req, res) {
    const companyId = getCompanyId(req);
    const params = {
      query: { ...req.query, company_id: companyId },
      body: {},
      user: req.user,
    };
    const fakeReq = { ...req, query: params.query };
    const company = getCompanyId(fakeReq);
    const values = [company];
    const conditions = ["tm.company_id = $1"];
    if (req.query.depot_id) {
      values.push(req.query.depot_id);
      conditions.push(`tm.depot_id = $${values.length}`);
    }
    if (req.query.direction) {
      values.push(req.query.direction);
      conditions.push(`tm.direction = $${values.length}`);
    }
    if (req.query.expense_type_ref_id) {
      values.push(req.query.expense_type_ref_id);
      conditions.push(`tm.expense_type_ref_id = $${values.length}`);
    }
    if (req.query.from) {
      values.push(req.query.from);
      conditions.push(`tm.movement_date >= $${values.length}`);
    }
    if (req.query.to) {
      values.push(req.query.to);
      conditions.push(`tm.movement_date <= $${values.length}`);
    }
    const { rows } = await pool.query(
      `
        ${treasurySelect}
        WHERE ${conditions.join(" AND ")}
        ORDER BY tm.movement_date ASC, tm.id ASC
      `,
      values,
    );
    const items = withRunningBalances(rows.map(mapTreasury));
    const totalIn = items.filter((item) => item.direction === "in").reduce((sum, item) => sum + item.amount, 0);
    const totalOut = items.filter((item) => item.direction === "out").reduce((sum, item) => sum + item.amount, 0);
    const byDayMap = new Map();
    for (const item of items) {
      const current = byDayMap.get(item.movement_date) || { date: item.movement_date, balance: 0 };
      current.balance += item.direction === "in" ? item.amount : -item.amount;
      byDayMap.set(item.movement_date, current);
    }
    let cumulative = 0;
    const dailyEvolution = [...byDayMap.values()].sort((a, b) => String(a.date).localeCompare(String(b.date))).map((item) => {
      cumulative += item.balance;
      return { ...item, cumulative_balance: cumulative };
    });
    const byDepotMap = new Map();
    const byExpenseMap = new Map();
    for (const item of items) {
      const depotKey = item.depot?.label || "Sans dépôt";
      const depotValue = byDepotMap.get(depotKey) || { depot_label: depotKey, total_in: 0, total_out: 0 };
      if (item.direction === "in") depotValue.total_in += item.amount;
      else depotValue.total_out += item.amount;
      byDepotMap.set(depotKey, depotValue);

      const expenseKey = item.expense_type?.label || "Sans type";
      byExpenseMap.set(expenseKey, (byExpenseMap.get(expenseKey) || 0) + item.amount);
    }
    res.json({
      summary: {
        total_in: totalIn,
        total_out: totalOut,
        net_balance: totalIn - totalOut,
        month_count: items.length,
        total_in_formatted: formatDzdAmount(totalIn),
        total_out_formatted: formatDzdAmount(totalOut),
        net_balance_formatted: formatDzdAmount(totalIn - totalOut),
      },
      daily_evolution: dailyEvolution,
      by_depot: [...byDepotMap.values()].map((item) => ({
        ...item,
        net_balance: item.total_in - item.total_out,
        total_in_formatted: formatDzdAmount(item.total_in),
        total_out_formatted: formatDzdAmount(item.total_out),
        net_balance_formatted: formatDzdAmount(item.total_in - item.total_out),
      })),
      by_expense_type: [...byExpenseMap.entries()].map(([label, amount]) => ({
        label,
        amount,
        amount_formatted: formatDzdAmount(amount),
        percentage: totalOut ? (amount / totalOut) * 100 : 0,
      })),
    });
  },

  async getTreasuryBalanceByDepot(req, res) {
    const companyId = getCompanyId(req);
    const { rows } = await pool.query(
      `
        SELECT
          d.id,
          d.label,
          COALESCE(SUM(CASE WHEN tm.direction = 'in' THEN tm.amount ELSE 0 END), 0) AS total_in,
          COALESCE(SUM(CASE WHEN tm.direction = 'out' THEN tm.amount ELSE 0 END), 0) AS total_out
        FROM mgx_depots d
        LEFT JOIN mgx_treasury_movements tm ON tm.depot_id = d.id AND tm.company_id = d.company_id
        WHERE d.company_id = $1
        GROUP BY d.id
        ORDER BY d.label ASC
      `,
      [companyId],
    );
    res.json({
      items: rows.map((row) => ({
        id: Number(row.id),
        label: row.label,
        total_in: toNumber(row.total_in),
        total_out: toNumber(row.total_out),
        net_balance: toNumber(row.total_in) - toNumber(row.total_out),
      })),
    });
  },
};
