import XLSX from "xlsx";

import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import { logAudit } from "../utils/auditLog.js";
import { formatDzdAmount, toNumber } from "../utils/formatters.js";

const PAYMENT_METHOD_OPTIONS = [
  { value: "cash", label: "Espèces" },
  { value: "check", label: "Chèque" },
  { value: "transfer", label: "Virement" },
  { value: "other", label: "Autre" },
];

const MAX_IMPORT_PREVIEW_ROWS = 20;
const THIRD_PARTY_TEMPLATE_HEADERS = [
  "Code",
  "Raison sociale",
  "NIF",
  "RC",
  "NIS",
  "AI (Numéro article)",
  "Secteur d'activité",
  "Date d'inscription",
  "Adresse",
  "Ville",
  "Wilaya",
  "Code postal",
  "Téléphone",
  "Email",
  "Fax",
  "Contact principal",
  "Compte collectif",
  "Délai de paiement",
  "Plafond de crédit",
  "Mode de règlement",
  "Actif",
];

const THIRD_PARTY_IMPORT_ALIASES = {
  ref_code: ["code", "ref_code", "reference", "référence", "numero", "numéro"],
  name: [
    "raison_sociale",
    "raison sociale",
    "name",
    "nom",
    "company_name",
    "societe",
    "société",
  ],
  nif: ["nif"],
  rc: ["rc"],
  nis: ["nis"],
  ai: ["ai", "ai_numero_article", "ai_numero_d_article", "numero_article"],
  industry_sector: [
    "secteur_d_activite",
    "secteur_dactivite",
    "secteur_activite",
    "secteur",
    "industry_sector",
  ],
  registration_date: [
    "date_d_inscription",
    "date_inscription",
    "registration_date",
  ],
  address: ["adresse", "address"],
  city: ["ville", "city"],
  wilaya: ["wilaya"],
  postal_code: ["code_postal", "postal_code"],
  phone: ["telephone", "téléphone", "phone", "tel"],
  email: ["email", "e_mail"],
  fax: ["fax"],
  principal_contact: ["contact_principal", "principal_contact", "contact"],
  account_code: ["compte_collectif", "collective_account", "account_code", "compte"],
  payment_terms: ["delai_de_paiement", "délai_de_paiement", "payment_terms"],
  credit_limit: ["plafond_de_credit", "plafond_de_crédit", "credit_limit"],
  preferred_payment_method: [
    "mode_de_reglement",
    "mode_de_règlement",
    "preferred_payment_method",
    "payment_method",
  ],
  is_active: ["actif", "is_active", "active"],
};

const HISTORY_ACTION_LABELS = {
  "clients.create": "Client créé",
  "clients.update": "Client mis à jour",
  "clients.delete": "Client désactivé",
  "suppliers.create": "Fournisseur créé",
  "suppliers.update": "Fournisseur mis à jour",
  "suppliers.delete": "Fournisseur désactivé",
};

const THIRD_PARTY_CONFIG = {
  client: {
    route: "clients",
    singularLabel: "client",
    pluralLabel: "clients",
    displayLabel: "Client",
    displayPluralLabel: "Clients",
    defaultCodePrefix: "CL",
    defaultCollectiveCodes: ["411000", "411"],
    balanceMode: "debit",
    defaultStatementTitle: "Balance âgée clients",
    defaultScheduleTitle: "Échéancier clients",
  },
  supplier: {
    route: "suppliers",
    singularLabel: "fournisseur",
    pluralLabel: "fournisseurs",
    displayLabel: "Fournisseur",
    displayPluralLabel: "Fournisseurs",
    defaultCodePrefix: "FOU",
    defaultCollectiveCodes: ["401000", "401"],
    balanceMode: "credit",
    defaultStatementTitle: "Balance âgée fournisseurs",
    defaultScheduleTitle: "Échéancier fournisseurs",
  },
};

const cleanString = (value) => String(value ?? "").trim();
const resolveStringField = (payload, keys, fallback = "") => {
  for (const key of keys) {
    if (Object.prototype.hasOwnProperty.call(payload, key)) {
      return cleanString(payload[key]);
    }
  }

  return cleanString(fallback);
};

const parseBoolean = (value, fallback = null) => {
  if (typeof value === "boolean") {
    return value;
  }

  if (value === null || value === undefined || value === "") {
    return fallback;
  }

  const normalized = cleanString(value).toLowerCase();

  if (["true", "1", "yes", "oui", "active", "actif"].includes(normalized)) {
    return true;
  }

  if (["false", "0", "no", "non", "inactive", "inactif"].includes(normalized)) {
    return false;
  }

  return fallback;
};

const parseDateOnly = (value, fieldLabel = "Date") => {
  const normalized = cleanString(value);

  if (!normalized || !/^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
    throw new ApiError(400, `${fieldLabel} invalide.`);
  }

  const date = new Date(`${normalized}T00:00:00Z`);
  const [year, month, day] = normalized.split("-").map(Number);

  if (
    Number.isNaN(date.getTime()) ||
    date.getUTCFullYear() !== year ||
    date.getUTCMonth() + 1 !== month ||
    date.getUTCDate() !== day
  ) {
    throw new ApiError(400, `${fieldLabel} invalide.`);
  }

  return normalized;
};

const parseOptionalDateOnly = (value, fieldLabel = "Date") => {
  const normalized = cleanString(value);

  if (!normalized) {
    return null;
  }

  return parseDateOnly(normalized, fieldLabel);
};

const parseAmount = (value, fieldLabel = "Montant") => {
  if (value === null || value === undefined || value === "") {
    return 0;
  }

  if (typeof value === "number") {
    if (!Number.isFinite(value) || value < 0) {
      throw new ApiError(400, `${fieldLabel} invalide.`);
    }

    return Number(value.toFixed(2));
  }

  const normalized = cleanString(value).replace(/\s+/g, "").replace(",", ".");
  const parsed = Number.parseFloat(normalized);

  if (!Number.isFinite(parsed) || parsed < 0) {
    throw new ApiError(400, `${fieldLabel} invalide.`);
  }

  return Number(parsed.toFixed(2));
};

const parseInteger = (value, fieldLabel = "Valeur", fallback = 0) => {
  if (value === null || value === undefined || value === "") {
    return fallback;
  }

  const parsed = Number.parseInt(value, 10);

  if (!Number.isFinite(parsed) || parsed < 0) {
    throw new ApiError(400, `${fieldLabel} invalide.`);
  }

  return parsed;
};

const getCompanyId = (req, source = req.query) => {
  const companyId = Number(source.company_id || req.user?.companyId || 0);

  if (!companyId) {
    throw new ApiError(400, "company_id is required.");
  }

  return companyId;
};

const getBalanceExpression = (type, lineAlias = "el") =>
  type === "client"
    ? `${lineAlias}.debit - ${lineAlias}.credit`
    : `${lineAlias}.credit - ${lineAlias}.debit`;

const getBalanceTone = (balance) => {
  if (balance > 0) {
    return "positive";
  }

  if (balance < 0) {
    return "negative";
  }

  return "neutral";
};

const getRequestConfig = (type) => {
  const config = THIRD_PARTY_CONFIG[type];

  if (!config) {
    throw new Error(`Unsupported third party type: ${type}`);
  }

  return config;
};

const sanitizeThirdPartyForAudit = (item) => ({
  id: item.id,
  ref_code: item.ref_code,
  name: item.name,
  third_party_type: item.third_party_type,
  account_id: item.account_id,
  nif: item.nif,
  rc: item.rc,
  nis: item.nis,
  ai: item.ai,
  industry_sector: item.industry_sector,
  registration_date: item.registration_date,
  city: item.city,
  wilaya: item.wilaya,
  phone: item.phone,
  email: item.email,
  credit_limit: item.credit_limit,
  payment_terms: item.payment_terms,
  preferred_payment_method: item.preferred_payment_method,
  is_active: item.is_active,
});

const fetchAccountById = async (db, companyId, accountId) => {
  const { rows } = await db.query(
    `
      SELECT id, code, label, is_active
      FROM accounts
      WHERE id = $1
        AND company_id = $2
      LIMIT 1
    `,
    [accountId, companyId],
  );

  return rows[0] || null;
};

const fetchDefaultCollectiveAccount = async (db, companyId, type) => {
  const config = getRequestConfig(type);
  const [preferredExactCode, fallbackPrefix] = config.defaultCollectiveCodes;
  const { rows } = await db.query(
    `
      SELECT id, code, label
      FROM accounts
      WHERE company_id = $1
        AND is_active = TRUE
        AND (
          code = $2
          OR code = $3
          OR code LIKE $3 || '%'
        )
      ORDER BY
        CASE
          WHEN code = $2 THEN 0
          WHEN code = $3 THEN 1
          ELSE 2
        END,
        LENGTH(code) ASC,
        code ASC
      LIMIT 1
    `,
    [companyId, preferredExactCode, fallbackPrefix],
  );

  return rows[0] || null;
};

const generateRefCode = async (db, companyId, type) => {
  const config = getRequestConfig(type);
  const regex = `^${config.defaultCodePrefix}[0-9]+$`;
  const { rows } = await db.query(
    `
      SELECT
        COALESCE(
          MAX(NULLIF(regexp_replace(ref_code, '\\D', '', 'g'), '')::int),
          0
        ) + 1 AS next_sequence
      FROM auxiliary_accounts
      WHERE company_id = $1
        AND third_party_type = $2
        AND ref_code ~ $3
    `,
    [companyId, type, regex],
  );

  const nextSequence = Number(rows[0]?.next_sequence || 1);

  return `${config.defaultCodePrefix}${String(nextSequence).padStart(5, "0")}`;
};

const normalizePaymentMethod = (value) => {
  const normalized = cleanString(value).toLowerCase();

  if (!normalized) {
    return null;
  }

  return PAYMENT_METHOD_OPTIONS.some((item) => item.value === normalized) ? normalized : null;
};

const normalizeImportKey = (value) =>
  cleanString(value)
    .normalize("NFD")
    .replace(/\p{Diacritic}/gu, "")
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "_")
    .replace(/^_+|_+$/g, "");

const parseDelimitedLine = (line, delimiter) => {
  const cells = [];
  let current = "";
  let inQuotes = false;

  for (let index = 0; index < line.length; index += 1) {
    const character = line[index];
    const nextCharacter = line[index + 1];

    if (character === '"') {
      if (inQuotes && nextCharacter === '"') {
        current += '"';
        index += 1;
      } else {
        inQuotes = !inQuotes;
      }

      continue;
    }

    if (character === delimiter && !inQuotes) {
      cells.push(current);
      current = "";
      continue;
    }

    current += character;
  }

  cells.push(current);

  return cells;
};

const readImportCsv = (file) => {
  const textContent = file.buffer.toString("utf8").replace(/^\uFEFF/, "");
  const lines = textContent
    .split(/\r?\n/)
    .map((line) => line.replace(/\r$/, ""))
    .filter((line) => line.trim().length > 0);

  if (!lines.length) {
    throw new ApiError(400, "Le fichier importé est vide.");
  }

  const headerLine = lines[0];
  const delimiter = headerLine.includes(";") ? ";" : ",";
  const headers = parseDelimitedLine(headerLine, delimiter).map(
    (column, index) => cleanString(column) || `column_${index + 1}`,
  );
  const rows = lines.slice(1).map((line) => {
    const values = parseDelimitedLine(line, delimiter);

    return headers.reduce((accumulator, header, index) => {
      accumulator[header] = values[index] ?? "";
      return accumulator;
    }, {});
  });

  return {
    headers,
    rows,
  };
};

const readImportSpreadsheet = (file) => {
  if (!file?.buffer?.length) {
    throw new ApiError(400, "Le fichier d'import est requis.");
  }

  const fileName = cleanString(file.originalname).toLowerCase();

  if (fileName.endsWith(".csv")) {
    return readImportCsv(file);
  }

  const workbook = XLSX.read(file.buffer, {
    type: "buffer",
    cellDates: true,
    raw: true,
  });
  const firstSheetName = workbook.SheetNames[0];

  if (!firstSheetName) {
    throw new ApiError(400, "Le fichier importé est vide.");
  }

  const sheet = workbook.Sheets[firstSheetName];
  const matrix = XLSX.utils.sheet_to_json(sheet, {
    header: 1,
    defval: "",
    blankrows: false,
    raw: false,
  });

  if (!matrix.length) {
    throw new ApiError(400, "Le fichier importé est vide.");
  }

  const headers = matrix[0].map((column, index) => cleanString(column) || `column_${index + 1}`);
  const rows = matrix.slice(1).map((rowValues) =>
    headers.reduce((accumulator, header, index) => {
      accumulator[header] = rowValues[index] ?? "";
      return accumulator;
    }, {}),
  );

  return {
    headers,
    rows,
  };
};

const getImportFieldValue = (row, fieldName) => {
  const aliases = THIRD_PARTY_IMPORT_ALIASES[fieldName] || [];

  for (const [columnName, value] of Object.entries(row)) {
    if (aliases.includes(normalizeImportKey(columnName))) {
      return value;
    }
  }

  return "";
};

const findAccountIdByCode = async (db, companyId, rawCode) => {
  const code = cleanString(rawCode);

  if (!code) {
    return null;
  }

  const { rows } = await db.query(
    `
      SELECT id
      FROM accounts
      WHERE company_id = $1
        AND code = $2
      LIMIT 1
    `,
    [companyId, code],
  );

  return rows[0] ? Number(rows[0].id) : null;
};

const fetchThirdPartyByRefCode = async (db, companyId, type, refCode) => {
  const code = cleanString(refCode).toUpperCase();

  if (!code) {
    return null;
  }

  const { rows } = await db.query(
    `
      SELECT id
      FROM auxiliary_accounts
      WHERE company_id = $1
        AND third_party_type = $2
        AND ref_code = $3
      LIMIT 1
    `,
    [companyId, type, code],
  );

  if (!rows[0]) {
    return null;
  }

  return fetchThirdPartyById(db, companyId, type, rows[0].id);
};

const buildThirdPartyTemplateBuffer = (type) => {
  const config = getRequestConfig(type);
  const sampleRow = {
    Code: type === "client" ? "CL00001" : "FOU00001",
    "Raison sociale":
      type === "client" ? "Client Exemple SPA" : "Fournisseur Exemple SARL",
    NIF: "000000000000000",
    RC: "16B0000000",
    NIS: "000000000000000",
    "AI (Numéro article)": "AI-001",
    "Secteur d'activité": "Industrie",
    "Date d'inscription": "2026-01-01",
    Adresse: "Zone industrielle",
    Ville: "Alger",
    Wilaya: "Alger",
    "Code postal": "16000",
    Téléphone: "+213 21 00 00 00",
    Email: type === "client" ? "client@example.dz" : "supplier@example.dz",
    Fax: "",
    "Contact principal": "Mme Exemple",
    "Compte collectif": type === "client" ? "411000" : "401000",
    "Délai de paiement": 30,
    "Plafond de crédit": 0,
    "Mode de règlement": "transfer",
    Actif: "oui",
  };
  const worksheet = XLSX.utils.json_to_sheet([sampleRow], {
    header: THIRD_PARTY_TEMPLATE_HEADERS,
  });
  const workbook = XLSX.utils.book_new();

  XLSX.utils.book_append_sheet(workbook, worksheet, config.displayPluralLabel);

  return XLSX.write(workbook, {
    type: "buffer",
    bookType: "xlsx",
  });
};

const getThirdPartyImportMode = (value) => {
  const normalized = cleanString(value).toLowerCase();

  if (["preview", "commit"].includes(normalized)) {
    return normalized;
  }

  return "preview";
};

const normalizeImportedDate = (value, fieldLabel = "Date") => {
  if (value === null || value === undefined || value === "") {
    return null;
  }

  if (value instanceof Date) {
    if (Number.isNaN(value.getTime())) {
      throw new ApiError(400, `${fieldLabel} invalide.`);
    }

    return value.toISOString().slice(0, 10);
  }

  if (typeof value === "number" && Number.isFinite(value)) {
    const excelEpoch = new Date(Date.UTC(1899, 11, 30));
    const date = new Date(excelEpoch.getTime() + value * 24 * 60 * 60 * 1000);

    if (Number.isNaN(date.getTime())) {
      throw new ApiError(400, `${fieldLabel} invalide.`);
    }

    return date.toISOString().slice(0, 10);
  }

  const normalized = cleanString(value);

  if (!normalized) {
    return null;
  }

  if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
    return normalized;
  }

  const localizedMatch = normalized.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{2}|\d{4})$/);

  if (localizedMatch) {
    const first = Number(localizedMatch[1]);
    const second = Number(localizedMatch[2]);
    const rawYear = Number(localizedMatch[3]);
    const year = rawYear < 100 ? 2000 + rawYear : rawYear;
    let day = first;
    let month = second;

    if (first <= 12 && second > 12) {
      month = first;
      day = second;
    }

    return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
  }

  return normalized;
};

const fetchIndustrySectorOptions = async (db, companyId, type) => {
  const { rows } = await db.query(
    `
      SELECT DISTINCT industry_sector
      FROM auxiliary_accounts
      WHERE company_id = $1
        AND third_party_type = $2
        AND COALESCE(BTRIM(industry_sector), '') <> ''
      ORDER BY industry_sector ASC
    `,
    [companyId, type],
  );

  return rows.map((row) => row.industry_sector).filter(Boolean);
};

const buildStatsCte = (type) => `
  WITH stats AS (
    SELECT
      aa.id AS auxiliary_id,
      COALESCE(
        SUM(
          CASE
            WHEN je.status = 'validated'
              THEN ${getBalanceExpression(type)}
            ELSE 0
          END
        ),
        0
      )::numeric AS current_balance,
      COALESCE(
        SUM(
          CASE
            WHEN je.status = 'validated'
              AND COALESCE(el.is_lettered, FALSE) = FALSE
              THEN GREATEST(${getBalanceExpression(type)}, 0)
            ELSE 0
          END
        ),
        0
      )::numeric AS open_balance,
      COUNT(DISTINCT je.id) FILTER (WHERE je.status = 'validated')::int AS entries_count,
      MAX(je.entry_date) FILTER (WHERE je.status = 'validated') AS last_entry_date
    FROM auxiliary_accounts aa
    LEFT JOIN entry_lines el
      ON el.auxiliary_account_id = aa.id
    LEFT JOIN journal_entries je
      ON je.id = el.entry_id
      AND je.company_id = aa.company_id
    WHERE aa.company_id = $1
      AND aa.third_party_type = $2
    GROUP BY aa.id
  )
`;

const baseThirdPartySelect = `
  SELECT
    aa.id,
    aa.company_id,
    aa.account_id,
    aa.third_party_type,
    aa.ref_code,
    aa.name,
    aa.address,
    aa.nif,
    aa.rc,
    aa.nis,
    aa.ai,
    aa.industry_sector,
    TO_CHAR(aa.registration_date, 'YYYY-MM-DD') AS registration_date,
    aa.city,
    aa.wilaya,
    aa.postal_code,
    aa.phone,
    aa.email,
    aa.fax,
    aa.principal_contact,
    aa.credit_limit,
    aa.payment_terms,
    aa.preferred_payment_method,
    aa.is_active,
    aa.created_at,
    a.code AS collective_account_code,
    a.label AS collective_account_label,
    COALESCE(stats.current_balance, 0)::numeric AS current_balance,
    COALESCE(stats.open_balance, 0)::numeric AS open_balance,
    COALESCE(stats.entries_count, 0)::int AS entries_count,
    TO_CHAR(stats.last_entry_date, 'YYYY-MM-DD') AS last_entry_date
  FROM auxiliary_accounts aa
  JOIN accounts a
    ON a.id = aa.account_id
  LEFT JOIN stats
    ON stats.auxiliary_id = aa.id
`;

const mapThirdPartyRow = (row) => {
  const currentBalance = toNumber(row.current_balance);
  const openBalance = toNumber(row.open_balance);

  return {
    id: Number(row.id),
    company_id: Number(row.company_id),
    account_id: Number(row.account_id),
    third_party_type: row.third_party_type,
    ref_code: row.ref_code,
    name: row.name,
    address: row.address,
    nif: row.nif,
    rc: row.rc,
    nis: row.nis,
    ai: row.ai,
    industry_sector: row.industry_sector,
    registration_date: row.registration_date || null,
    city: row.city,
    wilaya: row.wilaya,
    postal_code: row.postal_code,
    phone: row.phone,
    email: row.email,
    fax: row.fax,
    principal_contact: row.principal_contact,
    credit_limit: toNumber(row.credit_limit),
    formatted_credit_limit: formatDzdAmount(row.credit_limit),
    payment_terms: Number(row.payment_terms || 0),
    preferred_payment_method: row.preferred_payment_method,
    preferred_payment_method_label:
      PAYMENT_METHOD_OPTIONS.find((option) => option.value === row.preferred_payment_method)?.label ||
      "—",
    is_active: row.is_active,
    status_label: row.is_active ? "Actif" : "Inactif",
    collective_account_code: row.collective_account_code,
    collective_account_label: row.collective_account_label,
    current_balance: currentBalance,
    formatted_current_balance: formatDzdAmount(currentBalance),
    current_balance_tone: getBalanceTone(currentBalance),
    open_balance: openBalance,
    formatted_open_balance: formatDzdAmount(openBalance),
    open_balance_tone: getBalanceTone(openBalance),
    entries_count: Number(row.entries_count || 0),
    last_entry_date: row.last_entry_date || null,
    created_at: row.created_at?.toISOString?.() || row.created_at,
  };
};

const listThirdParties = async (db, companyId, type, query = {}) => {
  const values = [companyId, type];
  const whereClauses = [
    "aa.company_id = $1",
    "aa.third_party_type = $2",
  ];
  const search = cleanString(query.search);
  const active = cleanString(query.active).toLowerCase();
  const hasBalance = cleanString(query.has_balance).toLowerCase();

  if (search) {
    values.push(`%${search}%`);
    whereClauses.push(`(aa.ref_code ILIKE $${values.length} OR aa.name ILIKE $${values.length})`);
  }

  if (["true", "active", "1"].includes(active)) {
    whereClauses.push("aa.is_active = TRUE");
  } else if (["false", "inactive", "0"].includes(active)) {
    whereClauses.push("aa.is_active = FALSE");
  }

  if (["true", "with_balance", "1"].includes(hasBalance)) {
    whereClauses.push("ABS(COALESCE(stats.current_balance, 0)) > 0.005");
  } else if (["false", "zero", "zero_balance", "0"].includes(hasBalance)) {
    whereClauses.push("ABS(COALESCE(stats.current_balance, 0)) <= 0.005");
  }

  const { rows } = await db.query(
    `
      ${buildStatsCte(type)}
      ${baseThirdPartySelect}
      WHERE ${whereClauses.join("\nAND ")}
      ORDER BY aa.ref_code ASC, aa.name ASC
    `,
    values,
  );

  return rows.map(mapThirdPartyRow);
};

const fetchThirdPartyById = async (db, companyId, type, thirdPartyId) => {
  const values = [companyId, type, thirdPartyId];
  const { rows } = await db.query(
    `
      ${buildStatsCte(type)}
      ${baseThirdPartySelect}
      WHERE aa.company_id = $1
        AND aa.third_party_type = $2
        AND aa.id = $3
      LIMIT 1
    `,
    values,
  );

  return rows[0] ? mapThirdPartyRow(rows[0]) : null;
};

const fetchThirdPartyHistory = async (db, thirdPartyId) => {
  const { rows } = await db.query(
    `
      SELECT
        al.id,
        al.action,
        al.created_at,
        COALESCE(u.full_name, u.email, 'Système') AS actor_name
      FROM audit_log al
      LEFT JOIN users u
        ON u.id = al.user_id
      WHERE al.entity_type = 'auxiliary_account'
        AND al.entity_id = $1
      ORDER BY al.created_at DESC, al.id DESC
      LIMIT 20
    `,
    [thirdPartyId],
  );

  return rows.map((row) => ({
    id: Number(row.id),
    action: row.action,
    label: HISTORY_ACTION_LABELS[row.action] || row.action,
    actor_name: row.actor_name,
    occurred_at: row.created_at?.toISOString?.() || row.created_at,
  }));
};

const normalizeThirdPartyPayload = async ({ db, companyId, type, body, existingItem = null }) => {
  const config = getRequestConfig(type);
  const refCode = cleanString(body.ref_code || body.code || existingItem?.ref_code).toUpperCase();
  const name = cleanString(body.name || body.company_name || body.raison_sociale || existingItem?.name);
  const accountIdRaw =
    body.account_id ??
    body.collective_account_id ??
    existingItem?.account_id ??
    null;
  let accountId = Number(accountIdRaw || 0) || null;

  if (!accountId) {
    const defaultAccount = await fetchDefaultCollectiveAccount(db, companyId, type);

    if (!defaultAccount) {
      throw new ApiError(
        400,
        `Aucun compte collectif par défaut n'est disponible pour les ${config.pluralLabel}.`,
      );
    }

    accountId = Number(defaultAccount.id);
  }

  if (!name) {
    throw new ApiError(400, `La raison sociale du ${config.singularLabel} est obligatoire.`);
  }

  const account = await fetchAccountById(db, companyId, accountId);

  if (!account) {
    throw new ApiError(400, "Le compte collectif sélectionné est introuvable.");
  }

  if (!account.is_active) {
    throw new ApiError(400, "Le compte collectif sélectionné est inactif.");
  }

  const paymentMethod = normalizePaymentMethod(
    body.preferred_payment_method || body.payment_method || existingItem?.preferred_payment_method,
  );

  if (
    cleanString(body.preferred_payment_method || body.payment_method) &&
    !paymentMethod
  ) {
    throw new ApiError(400, "Le mode de règlement préféré est invalide.");
  }

  return {
    ref_code: refCode || (await generateRefCode(db, companyId, type)),
    name,
    account_id: accountId,
    nif: cleanString(body.nif || existingItem?.nif) || null,
    rc: cleanString(body.rc || existingItem?.rc) || null,
    nis: cleanString(body.nis || existingItem?.nis) || null,
    ai: resolveStringField(body, ["ai"], existingItem?.ai) || null,
    industry_sector:
      resolveStringField(body, ["industry_sector", "industrySector"], existingItem?.industry_sector) ||
      null,
    registration_date:
      body.registration_date === undefined && body.registrationDate === undefined
        ? existingItem?.registration_date || null
        : parseOptionalDateOnly(
            body.registration_date ?? body.registrationDate,
            "Date d'inscription",
          ),
    address: cleanString(body.address || existingItem?.address) || null,
    city: cleanString(body.city || existingItem?.city) || null,
    wilaya: cleanString(body.wilaya || existingItem?.wilaya) || null,
    postal_code: cleanString(body.postal_code || body.postalCode || existingItem?.postal_code) || null,
    phone: cleanString(body.phone || existingItem?.phone) || null,
    email: cleanString(body.email || existingItem?.email) || null,
    fax: cleanString(body.fax || existingItem?.fax) || null,
    principal_contact:
      cleanString(body.principal_contact || body.contact || existingItem?.principal_contact) || null,
    credit_limit: parseAmount(
      body.credit_limit ?? body.creditLimit ?? existingItem?.credit_limit ?? 0,
      "Plafond de crédit",
    ),
    payment_terms: parseInteger(
      body.payment_terms ?? body.paymentTerms ?? existingItem?.payment_terms ?? 30,
      "Délai de paiement",
      30,
    ),
    preferred_payment_method: paymentMethod,
    is_active:
      parseBoolean(body.is_active, existingItem?.is_active ?? true) ?? existingItem?.is_active ?? true,
  };
};

const saveThirdParty = async ({
  db,
  companyId,
  type,
  body,
  existingItem = null,
  userId = null,
  ipAddress = null,
  actionPrefix,
}) => {
  const config = getRequestConfig(type);
  const payload = await normalizeThirdPartyPayload({
    db,
    companyId,
    type,
    body,
    existingItem,
  });

  let savedId = existingItem?.id || null;

  if (!existingItem) {
    const { rows } = await db.query(
      `
        INSERT INTO auxiliary_accounts (
          company_id,
          account_id,
          third_party_type,
          ref_code,
          name,
          address,
          nif,
          rc,
          nis,
          ai,
          industry_sector,
          registration_date,
          city,
          wilaya,
          postal_code,
          phone,
          email,
          fax,
          principal_contact,
          credit_limit,
          payment_terms,
          preferred_payment_method,
          is_active
        )
        VALUES (
          $1, $2, $3, $4, $5, $6, $7, $8, $9, $10,
          $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23
        )
        RETURNING id
      `,
      [
        companyId,
        payload.account_id,
        type,
        payload.ref_code,
        payload.name,
        payload.address,
        payload.nif,
        payload.rc,
        payload.nis,
        payload.ai,
        payload.industry_sector,
        payload.registration_date,
        payload.city,
        payload.wilaya,
        payload.postal_code,
        payload.phone,
        payload.email,
        payload.fax,
        payload.principal_contact,
        payload.credit_limit,
        payload.payment_terms,
        payload.preferred_payment_method,
        payload.is_active,
      ],
    );

    savedId = Number(rows[0].id);
  } else {
    await db.query(
      `
        UPDATE auxiliary_accounts
        SET
          account_id = $2,
          ref_code = $3,
          name = $4,
          address = $5,
          nif = $6,
          rc = $7,
          nis = $8,
          ai = $9,
          industry_sector = $10,
          registration_date = $11,
          city = $12,
          wilaya = $13,
          postal_code = $14,
          phone = $15,
          email = $16,
          fax = $17,
          principal_contact = $18,
          credit_limit = $19,
          payment_terms = $20,
          preferred_payment_method = $21,
          is_active = $22
        WHERE id = $1
          AND company_id = $23
          AND third_party_type = $24
      `,
      [
        existingItem.id,
        payload.account_id,
        payload.ref_code,
        payload.name,
        payload.address,
        payload.nif,
        payload.rc,
        payload.nis,
        payload.ai,
        payload.industry_sector,
        payload.registration_date,
        payload.city,
        payload.wilaya,
        payload.postal_code,
        payload.phone,
        payload.email,
        payload.fax,
        payload.principal_contact,
        payload.credit_limit,
        payload.payment_terms,
        payload.preferred_payment_method,
        payload.is_active,
        companyId,
        type,
      ],
    );
  }

  const item = await fetchThirdPartyById(db, companyId, type, savedId);

  await logAudit(db, {
    companyId,
    userId,
    action: `${actionPrefix}.${existingItem ? "update" : "create"}`,
    entityType: "auxiliary_account",
    entityId: item.id,
    oldData: existingItem ? sanitizeThirdPartyForAudit(existingItem) : null,
    newData: sanitizeThirdPartyForAudit(item),
    ipAddress,
  });

  return item;
};

const buildThirdPartyImportPayload = async ({ db, companyId, type, row, existingItem = null }) => {
  const accountId = await findAccountIdByCode(db, companyId, getImportFieldValue(row, "account_code"));
  const registrationDate = normalizeImportedDate(
    getImportFieldValue(row, "registration_date"),
    "Date d'inscription",
  );

  return normalizeThirdPartyPayload({
    db,
    companyId,
    type,
    body: {
      ref_code: getImportFieldValue(row, "ref_code"),
      name: getImportFieldValue(row, "name"),
      nif: getImportFieldValue(row, "nif"),
      rc: getImportFieldValue(row, "rc"),
      nis: getImportFieldValue(row, "nis"),
      ai: getImportFieldValue(row, "ai"),
      industry_sector: getImportFieldValue(row, "industry_sector"),
      registration_date: registrationDate,
      address: getImportFieldValue(row, "address"),
      city: getImportFieldValue(row, "city"),
      wilaya: getImportFieldValue(row, "wilaya"),
      postal_code: getImportFieldValue(row, "postal_code"),
      phone: getImportFieldValue(row, "phone"),
      email: getImportFieldValue(row, "email"),
      fax: getImportFieldValue(row, "fax"),
      principal_contact: getImportFieldValue(row, "principal_contact"),
      account_id: accountId,
      payment_terms: getImportFieldValue(row, "payment_terms"),
      credit_limit: getImportFieldValue(row, "credit_limit"),
      preferred_payment_method: getImportFieldValue(row, "preferred_payment_method"),
      is_active: getImportFieldValue(row, "is_active"),
    },
    existingItem,
  });
};

const buildThirdPartyImportPreview = async ({ db, companyId, type, rows }) => {
  const previewRows = [];
  const seenCodes = new Set();

  for (const [index, row] of rows.entries()) {
    const rowNumber = index + 2;
    const rawCode = cleanString(getImportFieldValue(row, "ref_code")).toUpperCase();
    const rawName = cleanString(getImportFieldValue(row, "name"));
    const errors = [];
    const warnings = [];

    if (!rawName) {
      errors.push("La raison sociale est obligatoire.");
    }

    if (rawCode) {
      if (seenCodes.has(rawCode)) {
        errors.push("Code tiers en doublon dans le fichier.");
      } else {
        seenCodes.add(rawCode);
      }
    }

    let existingItem = null;
    let payload = null;

    try {
      existingItem = rawCode
        ? await fetchThirdPartyByRefCode(db, companyId, type, rawCode)
        : null;
      payload = await buildThirdPartyImportPayload({
        db,
        companyId,
        type,
        row,
        existingItem,
      });
    } catch (error) {
      errors.push(error.message || "Ligne d'import invalide.");
    }

    previewRows.push({
      row_number: rowNumber,
      original: row,
      normalized: payload
        ? {
            ref_code: payload.ref_code,
            name: payload.name,
            account_id: payload.account_id,
            account_code: cleanString(getImportFieldValue(row, "account_code")) || null,
            payment_terms: payload.payment_terms,
            credit_limit: payload.credit_limit,
            preferred_payment_method: payload.preferred_payment_method,
            is_active: payload.is_active,
            action: existingItem ? "update" : "create",
          }
        : {
            ref_code: rawCode || null,
            name: rawName || null,
            account_code: cleanString(getImportFieldValue(row, "account_code")) || null,
            action: existingItem ? "update" : "create",
          },
      warnings,
      errors,
    });
  }

  return {
    summary: {
      total_rows: rows.length,
      valid_rows: previewRows.filter((row) => row.errors.length === 0).length,
      error_rows: previewRows.filter((row) => row.errors.length > 0).length,
      warning_rows: previewRows.filter((row) => row.warnings.length > 0).length,
    },
    rows: previewRows,
  };
};

const commitThirdPartyImport = async ({
  db,
  companyId,
  type,
  rows,
  userId,
  ipAddress,
}) => {
  const config = getRequestConfig(type);
  let createdCount = 0;
  let updatedCount = 0;
  const logs = {
    successes: [],
    errors: [],
  };

  for (const row of rows) {
    if (row.errors.length > 0) {
      logs.errors.push({
        row_number: row.row_number,
        ref_code: row.normalized.ref_code,
        messages: row.errors,
      });
      continue;
    }

    const existingItem = row.normalized.ref_code
      ? await fetchThirdPartyByRefCode(db, companyId, type, row.normalized.ref_code)
      : null;

    const item = await saveThirdParty({
      db,
      companyId,
      type,
      body: {
        ref_code: getImportFieldValue(row.original, "ref_code"),
        name: getImportFieldValue(row.original, "name"),
        nif: getImportFieldValue(row.original, "nif"),
        rc: getImportFieldValue(row.original, "rc"),
        nis: getImportFieldValue(row.original, "nis"),
        ai: getImportFieldValue(row.original, "ai"),
        industry_sector: getImportFieldValue(row.original, "industry_sector"),
        registration_date: normalizeImportedDate(
          getImportFieldValue(row.original, "registration_date"),
          "Date d'inscription",
        ),
        address: getImportFieldValue(row.original, "address"),
        city: getImportFieldValue(row.original, "city"),
        wilaya: getImportFieldValue(row.original, "wilaya"),
        postal_code: getImportFieldValue(row.original, "postal_code"),
        phone: getImportFieldValue(row.original, "phone"),
        email: getImportFieldValue(row.original, "email"),
        fax: getImportFieldValue(row.original, "fax"),
        principal_contact: getImportFieldValue(row.original, "principal_contact"),
        account_id: await findAccountIdByCode(
          db,
          companyId,
          getImportFieldValue(row.original, "account_code"),
        ),
        payment_terms: getImportFieldValue(row.original, "payment_terms"),
        credit_limit: getImportFieldValue(row.original, "credit_limit"),
        preferred_payment_method: getImportFieldValue(
          row.original,
          "preferred_payment_method",
        ),
        is_active: getImportFieldValue(row.original, "is_active"),
      },
      existingItem,
      userId,
      ipAddress,
      actionPrefix: config.route,
    });

    if (existingItem) {
      updatedCount += 1;
    } else {
      createdCount += 1;
    }

    logs.successes.push({
      row_number: row.row_number,
      ref_code: item.ref_code,
      action: existingItem ? "updated" : "created",
    });
  }

  await logAudit(db, {
    companyId,
    userId,
    action: `${config.route}.import`,
    entityType: "auxiliary_account_import",
    newData: {
      created_count: createdCount,
      updated_count: updatedCount,
      error_count: logs.errors.length,
      total_rows: rows.length,
    },
    ipAddress,
  });

  return {
    created_count: createdCount,
    updated_count: updatedCount,
    error_count: logs.errors.length,
    warning_count: 0,
    logs,
  };
};

const createThirdPartyController = (type) => {
  const config = getRequestConfig(type);

  return {
    meta: async (req, res) => {
      const companyId = getCompanyId(req);
      const sectorOptions = await fetchIndustrySectorOptions(pool, companyId, type);

      res.json({
        company_id: companyId,
        type,
        meta: {
          payment_method_options: PAYMENT_METHOD_OPTIONS,
          sector_options: sectorOptions,
        },
      });
    },

    downloadTemplate: async (req, res) => {
      const workbookBuffer = buildThirdPartyTemplateBuffer(type);

      res.setHeader(
        "Content-Type",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      );
      res.setHeader(
        "Content-Disposition",
        `attachment; filename="${config.route}-import-template.xlsx"`,
      );

      res.send(workbookBuffer);
    },

    importItems: async (req, res) => {
      const companyId = getCompanyId(req, req.body);
      const mode = getThirdPartyImportMode(req.body.mode);
      const { headers, rows } = readImportSpreadsheet(req.file);
      const client = await pool.connect();

      try {
        const preview = await buildThirdPartyImportPreview({
          db: client,
          companyId,
          type,
          rows,
        });

        if (mode === "preview") {
          return res.json({
            mode,
            headers,
            summary: preview.summary,
            preview_rows: preview.rows.slice(0, MAX_IMPORT_PREVIEW_ROWS),
          });
        }

        await client.query("BEGIN");

        const importResult = await commitThirdPartyImport({
          db: client,
          companyId,
          type,
          rows: preview.rows,
          userId: req.user?.sub || null,
          ipAddress: req.ip,
        });

        await client.query("COMMIT");

        return res.json({
          mode,
          message: `Import ${config.displayPluralLabel.toLowerCase()} terminé.`,
          summary: preview.summary,
          ...importResult,
        });
      } catch (error) {
        await client.query("ROLLBACK").catch(() => {});
        throw error;
      } finally {
        client.release();
      }
    },

    list: async (req, res) => {
      const companyId = getCompanyId(req);
      const [items, defaultCollectiveAccount, sectorOptions] = await Promise.all([
        listThirdParties(pool, companyId, type, req.query),
        fetchDefaultCollectiveAccount(pool, companyId, type),
        fetchIndustrySectorOptions(pool, companyId, type),
      ]);

      res.json({
        company_id: companyId,
        type,
        items,
        meta: {
          default_collective_account: defaultCollectiveAccount
            ? {
                id: Number(defaultCollectiveAccount.id),
                code: defaultCollectiveAccount.code,
                label: defaultCollectiveAccount.label,
              }
            : null,
          payment_method_options: PAYMENT_METHOD_OPTIONS,
          sector_options: sectorOptions,
        },
      });
    },

    getById: async (req, res) => {
      const companyId = getCompanyId(req);
      const thirdPartyId = Number(req.params.id);

      if (!thirdPartyId) {
        throw new ApiError(400, `Identifiant ${config.singularLabel} invalide.`);
      }

      const item = await fetchThirdPartyById(pool, companyId, type, thirdPartyId);

      if (!item) {
        throw new ApiError(404, `${config.displayLabel} introuvable.`);
      }

      const history = await fetchThirdPartyHistory(pool, thirdPartyId);
      const sectorOptions = await fetchIndustrySectorOptions(pool, companyId, type);

      res.json({
        item: {
          ...item,
          history,
        },
        meta: {
          payment_method_options: PAYMENT_METHOD_OPTIONS,
          sector_options: sectorOptions,
        },
      });
    },

    create: async (req, res) => {
      const companyId = getCompanyId(req, req.body);
      const client = await pool.connect();

      try {
        await client.query("BEGIN");
        const item = await saveThirdParty({
          db: client,
          companyId,
          type,
          body: req.body,
          userId: req.user?.sub || null,
          ipAddress: req.ip,
          actionPrefix: config.route,
        });

        await client.query("COMMIT");

        res.status(201).json({
          message: `${config.displayLabel} créé avec succès.`,
          item,
        });
      } catch (error) {
        await client.query("ROLLBACK");

        if (error?.code === "23505") {
          throw new ApiError(409, `Ce code ${config.singularLabel} existe déjà.`);
        }

        throw error;
      } finally {
        client.release();
      }
    },

    update: async (req, res) => {
      const companyId = getCompanyId(req, req.body);
      const thirdPartyId = Number(req.params.id);

      if (!thirdPartyId) {
        throw new ApiError(400, `Identifiant ${config.singularLabel} invalide.`);
      }

      const client = await pool.connect();

      try {
        await client.query("BEGIN");

        const existingItem = await fetchThirdPartyById(client, companyId, type, thirdPartyId);

        if (!existingItem) {
          throw new ApiError(404, `${config.displayLabel} introuvable.`);
        }
        const item = await saveThirdParty({
          db: client,
          companyId,
          type,
          body: req.body,
          existingItem,
          userId: req.user?.sub || null,
          ipAddress: req.ip,
          actionPrefix: config.route,
        });

        await client.query("COMMIT");

        res.json({
          message: `${config.displayLabel} mis à jour.`,
          item,
        });
      } catch (error) {
        await client.query("ROLLBACK");

        if (error?.code === "23505") {
          throw new ApiError(409, `Ce code ${config.singularLabel} existe déjà.`);
        }

        throw error;
      } finally {
        client.release();
      }
    },

    remove: async (req, res) => {
      const companyId = getCompanyId(req);
      const thirdPartyId = Number(req.params.id);

      if (!thirdPartyId) {
        throw new ApiError(400, `Identifiant ${config.singularLabel} invalide.`);
      }

      const client = await pool.connect();

      try {
        await client.query("BEGIN");

        const existingItem = await fetchThirdPartyById(client, companyId, type, thirdPartyId);

        if (!existingItem) {
          throw new ApiError(404, `${config.displayLabel} introuvable.`);
        }

        await client.query(
          `
            UPDATE auxiliary_accounts
            SET is_active = FALSE
            WHERE id = $1
              AND company_id = $2
              AND third_party_type = $3
          `,
          [thirdPartyId, companyId, type],
        );

        const item = await fetchThirdPartyById(client, companyId, type, thirdPartyId);

        await logAudit(client, {
          companyId,
          userId: req.user?.sub || null,
          action: `${config.route}.delete`,
          entityType: "auxiliary_account",
          entityId: thirdPartyId,
          oldData: sanitizeThirdPartyForAudit(existingItem),
          newData: sanitizeThirdPartyForAudit(item),
          ipAddress: req.ip,
        });

        await client.query("COMMIT");

        res.json({
          message: `${config.displayLabel} désactivé.`,
          item,
        });
      } catch (error) {
        await client.query("ROLLBACK");
        throw error;
      } finally {
        client.release();
      }
    },

    getStatement: async (req, res) => {
      const companyId = getCompanyId(req);
      const thirdPartyId = Number(req.params.id);

      if (!thirdPartyId) {
        throw new ApiError(400, `Identifiant ${config.singularLabel} invalide.`);
      }

      const item = await fetchThirdPartyById(pool, companyId, type, thirdPartyId);

      if (!item) {
        throw new ApiError(404, `${config.displayLabel} introuvable.`);
      }

      const fromDate = req.query.from ? parseDateOnly(req.query.from, "Date début") : null;
      const toDate = req.query.to ? parseDateOnly(req.query.to, "Date fin") : null;
      const values = [thirdPartyId];
      const whereClauses = [
        "el.auxiliary_account_id = $1",
        "je.status = 'validated'",
      ];

      if (fromDate) {
        values.push(fromDate);
        whereClauses.push(`je.entry_date >= $${values.length}::date`);
      }

      if (toDate) {
        values.push(toDate);
        whereClauses.push(`je.entry_date <= $${values.length}::date`);
      }

      const { rows } = await pool.query(
        `
          WITH scoped_lines AS (
            SELECT
              je.id AS entry_id,
              el.id AS line_id,
              TO_CHAR(je.entry_date, 'YYYY-MM-DD') AS entry_date,
              je.piece_ref,
              je.entry_number,
              j.code AS journal_code,
              COALESCE(NULLIF(el.label, ''), je.label) AS label,
              el.debit,
              el.credit,
              ${getBalanceExpression(type)} AS balance_delta
            FROM entry_lines el
            JOIN journal_entries je
              ON je.id = el.entry_id
            JOIN journals j
              ON j.id = je.journal_id
            WHERE ${whereClauses.join("\nAND ")}
          )
          SELECT
            scoped_lines.*,
            SUM(balance_delta) OVER (
              ORDER BY entry_date ASC, entry_id ASC, line_id ASC
            )::numeric AS running_balance
          FROM scoped_lines
          ORDER BY entry_date ASC, entry_id ASC, line_id ASC
        `,
        values,
      );

      const entries = rows.map((row) => ({
        entry_id: Number(row.entry_id),
        line_id: Number(row.line_id),
        entry_date: row.entry_date,
        piece_ref: row.piece_ref,
        entry_number: row.entry_number,
        journal_code: row.journal_code,
        label: row.label,
        debit: toNumber(row.debit),
        credit: toNumber(row.credit),
        running_balance: toNumber(row.running_balance),
        formatted_debit: formatDzdAmount(row.debit),
        formatted_credit: formatDzdAmount(row.credit),
        formatted_running_balance: formatDzdAmount(row.running_balance),
      }));
      const totals = entries.reduce(
        (accumulator, row) => ({
          totalDebit: Number((accumulator.totalDebit + row.debit).toFixed(2)),
          totalCredit: Number((accumulator.totalCredit + row.credit).toFixed(2)),
        }),
        {
          totalDebit: 0,
          totalCredit: 0,
        },
      );
      const finalBalance = entries.length
        ? entries[entries.length - 1].running_balance
        : 0;

      res.json({
        item,
        from: fromDate,
        to: toDate,
        items: entries,
        summary: {
          total_debit: totals.totalDebit,
          total_credit: totals.totalCredit,
          final_balance: finalBalance,
          formatted_total_debit: formatDzdAmount(totals.totalDebit),
          formatted_total_credit: formatDzdAmount(totals.totalCredit),
          formatted_final_balance: formatDzdAmount(finalBalance),
          balance_tone: getBalanceTone(finalBalance),
        },
      });
    },

    getAgedBalance: async (req, res) => {
      const companyId = getCompanyId(req);
      const asOfDate = req.query.as_of_date
        ? parseDateOnly(req.query.as_of_date, "Date d'ancienneté")
        : new Date().toISOString().slice(0, 10);

      const { rows } = await pool.query(
        `
          WITH open_lines AS (
            SELECT
              aa.id AS auxiliary_id,
              aa.ref_code,
              aa.name,
              COALESCE(el.due_date, je.entry_date + COALESCE(aa.payment_terms, 0)) AS effective_due_date,
              GREATEST(${getBalanceExpression(type)}, 0)::numeric AS outstanding_amount
            FROM auxiliary_accounts aa
            JOIN entry_lines el
              ON el.auxiliary_account_id = aa.id
            JOIN journal_entries je
              ON je.id = el.entry_id
            WHERE aa.company_id = $1
              AND aa.third_party_type = $2
              AND je.status = 'validated'
              AND COALESCE(el.is_lettered, FALSE) = FALSE
              AND GREATEST(${getBalanceExpression(type)}, 0) > 0
          )
          SELECT
            auxiliary_id,
            ref_code,
            name,
            COALESCE(SUM(outstanding_amount), 0)::numeric AS total_balance,
            COALESCE(SUM(CASE WHEN effective_due_date > $3::date THEN outstanding_amount ELSE 0 END), 0)::numeric AS not_due,
            COALESCE(SUM(CASE WHEN $3::date - effective_due_date BETWEEN 0 AND 30 THEN outstanding_amount ELSE 0 END), 0)::numeric AS bucket_0_30,
            COALESCE(SUM(CASE WHEN $3::date - effective_due_date BETWEEN 31 AND 60 THEN outstanding_amount ELSE 0 END), 0)::numeric AS bucket_31_60,
            COALESCE(SUM(CASE WHEN $3::date - effective_due_date BETWEEN 61 AND 90 THEN outstanding_amount ELSE 0 END), 0)::numeric AS bucket_61_90,
            COALESCE(SUM(CASE WHEN $3::date - effective_due_date > 90 THEN outstanding_amount ELSE 0 END), 0)::numeric AS bucket_90_plus
          FROM open_lines
          GROUP BY auxiliary_id, ref_code, name
          ORDER BY name ASC, ref_code ASC
        `,
        [companyId, type, asOfDate],
      );

      const items = rows.map((row) => ({
        auxiliary_id: Number(row.auxiliary_id),
        ref_code: row.ref_code,
        name: row.name,
        total_balance: toNumber(row.total_balance),
        not_due: toNumber(row.not_due),
        bucket_0_30: toNumber(row.bucket_0_30),
        bucket_31_60: toNumber(row.bucket_31_60),
        bucket_61_90: toNumber(row.bucket_61_90),
        bucket_90_plus: toNumber(row.bucket_90_plus),
        formatted_total_balance: formatDzdAmount(row.total_balance),
        formatted_not_due: formatDzdAmount(row.not_due),
        formatted_bucket_0_30: formatDzdAmount(row.bucket_0_30),
        formatted_bucket_31_60: formatDzdAmount(row.bucket_31_60),
        formatted_bucket_61_90: formatDzdAmount(row.bucket_61_90),
        formatted_bucket_90_plus: formatDzdAmount(row.bucket_90_plus),
      }));
      const totals = items.reduce(
        (accumulator, itemRow) => ({
          total_balance: accumulator.total_balance + itemRow.total_balance,
          not_due: accumulator.not_due + itemRow.not_due,
          bucket_0_30: accumulator.bucket_0_30 + itemRow.bucket_0_30,
          bucket_31_60: accumulator.bucket_31_60 + itemRow.bucket_31_60,
          bucket_61_90: accumulator.bucket_61_90 + itemRow.bucket_61_90,
          bucket_90_plus: accumulator.bucket_90_plus + itemRow.bucket_90_plus,
        }),
        {
          total_balance: 0,
          not_due: 0,
          bucket_0_30: 0,
          bucket_31_60: 0,
          bucket_61_90: 0,
          bucket_90_plus: 0,
        },
      );

      res.json({
        company_id: companyId,
        type,
        title: config.defaultStatementTitle,
        as_of_date: asOfDate,
        items,
        totals: {
          ...totals,
          formatted_total_balance: formatDzdAmount(totals.total_balance),
          formatted_not_due: formatDzdAmount(totals.not_due),
          formatted_bucket_0_30: formatDzdAmount(totals.bucket_0_30),
          formatted_bucket_31_60: formatDzdAmount(totals.bucket_31_60),
          formatted_bucket_61_90: formatDzdAmount(totals.bucket_61_90),
          formatted_bucket_90_plus: formatDzdAmount(totals.bucket_90_plus),
        },
      });
    },

    getSchedule: async (req, res) => {
      const companyId = getCompanyId(req);
      const fromDate = req.query.from ? parseDateOnly(req.query.from, "Date début") : null;
      const toDate = req.query.to ? parseDateOnly(req.query.to, "Date fin") : null;
      const values = [companyId, type];
      const whereClauses = [
        "aa.company_id = $1",
        "aa.third_party_type = $2",
        "je.status = 'validated'",
        `GREATEST(${getBalanceExpression(type)}, 0) > 0`,
      ];

      if (fromDate) {
        values.push(fromDate);
        whereClauses.push(
          `COALESCE(el.due_date, je.entry_date + COALESCE(aa.payment_terms, 0)) >= $${values.length}::date`,
        );
      }

      if (toDate) {
        values.push(toDate);
        whereClauses.push(
          `COALESCE(el.due_date, je.entry_date + COALESCE(aa.payment_terms, 0)) <= $${values.length}::date`,
        );
      }

      const { rows } = await pool.query(
        `
          SELECT
            el.id AS line_id,
            aa.id AS auxiliary_id,
            aa.ref_code,
            aa.name,
            TO_CHAR(COALESCE(el.due_date, je.entry_date + COALESCE(aa.payment_terms, 0)), 'YYYY-MM-DD') AS due_date,
            je.piece_ref,
            je.entry_number,
            COALESCE(NULLIF(el.label, ''), je.label) AS label,
            GREATEST(${getBalanceExpression(type)}, 0)::numeric AS amount,
            COALESCE(el.is_lettered, FALSE) AS is_paid,
            j.code AS journal_code,
            CASE
              WHEN COALESCE(el.is_lettered, FALSE) = TRUE THEN 'paid'
              WHEN COALESCE(el.due_date, je.entry_date + COALESCE(aa.payment_terms, 0)) < CURRENT_DATE THEN 'overdue'
              ELSE 'pending'
            END AS status
          FROM auxiliary_accounts aa
          JOIN entry_lines el
            ON el.auxiliary_account_id = aa.id
          JOIN journal_entries je
            ON je.id = el.entry_id
          JOIN journals j
            ON j.id = je.journal_id
          WHERE ${whereClauses.join("\nAND ")}
          ORDER BY due_date ASC, aa.name ASC, je.entry_date ASC, el.id ASC
        `,
        values,
      );

      const items = rows.map((row) => ({
        line_id: Number(row.line_id),
        auxiliary_id: Number(row.auxiliary_id),
        ref_code: row.ref_code,
        name: row.name,
        due_date: row.due_date,
        piece_ref: row.piece_ref,
        entry_number: row.entry_number,
        journal_code: row.journal_code,
        label: row.label,
        amount: toNumber(row.amount),
        formatted_amount: formatDzdAmount(row.amount),
        status: row.status,
        status_label:
          row.status === "paid"
            ? "Payé"
            : row.status === "overdue"
              ? "En retard"
              : "En attente",
      }));

      res.json({
        company_id: companyId,
        type,
        title: config.defaultScheduleTitle,
        from: fromDate,
        to: toDate,
        items,
      });
    },
  };
};

export const clientsController = createThirdPartyController("client");
export const suppliersController = createThirdPartyController("supplier");
