import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import { formatDzdAmount } from "../utils/formatters.js";
import { logAudit } from "../utils/auditLog.js";
import { launchPuppeteerBrowser } from "../utils/puppeteer.js";
import {
  BALANCE_SHEET_LINES,
  CASH_FLOW_LINES,
  EQUITY_CHANGE_ROWS,
  FINANCIAL_NOTES_SECTIONS,
  FINANCIAL_STATEMENT_LINE_OPTIONS,
  FINANCIAL_STATEMENT_TYPES,
  INCOME_STATEMENT_LINES,
} from "../../../shared/financialStatements.js";

const TODAY = new Date().toISOString().slice(0, 10);

const BALANCE_LINE_LOOKUP = new Map(
  [...BALANCE_SHEET_LINES.assets, ...BALANCE_SHEET_LINES.liabilities].map((line) => [
    line.code,
    line,
  ]),
);
const INCOME_LINE_LOOKUP = new Map(
  INCOME_STATEMENT_LINES.map((line) => [line.code, line]),
);
const CASH_FLOW_LINE_LOOKUP = new Map(CASH_FLOW_LINES.map((line) => [line.code, line]));
const NOTE_SECTION_LOOKUP = new Map(
  FINANCIAL_NOTES_SECTIONS.map((section) => [section.code, section]),
);

const cleanString = (value) => String(value ?? "").trim();
const roundAmount = (value) => Number(Number(value || 0).toFixed(2));

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

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

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

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

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

  return fallback;
};

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

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

  if (!Number.isFinite(parsed)) {
    throw new ApiError(400, "Valeur entière invalide.");
  }

  return parsed;
};

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

  if (!normalized) {
    throw new ApiError(400, `${fieldLabel} invalide.`);
  }

  if (!/^\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 shiftIsoDateByYears = (value, years) => {
  const [year, month, day] = parseIsoDate(value).split("-").map(Number);
  const shifted = new Date(Date.UTC(year + years, month - 1, day));

  if (shifted.getUTCMonth() !== month - 1) {
    return new Date(Date.UTC(year + years, month, 0)).toISOString().slice(0, 10);
  }

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

const minIsoDate = (left, right) => (left <= right ? left : right);
const maxIsoDate = (left, right) => (left >= right ? left : right);

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 makeAmountCell = (value) => ({
  value: roundAmount(value),
  formatted: formatDzdAmount(value),
});

const fetchCompany = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT id, name, currency
      FROM companies
      WHERE id = $1
      LIMIT 1
    `,
    [companyId],
  );

  if (!rows[0]) {
    throw new ApiError(404, "Société introuvable.");
  }

  return {
    id: Number(rows[0].id),
    name: rows[0].name,
    currency: rows[0].currency,
  };
};

const fetchFiscalYears = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT
        id,
        label,
        TO_CHAR(start_date, 'YYYY-MM-DD') AS start_date,
        TO_CHAR(end_date, 'YYYY-MM-DD') AS end_date,
        is_current,
        is_closed
      FROM fiscal_years
      WHERE company_id = $1
      ORDER BY start_date ASC
    `,
    [companyId],
  );

  return rows.map((row) => ({
    id: Number(row.id),
    label: row.label,
    start_date: row.start_date,
    end_date: row.end_date,
    is_current: row.is_current,
    is_closed: row.is_closed,
  }));
};

const fetchFiscalYearById = async (db, companyId, fiscalYearId) => {
  const fiscalYears = await fetchFiscalYears(db, companyId);

  return fiscalYears.find((item) => item.id === Number(fiscalYearId)) || null;
};

const resolveFiscalYearContext = async (db, companyId, fiscalYearId = 0) => {
  const fiscalYears = await fetchFiscalYears(db, companyId);
  const currentFiscalYear = fiscalYearId
    ? fiscalYears.find((item) => item.id === Number(fiscalYearId))
    : fiscalYears.find((item) => item.is_current) || fiscalYears[fiscalYears.length - 1];

  if (!currentFiscalYear) {
    throw new ApiError(404, "Aucun exercice comptable disponible.");
  }

  const currentIndex = fiscalYears.findIndex((item) => item.id === currentFiscalYear.id);
  const comparativeFiscalYear = currentIndex > 0 ? fiscalYears[currentIndex - 1] : null;

  return {
    fiscal_year: currentFiscalYear,
    comparative_fiscal_year: comparativeFiscalYear,
    fiscal_years: fiscalYears,
  };
};

const fetchAccounts = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT
        id,
        code,
        label,
        account_type,
        balance_normal_side,
        is_active
      FROM accounts
      WHERE company_id = $1
      ORDER BY code ASC
    `,
    [companyId],
  );

  return rows.map((row) => ({
    id: Number(row.id),
    code: row.code,
    label: row.label,
    account_type: row.account_type,
    balance_normal_side: row.balance_normal_side,
    is_active: row.is_active,
  }));
};

const getDefaultBalanceSheetLine = (account) => {
  const code = account.code;

  if (/^20/.test(code)) {
    return { line_code: "asset_non_current_intangible", sign: 1 };
  }

  if (/^(21|22|23|24|25)/.test(code)) {
    return { line_code: "asset_non_current_tangible", sign: 1 };
  }

  if (/^(26|27)/.test(code)) {
    return { line_code: "asset_non_current_financial", sign: 1 };
  }

  if (/^280/.test(code)) {
    return { line_code: "asset_non_current_intangible", sign: -1 };
  }

  if (/^(281|282|283|284|285)/.test(code)) {
    return { line_code: "asset_non_current_tangible", sign: -1 };
  }

  if (/^(286|287|296|297)/.test(code)) {
    return { line_code: "asset_non_current_financial", sign: -1 };
  }

  if (/^(30|31|32|33|34|35|36|37|38)/.test(code)) {
    return { line_code: "asset_current_inventory", sign: 1 };
  }

  if (/^39/.test(code)) {
    return { line_code: "asset_current_inventory", sign: -1 };
  }

  if (/^41/.test(code)) {
    return { line_code: "asset_current_receivables", sign: 1 };
  }

  if (/^49/.test(code)) {
    return { line_code: "asset_current_receivables", sign: -1 };
  }

  if (/^(50|51|52|53|54|55|57|58)/.test(code)) {
    return { line_code: "asset_current_treasury", sign: 1 };
  }

  if (/^59/.test(code)) {
    return { line_code: "asset_current_treasury", sign: -1 };
  }

  if (/^(101|102|103|104|105)/.test(code)) {
    return { line_code: "equity_capital", sign: 1 };
  }

  if (/^(106|107|108|13)/.test(code)) {
    return { line_code: "equity_reserves", sign: 1 };
  }

  if (/^(11|119)/.test(code)) {
    return { line_code: "equity_retained_earnings", sign: 1 };
  }

  if (/^12/.test(code)) {
    return { line_code: "equity_current_result", sign: 1 };
  }

  if (/^(16|17|18)/.test(code)) {
    return { line_code: "liability_non_current", sign: 1 };
  }

  if (/^(40|42|43|44|45|46|47|48)/.test(code)) {
    return { line_code: "liability_current", sign: 1 };
  }

  return null;
};

const getDefaultIncomeLine = (account) => {
  const code = account.code;

  if (/^76/.test(code)) {
    return { line_code: "financial_products", sign: 1 };
  }

  if (/^66/.test(code)) {
    return { line_code: "financial_charges", sign: 1 };
  }

  if (/^69/.test(code)) {
    return { line_code: "income_tax", sign: 1 };
  }

  if (/^(70|71|72|73|74|75|77|78)/.test(code)) {
    return { line_code: "operating_products", sign: 1 };
  }

  if (/^(60|61|62|63|64|65|67|68)/.test(code)) {
    return { line_code: "operating_charges", sign: 1 };
  }

  return null;
};

const getDefaultCashFlowLine = (account) => {
  const code = account.code;

  if (/^(20|21|22|23|24|25|26|27)/.test(code)) {
    return { line_code: "investing_cash_flow", sign: -1 };
  }

  if (/^(10|11|12|13|14|15|16|17|18)/.test(code)) {
    return { line_code: "financing_cash_flow", sign: 1 };
  }

  if (/^7/.test(code)) {
    return { line_code: "operating_cash_flow", sign: 1 };
  }

  if (/^6/.test(code)) {
    return { line_code: "operating_cash_flow", sign: -1 };
  }

  return null;
};

const getDefaultMappingsForAccount = (account) => {
  const mappings = [];
  const balanceMapping = getDefaultBalanceSheetLine(account);
  const incomeMapping = getDefaultIncomeLine(account);
  const cashFlowMapping = getDefaultCashFlowLine(account);

  if (balanceMapping) {
    const line = BALANCE_LINE_LOOKUP.get(balanceMapping.line_code);

    mappings.push({
      statement_type: "balance_sheet",
      line_code: balanceMapping.line_code,
      line_label: line?.label || balanceMapping.line_code,
      sign: balanceMapping.sign,
    });
  }

  if (incomeMapping) {
    const line = INCOME_LINE_LOOKUP.get(incomeMapping.line_code);

    mappings.push({
      statement_type: "income_statement",
      line_code: incomeMapping.line_code,
      line_label: line?.label || incomeMapping.line_code,
      sign: incomeMapping.sign,
    });
  }

  if (cashFlowMapping) {
    const line = CASH_FLOW_LINE_LOOKUP.get(cashFlowMapping.line_code);

    mappings.push({
      statement_type: "cash_flow",
      line_code: cashFlowMapping.line_code,
      line_label: line?.label || cashFlowMapping.line_code,
      sign: cashFlowMapping.sign,
    });
  }

  return mappings;
};

const ensureDefaultMappings = async (db, companyId) => {
  const accounts = await fetchAccounts(db, companyId);
  const mappingRows = await db.query(
    `
      SELECT account_id, statement_type
      FROM financial_statement_mapping
      WHERE company_id = $1
    `,
    [companyId],
  );
  const existing = new Set(
    mappingRows.rows.map((row) => `${row.account_id}:${row.statement_type}`),
  );

  for (const account of accounts.filter((item) => item.is_active)) {
    const defaults = getDefaultMappingsForAccount(account);

    for (const mapping of defaults) {
      const key = `${account.id}:${mapping.statement_type}`;

      if (existing.has(key)) {
        continue;
      }

      await db.query(
        `
          INSERT INTO financial_statement_mapping (
            company_id,
            account_id,
            statement_type,
            line_code,
            line_label,
            sign
          )
          VALUES ($1, $2, $3, $4, $5, $6)
          ON CONFLICT (company_id, statement_type, line_code, account_id) DO NOTHING
        `,
        [
          companyId,
          account.id,
          mapping.statement_type,
          mapping.line_code,
          mapping.line_label,
          mapping.sign,
        ],
      );

      existing.add(key);
    }
  }
};

const fetchMappingRows = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT
        fsm.id,
        fsm.account_id,
        fsm.statement_type,
        fsm.line_code,
        fsm.line_label,
        fsm.sign,
        a.code AS account_code,
        a.label AS account_label,
        a.account_type,
        a.balance_normal_side
      FROM financial_statement_mapping fsm
      JOIN accounts a
        ON a.id = fsm.account_id
      WHERE fsm.company_id = $1
      ORDER BY a.code ASC, fsm.statement_type ASC
    `,
    [companyId],
  );

  return rows.map((row) => ({
    id: Number(row.id),
    account_id: Number(row.account_id),
    statement_type: row.statement_type,
    line_code: row.line_code,
    line_label: row.line_label,
    sign: Number(row.sign),
    account_code: row.account_code,
    account_label: row.account_label,
    account_type: row.account_type,
    balance_normal_side: row.balance_normal_side,
  }));
};

const fetchAccountAmountsToDate = async (db, companyId, dateValue) => {
  const { rows } = await db.query(
    `
      SELECT
        el.account_id,
        COALESCE(
          SUM(
            CASE
              WHEN a.balance_normal_side = 'credit' THEN el.credit - el.debit
              ELSE el.debit - el.credit
            END
          ),
          0
        )::numeric AS amount
      FROM entry_lines el
      JOIN journal_entries je
        ON je.id = el.entry_id
      JOIN accounts a
        ON a.id = el.account_id
      WHERE je.company_id = $1
        AND je.status = 'validated'
        AND je.entry_date <= $2::date
      GROUP BY el.account_id
    `,
    [companyId, dateValue],
  );

  return new Map(rows.map((row) => [Number(row.account_id), roundAmount(row.amount)]));
};

const fetchAccountMovementsBetween = async (db, companyId, dateFrom, dateTo) => {
  const { rows } = await db.query(
    `
      SELECT
        el.account_id,
        COALESCE(
          SUM(
            CASE
              WHEN a.balance_normal_side = 'credit' THEN el.credit - el.debit
              ELSE el.debit - el.credit
            END
          ),
          0
        )::numeric AS amount
      FROM entry_lines el
      JOIN journal_entries je
        ON je.id = el.entry_id
      JOIN accounts a
        ON a.id = el.account_id
      WHERE je.company_id = $1
        AND je.status = 'validated'
        AND je.entry_date BETWEEN $2::date AND $3::date
      GROUP BY el.account_id
    `,
    [companyId, dateFrom, dateTo],
  );

  return new Map(rows.map((row) => [Number(row.account_id), roundAmount(row.amount)]));
};

const fetchClassFiveClosingBalance = async (db, companyId, dateValue) => {
  const { rows } = await db.query(
    `
      SELECT
        COALESCE(
          SUM(
            CASE
              WHEN a.balance_normal_side = 'credit' THEN el.credit - el.debit
              ELSE el.debit - el.credit
            END
          ),
          0
        )::numeric AS amount
      FROM entry_lines el
      JOIN journal_entries je
        ON je.id = el.entry_id
      JOIN accounts a
        ON a.id = el.account_id
      WHERE je.company_id = $1
        AND je.status = 'validated'
        AND je.entry_date <= $2::date
        AND a.code LIKE '5%'
    `,
    [companyId, dateValue],
  );

  return roundAmount(rows[0]?.amount || 0);
};

const aggregateMappings = (mappingRows, statementType, accountAmounts) => {
  const aggregation = new Map();

  mappingRows
    .filter((row) => row.statement_type === statementType)
    .forEach((row) => {
      const amount = roundAmount((accountAmounts.get(row.account_id) || 0) * row.sign);

      if (!aggregation.has(row.line_code)) {
        aggregation.set(row.line_code, {
          line_code: row.line_code,
          line_label: row.line_label,
          amount: 0,
          accounts: [],
        });
      }

      const target = aggregation.get(row.line_code);
      target.amount = roundAmount(target.amount + amount);
      target.accounts.push({
        account_id: row.account_id,
        code: row.account_code,
        label: row.account_label,
        amount,
        sign: row.sign,
      });
    });

  return aggregation;
};

const getComparativeDatesForBilan = ({ asOfDate, fiscalYear, comparativeFiscalYear }) => {
  if (!comparativeFiscalYear) {
    return {
      current_date: asOfDate,
      comparative_date: "",
    };
  }

  const shifted = shiftIsoDateByYears(asOfDate, -1);

  return {
    current_date: asOfDate,
    comparative_date: maxIsoDate(
      comparativeFiscalYear.start_date,
      minIsoDate(shifted, comparativeFiscalYear.end_date),
    ),
  };
};

const getComparativeRange = ({ dateFrom, dateTo, comparativeFiscalYear }) => {
  if (!comparativeFiscalYear) {
    return {
      date_from: "",
      date_to: "",
    };
  }

  return {
    date_from: maxIsoDate(
      comparativeFiscalYear.start_date,
      shiftIsoDateByYears(dateFrom, -1),
    ),
    date_to: minIsoDate(comparativeFiscalYear.end_date, shiftIsoDateByYears(dateTo, -1)),
  };
};

const buildBilanPayload = ({
  company,
  fiscalYear,
  comparativeFiscalYear,
  asOfDate,
  comparativeDate,
  currentMap,
  comparativeMap,
  comparative,
}) => {
  const assetSections = [];
  const liabilitySections = [];

  const makeLineItem = (line) => ({
    code: line.code,
    label: line.label,
    current: roundAmount(currentMap.get(line.code)?.amount || 0),
    comparative: comparative ? roundAmount(comparativeMap.get(line.code)?.amount || 0) : 0,
  });

  const pushSection = (target, title, lines) => {
    const items = lines.map(makeLineItem);
    const totalCurrent = roundAmount(items.reduce((sum, item) => sum + item.current, 0));
    const totalComparative = roundAmount(items.reduce((sum, item) => sum + item.comparative, 0));

    target.push({
      title,
      rows: items.map((item) => ({
        ...item,
        current_formatted: formatDzdAmount(item.current),
        comparative_formatted: formatDzdAmount(item.comparative),
      })),
      total: {
        current: totalCurrent,
        comparative: totalComparative,
        current_formatted: formatDzdAmount(totalCurrent),
        comparative_formatted: formatDzdAmount(totalComparative),
      },
    });
  };

  const assetSectionsConfig = [
    {
      title: "Actif non courant",
      lines: BALANCE_SHEET_LINES.assets.filter((line) => line.section === "Actif non courant"),
    },
    {
      title: "Actif courant",
      lines: BALANCE_SHEET_LINES.assets.filter((line) => line.section === "Actif courant"),
    },
  ];
  const liabilitySectionsConfig = [
    {
      title: "Capitaux propres",
      lines: BALANCE_SHEET_LINES.liabilities.filter(
        (line) => line.section === "Capitaux propres",
      ),
    },
    {
      title: "Passifs non courants",
      lines: BALANCE_SHEET_LINES.liabilities.filter(
        (line) => line.section === "Passifs non courants",
      ),
    },
    {
      title: "Passifs courants",
      lines: BALANCE_SHEET_LINES.liabilities.filter(
        (line) => line.section === "Passifs courants",
      ),
    },
  ];

  assetSectionsConfig.forEach((section) => pushSection(assetSections, section.title, section.lines));
  liabilitySectionsConfig.forEach((section) =>
    pushSection(liabilitySections, section.title, section.lines),
  );

  const totalAssetCurrent = roundAmount(
    assetSections.reduce((sum, section) => sum + section.total.current, 0),
  );
  const totalAssetComparative = roundAmount(
    assetSections.reduce((sum, section) => sum + section.total.comparative, 0),
  );
  const totalLiabilityCurrent = roundAmount(
    liabilitySections.reduce((sum, section) => sum + section.total.current, 0),
  );
  const totalLiabilityComparative = roundAmount(
    liabilitySections.reduce((sum, section) => sum + section.total.comparative, 0),
  );

  return {
    statement: "bilan",
    title: "Bilan",
    company,
    fiscal_year: fiscalYear,
    comparative_fiscal_year: comparativeFiscalYear,
    as_of_date: asOfDate,
    comparative_as_of_date: comparativeDate,
    comparative,
    asset_sections: assetSections,
    liability_sections: liabilitySections,
    totals: {
      asset: makeAmountCell(totalAssetCurrent),
      asset_comparative: makeAmountCell(totalAssetComparative),
      liability: makeAmountCell(totalLiabilityCurrent),
      liability_comparative: makeAmountCell(totalLiabilityComparative),
      balanced: Math.abs(totalAssetCurrent - totalLiabilityCurrent) < 0.01,
      difference: makeAmountCell(totalAssetCurrent - totalLiabilityCurrent),
    },
  };
};

const buildIncomeStatementPayload = ({
  company,
  fiscalYear,
  comparativeFiscalYear,
  dateFrom,
  dateTo,
  comparativeRange,
  currentMap,
  comparativeMap,
}) => {
  const valueFor = (code, map) => roundAmount(map.get(code)?.amount || 0);

  const operatingProductsCurrent = valueFor("operating_products", currentMap);
  const operatingChargesCurrent = valueFor("operating_charges", currentMap);
  const financialProductsCurrent = valueFor("financial_products", currentMap);
  const financialChargesCurrent = valueFor("financial_charges", currentMap);
  const incomeTaxCurrent = valueFor("income_tax", currentMap);
  const operatingResultCurrent = roundAmount(
    operatingProductsCurrent - operatingChargesCurrent,
  );
  const financialResultCurrent = roundAmount(
    financialProductsCurrent - financialChargesCurrent,
  );
  const ordinaryBeforeTaxCurrent = roundAmount(
    operatingResultCurrent + financialResultCurrent,
  );
  const netResultCurrent = roundAmount(ordinaryBeforeTaxCurrent - incomeTaxCurrent);

  const operatingProductsComparative = valueFor("operating_products", comparativeMap);
  const operatingChargesComparative = valueFor("operating_charges", comparativeMap);
  const financialProductsComparative = valueFor("financial_products", comparativeMap);
  const financialChargesComparative = valueFor("financial_charges", comparativeMap);
  const incomeTaxComparative = valueFor("income_tax", comparativeMap);
  const operatingResultComparative = roundAmount(
    operatingProductsComparative - operatingChargesComparative,
  );
  const financialResultComparative = roundAmount(
    financialProductsComparative - financialChargesComparative,
  );
  const ordinaryBeforeTaxComparative = roundAmount(
    operatingResultComparative + financialResultComparative,
  );
  const netResultComparative = roundAmount(
    ordinaryBeforeTaxComparative - incomeTaxComparative,
  );

  const rows = [
    {
      label: "Produits d'exploitation",
      current: operatingProductsCurrent,
      comparative: operatingProductsComparative,
      row_type: "section",
    },
    {
      label: "Charges d'exploitation",
      current: operatingChargesCurrent,
      comparative: operatingChargesComparative,
      row_type: "section",
    },
    {
      label: "Résultat d'exploitation",
      current: operatingResultCurrent,
      comparative: operatingResultComparative,
      row_type: "computed_primary",
    },
    {
      label: "Produits financiers",
      current: financialProductsCurrent,
      comparative: financialProductsComparative,
      row_type: "section",
    },
    {
      label: "Charges financières",
      current: financialChargesCurrent,
      comparative: financialChargesComparative,
      row_type: "section",
    },
    {
      label: "Résultat financier",
      current: financialResultCurrent,
      comparative: financialResultComparative,
      row_type: "computed",
    },
    {
      label: "Résultat ordinaire avant impôts",
      current: ordinaryBeforeTaxCurrent,
      comparative: ordinaryBeforeTaxComparative,
      row_type: "computed_primary",
    },
    {
      label: "Impôts sur les bénéfices",
      current: incomeTaxCurrent,
      comparative: incomeTaxComparative,
      row_type: "section",
    },
    {
      label: "Résultat net de l'exercice",
      current: netResultCurrent,
      comparative: netResultComparative,
      row_type: "result",
    },
  ].map((row) => ({
    ...row,
    current_formatted: formatDzdAmount(row.current),
    comparative_formatted: formatDzdAmount(row.comparative),
  }));

  return {
    statement: "income-statement",
    title: "Compte de résultat",
    company,
    fiscal_year: fiscalYear,
    comparative_fiscal_year: comparativeFiscalYear,
    date_from: dateFrom,
    date_to: dateTo,
    comparative_date_from: comparativeRange.date_from,
    comparative_date_to: comparativeRange.date_to,
    rows,
    totals: {
      net_result: makeAmountCell(netResultCurrent),
      comparative_net_result: makeAmountCell(netResultComparative),
    },
  };
};

const buildCashFlowPayload = ({
  company,
  fiscalYear,
  comparativeFiscalYear,
  dateFrom,
  dateTo,
  comparativeRange,
  currentMap,
  comparativeMap,
  openingTreasury,
  comparativeOpeningTreasury,
  closingTreasury,
  comparativeClosingTreasury,
}) => {
  const getValue = (code, map) => roundAmount(map.get(code)?.amount || 0);
  const operatingCurrent = getValue("operating_cash_flow", currentMap);
  const investingCurrent = getValue("investing_cash_flow", currentMap);
  const financingCurrent = getValue("financing_cash_flow", currentMap);
  const variationCurrent = roundAmount(closingTreasury - openingTreasury);

  const operatingComparative = getValue("operating_cash_flow", comparativeMap);
  const investingComparative = getValue("investing_cash_flow", comparativeMap);
  const financingComparative = getValue("financing_cash_flow", comparativeMap);
  const variationComparative = roundAmount(
    comparativeClosingTreasury - comparativeOpeningTreasury,
  );

  const rows = [
    {
      label: "Flux liés aux activités opérationnelles",
      current: operatingCurrent,
      comparative: operatingComparative,
      row_type: "section",
    },
    {
      label: "Flux liés aux activités d'investissement",
      current: investingCurrent,
      comparative: investingComparative,
      row_type: "section",
    },
    {
      label: "Flux liés aux activités de financement",
      current: financingCurrent,
      comparative: financingComparative,
      row_type: "section",
    },
    {
      label: "Variation nette de trésorerie",
      current: variationCurrent,
      comparative: variationComparative,
      row_type: "computed_primary",
    },
    {
      label: "Trésorerie à l'ouverture",
      current: openingTreasury,
      comparative: comparativeOpeningTreasury,
      row_type: "section",
    },
    {
      label: "Trésorerie à la clôture",
      current: closingTreasury,
      comparative: comparativeClosingTreasury,
      row_type: "result",
    },
  ].map((row) => ({
    ...row,
    current_formatted: formatDzdAmount(row.current),
    comparative_formatted: formatDzdAmount(row.comparative),
  }));

  return {
    statement: "cash-flow",
    title: "Tableau des flux de trésorerie",
    company,
    fiscal_year: fiscalYear,
    comparative_fiscal_year: comparativeFiscalYear,
    date_from: dateFrom,
    date_to: dateTo,
    comparative_date_from: comparativeRange.date_from,
    comparative_date_to: comparativeRange.date_to,
    rows,
  };
};

const buildEquityChangesPayload = ({
  company,
  fiscalYear,
  comparativeFiscalYear,
  currentMap,
  previousMap,
}) => {
  const rows = EQUITY_CHANGE_ROWS.map((line) => {
    const opening = roundAmount(previousMap.get(line.code)?.amount || 0);
    const closing = roundAmount(currentMap.get(line.code)?.amount || 0);
    const change = roundAmount(closing - opening);
    const capital = line.code === "equity_capital" ? change : 0;
    const result = line.code === "equity_current_result" ? change : 0;
    const dividends =
      line.code === "equity_retained_earnings" && change < 0 ? change : 0;
    const other = roundAmount(change - capital - result - dividends);

    return {
      code: line.code,
      label: line.label,
      opening,
      capital,
      result,
      dividends,
      other,
      closing,
      opening_formatted: formatDzdAmount(opening),
      capital_formatted: formatDzdAmount(capital),
      result_formatted: formatDzdAmount(result),
      dividends_formatted: formatDzdAmount(dividends),
      other_formatted: formatDzdAmount(other),
      closing_formatted: formatDzdAmount(closing),
    };
  });

  const totals = rows.reduce(
    (accumulator, row) => ({
      opening: roundAmount(accumulator.opening + row.opening),
      capital: roundAmount(accumulator.capital + row.capital),
      result: roundAmount(accumulator.result + row.result),
      dividends: roundAmount(accumulator.dividends + row.dividends),
      other: roundAmount(accumulator.other + row.other),
      closing: roundAmount(accumulator.closing + row.closing),
    }),
    { opening: 0, capital: 0, result: 0, dividends: 0, other: 0, closing: 0 },
  );

  return {
    statement: "equity-changes",
    title: "Variation des capitaux propres",
    company,
    fiscal_year: fiscalYear,
    comparative_fiscal_year: comparativeFiscalYear,
    columns: [
      { key: "opening", label: "Ouverture N-1" },
      { key: "capital", label: "Capital" },
      { key: "result", label: "Résultat" },
      { key: "dividends", label: "Dividendes" },
      { key: "other", label: "Autres mouvements" },
      { key: "closing", label: "Clôture N" },
    ],
    rows,
    totals: Object.fromEntries(
      Object.entries(totals).map(([key, value]) => [key, makeAmountCell(value)]),
    ),
  };
};

const buildDefaultNotes = () =>
  FINANCIAL_NOTES_SECTIONS.map((section) => ({
    code: section.code,
    label: section.label,
    content: section.defaultContent,
  }));

const fetchNotes = async (db, companyId, fiscalYearId) => {
  const { rows } = await db.query(
    `
      SELECT sections
      FROM financial_statement_notes
      WHERE company_id = $1
        AND fiscal_year_id = $2
      LIMIT 1
    `,
    [companyId, fiscalYearId],
  );

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

  const sections = rows[0].sections || {};

  return FINANCIAL_NOTES_SECTIONS.map((section) => ({
    code: section.code,
    label: section.label,
    content: sections[section.code] || section.defaultContent,
  }));
};

const loadBilanData = async (db, companyId, query) => {
  await ensureDefaultMappings(db, companyId);
  const comparative = parseBoolean(query.comparative, true);
  const { fiscal_year: fiscalYear, comparative_fiscal_year: comparativeFiscalYear } =
    await resolveFiscalYearContext(db, companyId, parseInteger(query.fiscal_year_id, 0));
  const asOfDate = cleanString(query.as_of_date)
    ? parseIsoDate(query.as_of_date, "La date d'arrêté")
    : fiscalYear.is_current
      ? minIsoDate(TODAY, fiscalYear.end_date)
      : fiscalYear.end_date;
  const dates = getComparativeDatesForBilan({
    asOfDate,
    fiscalYear,
    comparativeFiscalYear,
  });
  const mappingRows = await fetchMappingRows(db, companyId);
  const currentAmounts = await fetchAccountAmountsToDate(db, companyId, dates.current_date);
  const comparativeAmounts =
    comparative && dates.comparative_date
      ? await fetchAccountAmountsToDate(db, companyId, dates.comparative_date)
      : new Map();

  return buildBilanPayload({
    company: await fetchCompany(db, companyId),
    fiscalYear,
    comparativeFiscalYear,
    asOfDate: dates.current_date,
    comparativeDate: dates.comparative_date,
    comparative,
    currentMap: aggregateMappings(mappingRows, "balance_sheet", currentAmounts),
    comparativeMap: aggregateMappings(mappingRows, "balance_sheet", comparativeAmounts),
  });
};

const loadIncomeStatementData = async (db, companyId, query) => {
  await ensureDefaultMappings(db, companyId);
  const { fiscal_year: fiscalYear, comparative_fiscal_year: comparativeFiscalYear } =
    await resolveFiscalYearContext(db, companyId, parseInteger(query.fiscal_year_id, 0));
  const dateFrom = cleanString(query.date_from)
    ? parseIsoDate(query.date_from, "La date de début")
    : fiscalYear.start_date;
  const dateTo = cleanString(query.date_to)
    ? parseIsoDate(query.date_to, "La date de fin")
    : fiscalYear.is_current
      ? minIsoDate(TODAY, fiscalYear.end_date)
      : fiscalYear.end_date;
  const comparativeRange = getComparativeRange({
    dateFrom,
    dateTo,
    comparativeFiscalYear,
  });
  const mappingRows = await fetchMappingRows(db, companyId);
  const currentAmounts = await fetchAccountMovementsBetween(db, companyId, dateFrom, dateTo);
  const comparativeAmounts =
    comparativeRange.date_from && comparativeRange.date_to
      ? await fetchAccountMovementsBetween(
          db,
          companyId,
          comparativeRange.date_from,
          comparativeRange.date_to,
        )
      : new Map();

  return buildIncomeStatementPayload({
    company: await fetchCompany(db, companyId),
    fiscalYear,
    comparativeFiscalYear,
    dateFrom,
    dateTo,
    comparativeRange,
    currentMap: aggregateMappings(mappingRows, "income_statement", currentAmounts),
    comparativeMap: aggregateMappings(mappingRows, "income_statement", comparativeAmounts),
  });
};

const loadCashFlowData = async (db, companyId, query) => {
  await ensureDefaultMappings(db, companyId);
  const { fiscal_year: fiscalYear, comparative_fiscal_year: comparativeFiscalYear } =
    await resolveFiscalYearContext(db, companyId, parseInteger(query.fiscal_year_id, 0));
  const dateFrom = fiscalYear.start_date;
  const dateTo = fiscalYear.is_current ? minIsoDate(TODAY, fiscalYear.end_date) : fiscalYear.end_date;
  const comparativeRange = comparativeFiscalYear
    ? {
        date_from: comparativeFiscalYear.start_date,
        date_to: comparativeFiscalYear.end_date,
      }
    : { date_from: "", date_to: "" };
  const mappingRows = await fetchMappingRows(db, companyId);
  const currentAmounts = await fetchAccountMovementsBetween(db, companyId, dateFrom, dateTo);
  const comparativeAmounts =
    comparativeRange.date_from && comparativeRange.date_to
      ? await fetchAccountMovementsBetween(
          db,
          companyId,
          comparativeRange.date_from,
          comparativeRange.date_to,
        )
      : new Map();
  const openingCurrent = await fetchClassFiveClosingBalance(
    db,
    companyId,
    new Date(
      new Date(`${fiscalYear.start_date}T00:00:00Z`).getTime() - 86400000,
    )
      .toISOString()
      .slice(0, 10),
  );
  const openingComparative = comparativeFiscalYear
    ? await fetchClassFiveClosingBalance(
        db,
        companyId,
        new Date(
          new Date(`${comparativeFiscalYear.start_date}T00:00:00Z`).getTime() - 86400000,
        )
          .toISOString()
          .slice(0, 10),
      )
    : 0;
  const closingCurrent = await fetchClassFiveClosingBalance(db, companyId, dateTo);
  const closingComparative = comparativeFiscalYear
    ? await fetchClassFiveClosingBalance(db, companyId, comparativeFiscalYear.end_date)
    : 0;

  return buildCashFlowPayload({
    company: await fetchCompany(db, companyId),
    fiscalYear,
    comparativeFiscalYear,
    dateFrom,
    dateTo,
    comparativeRange,
    currentMap: aggregateMappings(mappingRows, "cash_flow", currentAmounts),
    comparativeMap: aggregateMappings(mappingRows, "cash_flow", comparativeAmounts),
    openingTreasury: openingCurrent,
    comparativeOpeningTreasury: openingComparative,
    closingTreasury: closingCurrent,
    comparativeClosingTreasury: closingComparative,
  });
};

const loadEquityChangesData = async (db, companyId, query) => {
  await ensureDefaultMappings(db, companyId);
  const { fiscal_year: fiscalYear, comparative_fiscal_year: comparativeFiscalYear } =
    await resolveFiscalYearContext(db, companyId, parseInteger(query.fiscal_year_id, 0));
  const mappingRows = await fetchMappingRows(db, companyId);
  const currentAmounts = await fetchAccountAmountsToDate(db, companyId, fiscalYear.end_date);
  const previousAmounts = comparativeFiscalYear
    ? await fetchAccountAmountsToDate(db, companyId, comparativeFiscalYear.end_date)
    : new Map();

  return buildEquityChangesPayload({
    company: await fetchCompany(db, companyId),
    fiscalYear,
    comparativeFiscalYear,
    currentMap: aggregateMappings(mappingRows, "balance_sheet", currentAmounts),
    previousMap: aggregateMappings(mappingRows, "balance_sheet", previousAmounts),
  });
};

const loadNotesData = async (db, companyId, query) => {
  const { fiscal_year: fiscalYear, comparative_fiscal_year: comparativeFiscalYear } =
    await resolveFiscalYearContext(db, companyId, parseInteger(query.fiscal_year_id, 0));

  return {
    statement: "notes",
    title: "Annexe",
    company: await fetchCompany(db, companyId),
    fiscal_year: fiscalYear,
    comparative_fiscal_year: comparativeFiscalYear,
    sections: await fetchNotes(db, companyId, fiscalYear.id),
  };
};

const renderAmount = (value) => formatDzdAmount(roundAmount(value));

const renderStatementHtml = (statement) => {
  if (statement.statement === "bilan") {
    const renderSection = (section) => `
      <div class="statement-block">
        <h3>${section.title}</h3>
        <table>
          <thead>
            <tr><th>Libellé</th><th>N</th><th>N-1</th></tr>
          </thead>
          <tbody>
            ${section.rows
              .map(
                (row) => `
                  <tr>
                    <td>${row.label}</td>
                    <td class="amount">${row.current_formatted}</td>
                    <td class="amount">${row.comparative_formatted}</td>
                  </tr>
                `,
              )
              .join("")}
            <tr class="total-row">
              <td>Total ${section.title}</td>
              <td class="amount">${section.total.current_formatted}</td>
              <td class="amount">${section.total.comparative_formatted}</td>
            </tr>
          </tbody>
        </table>
      </div>
    `;

    return `
      <section class="page">
        <h2>${statement.title}</h2>
        <p class="subtitle">${statement.company.name} · ${statement.fiscal_year.label}</p>
        <div class="columns">
          <div>
            <h3 class="column-title">ACTIF</h3>
            ${statement.asset_sections.map(renderSection).join("")}
            <div class="highlight-card">
              <span>TOTAL ACTIF</span>
              <strong>${statement.totals.asset.formatted}</strong>
            </div>
          </div>
          <div>
            <h3 class="column-title">PASSIF + CAPITAUX PROPRES</h3>
            ${statement.liability_sections.map(renderSection).join("")}
            <div class="highlight-card">
              <span>TOTAL PASSIF</span>
              <strong>${statement.totals.liability.formatted}</strong>
            </div>
          </div>
        </div>
      </section>
    `;
  }

  if (statement.statement === "notes") {
    return `
      <section class="page">
        <h2>${statement.title}</h2>
        <p class="subtitle">${statement.company.name} · ${statement.fiscal_year.label}</p>
        ${statement.sections
          .map(
            (section) => `
              <div class="note-block">
                <h3>${section.label}</h3>
                <div class="note-content">${section.content.replace(/\n/g, "<br />")}</div>
              </div>
            `,
          )
          .join("")}
      </section>
    `;
  }

  if (statement.statement === "equity-changes") {
    return `
      <section class="page">
        <h2>${statement.title}</h2>
        <p class="subtitle">${statement.company.name} · ${statement.fiscal_year.label}</p>
        <table>
          <thead>
            <tr>
              <th>Composante</th>
              ${statement.columns.map((column) => `<th>${column.label}</th>`).join("")}
            </tr>
          </thead>
          <tbody>
            ${statement.rows
              .map(
                (row) => `
                  <tr>
                    <td>${row.label}</td>
                    <td class="amount">${row.opening_formatted}</td>
                    <td class="amount">${row.capital_formatted}</td>
                    <td class="amount">${row.result_formatted}</td>
                    <td class="amount">${row.dividends_formatted}</td>
                    <td class="amount">${row.other_formatted}</td>
                    <td class="amount">${row.closing_formatted}</td>
                  </tr>
                `,
              )
              .join("")}
          </tbody>
        </table>
      </section>
    `;
  }

  return `
    <section class="page">
      <h2>${statement.title}</h2>
      <p class="subtitle">${statement.company.name} · ${statement.fiscal_year.label}</p>
      <table>
        <thead>
          <tr>
            <th>Libellé</th>
            <th>N</th>
            <th>N-1</th>
          </tr>
        </thead>
        <tbody>
          ${statement.rows
            .map(
              (row) => `
                <tr class="${row.row_type || ""}">
                  <td>${row.label}</td>
                  <td class="amount">${row.current_formatted}</td>
                  <td class="amount">${row.comparative_formatted}</td>
                </tr>
              `,
            )
            .join("")}
        </tbody>
      </table>
    </section>
  `;
};

const exportFinancialStatementsPdf = async ({
  company,
  fiscalYear,
  selectedStatements,
}) => {
  const browser = await launchPuppeteerBrowser();

  try {
    const page = await browser.newPage();
    const html = `
      <!DOCTYPE html>
      <html lang="fr">
        <head>
          <meta charset="utf-8" />
          <style>
            * { box-sizing: border-box; }
            body { margin: 0; font-family: Inter, Arial, sans-serif; color: #0f172a; background: #f8fafc; }
            .cover, .page { padding: 28px; page-break-after: always; background: white; min-height: 100vh; }
            h1, h2, h3 { margin: 0; font-family: "DM Sans", Arial, sans-serif; }
            .cover { display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; }
            .cover h1 { font-size: 34px; }
            .cover p { color: #475569; }
            .subtitle { margin: 8px 0 20px; color: #64748b; }
            .columns { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
            .column-title { margin-bottom: 12px; color: #2563eb; }
            .statement-block { margin-bottom: 18px; }
            table { width: 100%; border-collapse: collapse; }
            th, td { border-bottom: 1px solid #e2e8f0; padding: 8px 10px; vertical-align: top; }
            th { background: #f8fafc; text-transform: uppercase; letter-spacing: 0.06em; font-size: 10px; }
            .amount { text-align: right; font-family: "JetBrains Mono", "Courier New", monospace; }
            .total-row td { font-weight: 700; background: #eff6ff; }
            .highlight-card { display: flex; justify-content: space-between; margin-top: 14px; padding: 12px 14px; border: 1px solid #bfdbfe; background: #eff6ff; border-radius: 12px; }
            .note-block { margin-top: 16px; border: 1px solid #e2e8f0; border-radius: 12px; padding: 14px; }
            .note-content { margin-top: 10px; color: #334155; line-height: 1.7; }
            tr.computed_primary td, tr.result td { font-weight: 700; background: #eff6ff; }
            tr.computed td { font-weight: 600; background: #f8fafc; }
          </style>
        </head>
        <body>
          <section class="cover">
            <h1>États Financiers SCF</h1>
            <p>${company.name}</p>
            <p>${fiscalYear.label}</p>
            <p>Généré le ${new Intl.DateTimeFormat("fr-FR", {
              dateStyle: "short",
              timeStyle: "short",
            }).format(new Date())}</p>
          </section>
          ${selectedStatements.map(renderStatementHtml).join("")}
        </body>
      </html>
    `;

    await page.setContent(html, {
      waitUntil: "networkidle0",
    });

    return await page.pdf({
      format: "A4",
      printBackground: true,
      landscape: false,
      margin: {
        top: "12mm",
        right: "10mm",
        bottom: "12mm",
        left: "10mm",
      },
    });
  } finally {
    await browser.close();
  }
};

const exportFinancialStatementsExcel = async ({ selectedStatements }) => {
  const excelJsModule = await import("exceljs");
  const ExcelJS = excelJsModule.default || excelJsModule;
  const workbook = new ExcelJS.Workbook();

  selectedStatements.forEach((statement) => {
    const sheet = workbook.addWorksheet(statement.title.slice(0, 31));

    sheet.addRow([statement.title]);
    sheet.lastRow.font = { bold: true, size: 16 };
    sheet.addRow([statement.company.name]);
    sheet.addRow([statement.fiscal_year.label]);
    sheet.addRow([]);

    if (statement.statement === "bilan") {
      sheet.addRow(["ACTIF", "", "PASSIF + CAPITAUX PROPRES", ""]);
      sheet.lastRow.font = { bold: true };
      const maxRows = Math.max(
        statement.asset_sections.reduce((sum, section) => sum + section.rows.length + 2, 0),
        statement.liability_sections.reduce((sum, section) => sum + section.rows.length + 2, 0),
      );

      const leftRows = [];
      statement.asset_sections.forEach((section) => {
        leftRows.push([section.title, ""]);
        section.rows.forEach((row) => {
          leftRows.push([row.label, row.current]);
        });
        leftRows.push([`Total ${section.title}`, section.total.current]);
      });

      const rightRows = [];
      statement.liability_sections.forEach((section) => {
        rightRows.push([section.title, ""]);
        section.rows.forEach((row) => {
          rightRows.push([row.label, row.current]);
        });
        rightRows.push([`Total ${section.title}`, section.total.current]);
      });

      for (let index = 0; index < maxRows; index += 1) {
        const left = leftRows[index] || ["", ""];
        const right = rightRows[index] || ["", ""];

        sheet.addRow([left[0], left[1], right[0], right[1]]);
      }

      sheet.addRow([]);
      sheet.addRow(["TOTAL ACTIF", statement.totals.asset.value, "TOTAL PASSIF", statement.totals.liability.value]);
    } else if (statement.statement === "notes") {
      statement.sections.forEach((section) => {
        sheet.addRow([section.label]);
        sheet.lastRow.font = { bold: true };
        sheet.addRow([section.content]);
        sheet.addRow([]);
      });
    } else if (statement.statement === "equity-changes") {
      sheet.addRow([
        "Composante",
        ...statement.columns.map((column) => column.label),
      ]);
      sheet.lastRow.font = { bold: true };
      statement.rows.forEach((row) => {
        sheet.addRow([
          row.label,
          row.opening,
          row.capital,
          row.result,
          row.dividends,
          row.other,
          row.closing,
        ]);
      });
    } else {
      sheet.addRow(["Libellé", "N", "N-1"]);
      sheet.lastRow.font = { bold: true };
      statement.rows.forEach((row) => {
        sheet.addRow([row.label, row.current, row.comparative]);
      });
    }

    sheet.columns = new Array(Math.max(sheet.columnCount, 3)).fill(null).map((_, index) => ({
      width: index === 0 ? 40 : 18,
    }));
  });

  return workbook.xlsx.writeBuffer();
};

const parseSelectedStatements = (value) => {
  const requested = cleanString(value)
    ? cleanString(value)
        .split(",")
        .map((item) => cleanString(item))
        .filter(Boolean)
    : ["bilan", "income-statement", "cash-flow", "equity-changes", "notes"];

  return Array.from(new Set(requested));
};

const loadStatementByKey = async (db, companyId, key, query) => {
  switch (key) {
    case "bilan":
      return loadBilanData(db, companyId, query);
    case "income-statement":
      return loadIncomeStatementData(db, companyId, query);
    case "cash-flow":
      return loadCashFlowData(db, companyId, query);
    case "equity-changes":
      return loadEquityChangesData(db, companyId, query);
    case "notes":
      return loadNotesData(db, companyId, query);
    default:
      throw new ApiError(404, "État financier introuvable.");
  }
};

export const financialStatementsController = {
  list: async (req, res) => {
    const companyId = getCompanyId(req);
    const context = await resolveFiscalYearContext(pool, companyId, 0);

    res.json({
      company_id: companyId,
      company: await fetchCompany(pool, companyId),
      fiscal_year: context.fiscal_year,
      comparative_fiscal_year: context.comparative_fiscal_year,
      items: [
        {
          key: "mapping",
          path: "/financial-statements/mapping",
          title: "Configuration du mapping",
        },
        {
          key: "bilan",
          path: "/financial-statements/bilan",
          title: "Bilan",
        },
        {
          key: "income-statement",
          path: "/financial-statements/income-statement",
          title: "Compte de résultat",
        },
        {
          key: "cash-flow",
          path: "/financial-statements/cash-flow",
          title: "Tableau des flux de trésorerie",
        },
        {
          key: "equity-changes",
          path: "/financial-statements/equity-changes",
          title: "Variation des capitaux propres",
        },
        {
          key: "notes",
          path: "/financial-statements/notes",
          title: "Annexe",
        },
        {
          key: "export",
          path: "/financial-statements/export",
          title: "Centre d'export",
        },
      ],
    });
  },

  getMapping: async (req, res) => {
    const companyId = getCompanyId(req);

    await ensureDefaultMappings(pool, companyId);

    const accounts = await fetchAccounts(pool, companyId);
    const mappingRows = await fetchMappingRows(pool, companyId);
    const mappingByAccount = new Map();

    mappingRows.forEach((mapping) => {
      if (!mappingByAccount.has(mapping.account_id)) {
        mappingByAccount.set(mapping.account_id, {});
      }

      mappingByAccount.get(mapping.account_id)[mapping.statement_type] = {
        id: mapping.id,
        line_code: mapping.line_code,
        line_label: mapping.line_label,
        sign: mapping.sign,
      };
    });

    res.json({
      company_id: companyId,
      items: accounts
        .filter((account) => account.is_active)
        .map((account) => ({
          account_id: account.id,
          account_code: account.code,
          account_label: account.label,
          account_type: account.account_type,
          balance_normal_side: account.balance_normal_side,
          mappings: mappingByAccount.get(account.id) || {},
        })),
      meta: {
        line_options: FINANCIAL_STATEMENT_LINE_OPTIONS,
        statement_types: FINANCIAL_STATEMENT_TYPES,
      },
    });
  },

  updateMapping: async (req, res) => {
    const companyId = getCompanyId(req, req.body);
    const items = Array.isArray(req.body.items) ? req.body.items : [];
    const client = await pool.connect();

    try {
      await client.query("BEGIN");
      await ensureDefaultMappings(client, companyId);
      const accounts = await fetchAccounts(client, companyId);
      const validAccountIds = new Set(accounts.map((account) => account.id));

      for (const item of items) {
        const accountId = Number(item.account_id || 0);

        if (!validAccountIds.has(accountId)) {
          throw new ApiError(400, `Compte invalide: ${item.account_id}`);
        }

        for (const statementType of FINANCIAL_STATEMENT_TYPES) {
          const payload = item.mappings?.[statementType] || null;

          await client.query(
            `
              DELETE FROM financial_statement_mapping
              WHERE company_id = $1
                AND account_id = $2
                AND statement_type = $3
            `,
            [companyId, accountId, statementType],
          );

          if (!payload?.line_code) {
            continue;
          }

          const lineOptions = FINANCIAL_STATEMENT_LINE_OPTIONS[statementType] || [];
          const line = lineOptions.find((option) => option.code === payload.line_code);

          if (!line) {
            throw new ApiError(400, `Ligne SCF invalide pour ${statementType}.`);
          }

          const sign = Number(payload.sign) === -1 ? -1 : 1;

          await client.query(
            `
              INSERT INTO financial_statement_mapping (
                company_id,
                account_id,
                statement_type,
                line_code,
                line_label,
                sign
              )
              VALUES ($1, $2, $3, $4, $5, $6)
            `,
            [companyId, accountId, statementType, line.code, line.label, sign],
          );
        }
      }

      await logAudit(client, {
        companyId,
        userId: req.user?.sub || null,
        action: "financial_statements.mapping.update",
        entityType: "financial_statement_mapping",
        entityId: null,
        newData: {
          updated_accounts: items.length,
        },
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.json({
        message: "Mapping SCF mis à jour.",
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },

  getBilan: async (req, res) => {
    const companyId = getCompanyId(req);
    res.json(await loadBilanData(pool, companyId, req.query));
  },

  getIncomeStatement: async (req, res) => {
    const companyId = getCompanyId(req);
    res.json(await loadIncomeStatementData(pool, companyId, req.query));
  },

  getCashFlow: async (req, res) => {
    const companyId = getCompanyId(req);
    res.json(await loadCashFlowData(pool, companyId, req.query));
  },

  getEquityChanges: async (req, res) => {
    const companyId = getCompanyId(req);
    res.json(await loadEquityChangesData(pool, companyId, req.query));
  },

  getNotes: async (req, res) => {
    const companyId = getCompanyId(req);
    res.json(await loadNotesData(pool, companyId, req.query));
  },

  updateNotes: async (req, res) => {
    const companyId = getCompanyId(req, req.body);
    const fiscalYearId = parseInteger(req.body.fiscal_year_id, 0);

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

    const fiscalYear = await fetchFiscalYearById(pool, companyId, fiscalYearId);

    if (!fiscalYear) {
      throw new ApiError(404, "Exercice introuvable.");
    }

    const sectionsInput = Array.isArray(req.body.sections) ? req.body.sections : [];
    const sections = {};

    FINANCIAL_NOTES_SECTIONS.forEach((section) => {
      const payload = sectionsInput.find((item) => item.code === section.code);
      sections[section.code] = cleanString(payload?.content) || section.defaultContent;
    });

    await pool.query(
      `
        INSERT INTO financial_statement_notes (
          company_id,
          fiscal_year_id,
          sections,
          updated_by,
          updated_at
        )
        VALUES ($1, $2, $3::jsonb, $4, CURRENT_TIMESTAMP)
        ON CONFLICT (company_id, fiscal_year_id)
        DO UPDATE SET
          sections = EXCLUDED.sections,
          updated_by = EXCLUDED.updated_by,
          updated_at = CURRENT_TIMESTAMP
      `,
      [companyId, fiscalYearId, JSON.stringify(sections), req.user?.sub || null],
    );

    await logAudit(pool, {
      companyId,
      userId: req.user?.sub || null,
      action: "financial_statements.notes.update",
      entityType: "financial_statement_notes",
      entityId: fiscalYearId,
      newData: {
        fiscal_year_id: fiscalYearId,
        section_codes: Object.keys(sections),
      },
      ipAddress: req.ip,
    });

    res.json(await loadNotesData(pool, companyId, { fiscal_year_id: fiscalYearId }));
  },

  exportStatements: async (req, res) => {
    const companyId = getCompanyId(req);
    const format = cleanString(req.query.format || "pdf").toLowerCase();
    const selectedKeys = parseSelectedStatements(req.query.statements);
    const selectedStatements = [];

    for (const key of selectedKeys) {
      selectedStatements.push(await loadStatementByKey(pool, companyId, key, req.query));
    }

    if (!selectedStatements.length) {
      throw new ApiError(400, "Aucun état financier sélectionné.");
    }

    const company = selectedStatements[0].company;
    const fiscalYear = selectedStatements[0].fiscal_year;

    if (format === "excel" || format === "xlsx") {
      const buffer = await exportFinancialStatementsExcel({
        selectedStatements,
      });

      res.setHeader(
        "Content-Type",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      );
      res.setHeader(
        "Content-Disposition",
        `attachment; filename="etats-financiers-scf-${fiscalYear.label.replace(/\s+/g, "-").toLowerCase()}.xlsx"`,
      );
      res.send(Buffer.from(buffer));
      return;
    }

    const pdfBuffer = await exportFinancialStatementsPdf({
      company,
      fiscalYear,
      selectedStatements,
    });

    res.setHeader("Content-Type", "application/pdf");
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="etats-financiers-scf-${fiscalYear.label.replace(/\s+/g, "-").toLowerCase()}.pdf"`,
    );
    res.send(Buffer.from(pdfBuffer));
  },
};
