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

const TODAY = new Date().toISOString().slice(0, 10);
const MONTH_LABELS = [
  "Janvier",
  "Fevrier",
  "Mars",
  "Avril",
  "Mai",
  "Juin",
  "Juillet",
  "Aout",
  "Septembre",
  "Octobre",
  "Novembre",
  "Decembre",
];

const DECLARATION_TYPE_LABELS = {
  GN12: "GN°12",
  GN12BIS: "GN°12 Bis",
  G50TER: "G50ter",
  G50: "G50",
};

const ENTRY_KIND_LABELS = {
  gn12_integral: "IFU prévisionnel",
  gn12_minimum: "Minimum d'imposition IFU",
  gn12_tranche1: "IFU prévisionnel tranche 1",
  gn12_tranche2: "IFU prévisionnel tranche 2",
  gn12_tranche3: "IFU prévisionnel tranche 3",
  g50_tva: "Paiement TVA",
  g50_ibs: "Paiement IBS",
  g50ter_irg: "Paiement IRG/Salaires",
  penalty: "Pénalité fiscale",
};

const PENALTY_REFERENCE_ROWS = [
  {
    infraction: "Depot tardif",
    penalty: "10% des droits",
    article: "art. 402 CIDTA",
  },
  {
    infraction: "Apres mise en demeure (1 mois)",
    penalty: "25% + taxation d'office",
    article: "art. 402 CIDTA",
  },
  {
    infraction: "Paiement tardif",
    penalty: "10% + 3%/mois (max 25%)",
    article: "art. 402 CIDTA",
  },
  {
    infraction: "G50 neant en retard",
    penalty: "500 DA par obligation",
    article: "art. 364 quinquies CIDTA",
  },
];

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

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

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

  return companyId;
};

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

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

  if (!Number.isFinite(parsed)) {
    throw new ApiError(400, `${label} est invalide.`);
  }

  return parsed;
};

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

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

  if (!Number.isFinite(parsed)) {
    throw new ApiError(400, `${label} est invalide.`);
  }

  if (parsed < 0) {
    throw new ApiError(400, `${label} ne peut pas etre negatif.`);
  }

  return roundAmount(parsed);
};

const parseOptionalIsoDate = (value, label) => {
  const normalized = cleanString(value);

  if (!normalized) {
    return null;
  }

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

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

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

  return normalized;
};

const getTodayIsoDate = () => TODAY;

const dateToTime = (value) => {
  const normalized = parseOptionalIsoDate(value, "La date");
  return normalized ? new Date(`${normalized}T00:00:00Z`).getTime() : null;
};

const diffDays = (fromDate, toDate) => {
  const fromTime = dateToTime(fromDate);
  const toTime = dateToTime(toDate);

  if (fromTime === null || toTime === null) {
    return 0;
  }

  return Math.floor((toTime - fromTime) / (24 * 60 * 60 * 1000));
};

const monthLabel = (month) => MONTH_LABELS[Number(month || 0) - 1] || `M${month}`;

const getDeclarationTypeLabel = (declarationType) =>
  DECLARATION_TYPE_LABELS[declarationType] || declarationType;

const getEntryKindLabel = (entryKind) => ENTRY_KIND_LABELS[entryKind] || entryKind;

const buildG50PeriodLabel = ({ annee, periodicite, mois, trimestre }) => {
  if (periodicite === "trimestriel") {
    return `T${trimestre} ${annee}`;
  }

  return `${monthLabel(mois)} ${annee}`;
};

const buildG50Deadline = ({ annee, periodicite, mois, trimestre }) => {
  if (periodicite === "trimestriel") {
    const quarterDeadlineMonth = [4, 7, 10, 1][Number(trimestre || 1) - 1];
    const quarterDeadlineYear = Number(trimestre || 1) === 4 ? Number(annee) + 1 : Number(annee);
    return `${quarterDeadlineYear}-${String(quarterDeadlineMonth).padStart(2, "0")}-20`;
  }

  const nextMonthDate = new Date(Date.UTC(Number(annee), Number(mois), 20));
  return nextMonthDate.toISOString().slice(0, 10);
};

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

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

  return rows[0];
};

const fetchBankAccountById = async (db, companyId, bankAccountId) => {
  const { rows } = await db.query(
    `
      SELECT
        ba.id,
        ba.company_id,
        ba.account_id,
        ba.bank_name,
        ba.account_number,
        ba.rib,
        ba.currency,
        ba.is_active,
        a.code AS accounting_code,
        a.label AS accounting_label
      FROM bank_accounts ba
      LEFT JOIN accounts a
        ON a.id = ba.account_id
      WHERE ba.company_id = $1
        AND ba.id = $2
      LIMIT 1
    `,
    [companyId, bankAccountId],
  );

  if (!rows[0]) {
    throw new ApiError(404, "Compte bancaire introuvable.");
  }

  if (!rows[0].is_active) {
    throw new ApiError(409, "Le compte bancaire selectionne est inactif.");
  }

  return {
    ...rows[0],
    id: Number(rows[0].id),
    company_id: Number(rows[0].company_id),
    account_id: rows[0].account_id ? Number(rows[0].account_id) : null,
  };
};

const fetchJournalByType = async (db, companyId, journalType) => {
  const { rows } = await db.query(
    `
      SELECT id, code, label, journal_type, numbering_mode, is_active
      FROM journals
      WHERE company_id = $1
        AND journal_type = $2
      ORDER BY is_active DESC, code ASC
      LIMIT 1
    `,
    [companyId, journalType],
  );

  if (!rows[0]) {
    throw new ApiError(404, "Aucun journal correspondant n'est configure.");
  }

  if (!rows[0].is_active) {
    throw new ApiError(409, "Le journal selectionne est inactif.");
  }

  return rows[0];
};

const fetchPostingAccount = async (db, companyId, { exactCode, fallbackPrefixes = [], label }) => {
  const prefixes = fallbackPrefixes
    .map((value) => cleanString(value).toUpperCase())
    .filter(Boolean);
  const exact = cleanString(exactCode).toUpperCase();
  const likePatterns = prefixes.map((value) => `${value}%`);

  const { rows } = await db.query(
    `
      SELECT
        id,
        code,
        label,
        is_movable,
        is_active
      FROM accounts
      WHERE company_id = $1
        AND is_active = TRUE
        AND (
          UPPER(code) = $2
          OR (cardinality($3::text[]) > 0 AND UPPER(code) LIKE ANY($3::text[]))
        )
      ORDER BY
        CASE
          WHEN UPPER(code) = $2 THEN 0
          ELSE 1
        END,
        CASE
          WHEN is_movable THEN 0
          ELSE 1
        END,
        LENGTH(code) ASC,
        code ASC
      LIMIT 1
    `,
    [companyId, exact, likePatterns],
  );

  if (!rows[0]) {
    throw new ApiError(
      404,
      `${label} introuvable dans le plan comptable (${exactCode}).`,
    );
  }

  if (!rows[0].is_movable) {
    throw new ApiError(
      409,
      `${label} doit etre un compte mouvementable.`,
    );
  }

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

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

    if (rows[0]?.is_active && rows[0]?.is_movable) {
      return {
        ...rows[0],
        id: Number(rows[0].id),
      };
    }
  }

  return fetchPostingAccount(db, companyId, {
    exactCode: "512000",
    fallbackPrefixes: ["512"],
    label: "Compte banque",
  });
};

const resolveAccountingContext = async (db, companyId, entryDate) => {
  const { rows } = await db.query(
    `
      SELECT
        fy.id AS fiscal_year_id,
        fy.label AS fiscal_year_label,
        fy.is_closed AS fiscal_year_is_closed,
        ap.id AS period_id,
        ap.month,
        ap.year,
        ap.is_closed AS period_is_closed
      FROM fiscal_years fy
      LEFT JOIN accounting_periods ap
        ON ap.fiscal_year_id = fy.id
        AND ap.company_id = fy.company_id
        AND ap.month = EXTRACT(MONTH FROM $2::date)::int
        AND ap.year = EXTRACT(YEAR FROM $2::date)::int
      WHERE fy.company_id = $1
        AND $2::date BETWEEN fy.start_date AND fy.end_date
      ORDER BY fy.is_current DESC, fy.start_date DESC
      LIMIT 1
    `,
    [companyId, entryDate],
  );

  const context = rows[0] || null;

  if (!context?.fiscal_year_id || !context?.period_id) {
    throw new ApiError(
      400,
      "La date d'ecriture doit appartenir a un exercice et une periode comptables existants.",
    );
  }

  if (context.fiscal_year_is_closed || context.period_is_closed) {
    throw new ApiError(400, "La date d'ecriture doit appartenir a une periode ouverte.");
  }

  return {
    fiscal_year_id: Number(context.fiscal_year_id),
    fiscal_year_label: context.fiscal_year_label,
    period_id: Number(context.period_id),
  };
};

const buildEntryNumber = async (
  db,
  { companyId, journal, fiscalYearId, fiscalYearLabel, manualValue = "" },
) => {
  const manualEntryNumber = cleanString(manualValue);

  if (manualEntryNumber) {
    return manualEntryNumber.toUpperCase();
  }

  const fiscalSegment =
    (
      cleanString(fiscalYearLabel).replace(/[^0-9A-Za-z]+/g, "") || String(fiscalYearId)
    ).toUpperCase();
  const prefix = `${cleanString(journal.code).toUpperCase()}-${fiscalSegment}-`;
  const { rows } = await db.query(
    `
      SELECT
        COALESCE(
          MAX(
            NULLIF(SUBSTRING(entry_number FROM '([0-9]+)$'), '')::int
          ),
          0
        ) + 1 AS next_sequence
      FROM journal_entries
      WHERE company_id = $1
        AND journal_id = $2
        AND fiscal_year_id = $3
        AND UPPER(entry_number) LIKE UPPER($4)
    `,
    [companyId, journal.id, fiscalYearId, `${prefix}%`],
  );

  return `${prefix}${String(Number(rows[0]?.next_sequence || 1)).padStart(5, "0")}`;
};

const insertEntryLines = async (db, entryId, lines) => {
  for (const line of lines) {
    await db.query(
      `
        INSERT INTO entry_lines (
          entry_id,
          account_id,
          auxiliary_account_id,
          label,
          debit,
          credit,
          due_date
        )
        VALUES ($1, $2, NULL, $3, $4, $5, NULL)
      `,
      [entryId, line.account_id, line.label, line.debit, line.credit],
    );
  }
};

const fetchExistingEntryKinds = async (db, companyId, declarationType, declarationId) => {
  const { rows } = await db.query(
    `
      SELECT entry_kind
      FROM fiscal_declaration_entries
      WHERE company_id = $1
        AND declaration_type = $2
        AND declaration_id = $3
    `,
    [companyId, declarationType, declarationId],
  );

  return new Set(rows.map((row) => row.entry_kind));
};

const fetchGn12Declaration = async (db, companyId, declarationId) => {
  const { rows } = await db.query(
    `
      SELECT
        id,
        company_id,
        fiscal_year_id,
        annee,
        statut,
        montant_effectif_a_payer,
        minimum_imposition,
        mode_paiement,
        paiement_integral_montant,
        paiement_integral_quittance,
        TO_CHAR(paiement_integral_date, 'YYYY-MM-DD') AS paiement_integral_date,
        tranche1_montant,
        tranche1_quittance,
        TO_CHAR(tranche1_date, 'YYYY-MM-DD') AS tranche1_date,
        tranche2_montant,
        tranche2_quittance,
        TO_CHAR(tranche2_date, 'YYYY-MM-DD') AS tranche2_date,
        tranche3_montant,
        tranche3_quittance,
        TO_CHAR(tranche3_date, 'YYYY-MM-DD') AS tranche3_date,
        TO_CHAR(date_depot, 'YYYY-MM-DD') AS date_depot,
        TO_CHAR(date_signature, 'YYYY-MM-DD') AS date_signature
      FROM ifu_declarations_previsionnelles
      WHERE company_id = $1
        AND id = $2
      LIMIT 1
    `,
    [companyId, declarationId],
  );

  if (!rows[0]) {
    throw new ApiError(404, "Declaration GN°12 introuvable.");
  }

  const row = rows[0];

  return {
    id: Number(row.id),
    company_id: Number(row.company_id),
    fiscal_year_id: Number(row.fiscal_year_id),
    annee: Number(row.annee),
    statut: row.statut,
    declaration_label: `GN°12 ${row.annee}`,
    period_label: `Exercice ${row.annee}`,
    droits_dus: Number(row.montant_effectif_a_payer || 0),
    date_limite: `${row.annee}-06-30`,
    date_depot: row.date_depot || null,
    date_reference: row.paiement_integral_date || row.date_depot || row.date_signature || TODAY,
    piece_ref:
      row.paiement_integral_quittance ||
      row.tranche1_quittance ||
      row.tranche2_quittance ||
      row.tranche3_quittance ||
      null,
    payload: {
      mode_paiement: row.mode_paiement || "integral",
      minimum_imposition: Number(row.minimum_imposition || 0),
      paiement_integral_montant: Number(row.paiement_integral_montant || 0),
      paiement_integral_quittance: row.paiement_integral_quittance || null,
      paiement_integral_date: row.paiement_integral_date || null,
      tranche1_montant: Number(row.tranche1_montant || 0),
      tranche1_quittance: row.tranche1_quittance || null,
      tranche1_date: row.tranche1_date || `${row.annee}-06-30`,
      tranche2_montant: Number(row.tranche2_montant || 0),
      tranche2_quittance: row.tranche2_quittance || null,
      tranche2_date: row.tranche2_date || `${row.annee}-09-15`,
      tranche3_montant: Number(row.tranche3_montant || 0),
      tranche3_quittance: row.tranche3_quittance || null,
      tranche3_date: row.tranche3_date || `${row.annee}-12-15`,
    },
  };
};

const fetchGn12BisDeclaration = async (db, companyId, declarationId) => {
  const { rows } = await db.query(
    `
      SELECT
        id,
        company_id,
        fiscal_year_id,
        annee,
        statut,
        total_ifu_complementaire,
        total_ifu_exercice,
        montant_acquitte,
        quittance_numero,
        TO_CHAR(quittance_date, 'YYYY-MM-DD') AS quittance_date,
        TO_CHAR(date_depot, 'YYYY-MM-DD') AS date_depot
      FROM ifu_declarations_definitives
      WHERE company_id = $1
        AND id = $2
      LIMIT 1
    `,
    [companyId, declarationId],
  );

  if (!rows[0]) {
    throw new ApiError(404, "Declaration GN°12 Bis introuvable.");
  }

  const row = rows[0];
  const droitsDus = Number(row.total_ifu_complementaire || row.total_ifu_exercice || 0);

  return {
    id: Number(row.id),
    company_id: Number(row.company_id),
    fiscal_year_id: Number(row.fiscal_year_id),
    annee: Number(row.annee),
    statut: row.statut,
    declaration_label: `GN°12 Bis ${row.annee}`,
    period_label: `Exercice ${row.annee}`,
    droits_dus: droitsDus,
    date_limite: `${Number(row.annee) + 1}-01-20`,
    date_depot: row.date_depot || null,
    date_reference: row.quittance_date || row.date_depot || TODAY,
    piece_ref: row.quittance_numero || null,
    payload: {
      montant_acquitte: Number(row.montant_acquitte || 0),
      quittance_numero: row.quittance_numero || null,
      quittance_date: row.quittance_date || null,
    },
  };
};

const fetchG50terDeclaration = async (db, companyId, declarationId) => {
  const { rows } = await db.query(
    `
      SELECT
        id,
        company_id,
        fiscal_year_id,
        annee,
        trimestre,
        statut,
        montant_irg,
        quittance_numero,
        TO_CHAR(quittance_date, 'YYYY-MM-DD') AS quittance_date,
        TO_CHAR(date_versement, 'YYYY-MM-DD') AS date_versement,
        TO_CHAR(delai_limite, 'YYYY-MM-DD') AS delai_limite
      FROM g50ter_declarations
      WHERE company_id = $1
        AND id = $2
      LIMIT 1
    `,
    [companyId, declarationId],
  );

  if (!rows[0]) {
    throw new ApiError(404, "Declaration G50ter introuvable.");
  }

  const row = rows[0];

  return {
    id: Number(row.id),
    company_id: Number(row.company_id),
    fiscal_year_id: Number(row.fiscal_year_id),
    annee: Number(row.annee),
    trimestre: Number(row.trimestre),
    statut: row.statut,
    declaration_label: `G50ter T${row.trimestre} ${row.annee}`,
    period_label: `T${row.trimestre} ${row.annee}`,
    droits_dus: Number(row.montant_irg || 0),
    date_limite: row.delai_limite || buildG50Deadline({
      annee: row.annee,
      periodicite: "trimestriel",
      trimestre: row.trimestre,
    }),
    date_depot: row.date_versement || row.quittance_date || null,
    date_reference: row.date_versement || row.quittance_date || TODAY,
    piece_ref: row.quittance_numero || null,
    payload: {
      montant_irg: Number(row.montant_irg || 0),
      quittance_date: row.quittance_date || null,
      date_versement: row.date_versement || null,
    },
  };
};

const fetchG50Declaration = async (db, companyId, declarationId) => {
  const { rows } = await db.query(
    `
      SELECT
        id,
        company_id,
        mois,
        trimestre,
        annee,
        periodicite,
        statut,
        total_cadre1,
        total_cadre2,
        total_cadre4,
        total_cadre5,
        total_cadre8,
        total_a_payer,
        quittance_numero,
        TO_CHAR(quittance_date, 'YYYY-MM-DD') AS quittance_date
      FROM g50_declarations
      WHERE company_id = $1
        AND id = $2
      LIMIT 1
    `,
    [companyId, declarationId],
  );

  if (!rows[0]) {
    throw new ApiError(404, "Declaration G50 introuvable.");
  }

  const row = rows[0];
  const periodLabel = buildG50PeriodLabel({
    annee: row.annee,
    periodicite: row.periodicite,
    mois: row.mois,
    trimestre: row.trimestre,
  });

  return {
    id: Number(row.id),
    company_id: Number(row.company_id),
    fiscal_year_id: null,
    annee: Number(row.annee),
    mois: row.mois ? Number(row.mois) : null,
    trimestre: row.trimestre ? Number(row.trimestre) : null,
    periodicite: row.periodicite,
    statut: row.statut,
    declaration_label: `G50 ${periodLabel}`,
    period_label: periodLabel,
    droits_dus: Number(row.total_a_payer || 0),
    date_limite: buildG50Deadline({
      annee: row.annee,
      periodicite: row.periodicite,
      mois: row.mois,
      trimestre: row.trimestre,
    }),
    date_depot: row.quittance_date || null,
    date_reference: row.quittance_date || TODAY,
    piece_ref: row.quittance_numero || null,
    payload: {
      total_cadre1: Number(row.total_cadre1 || 0),
      total_cadre2: Number(row.total_cadre2 || 0),
      total_cadre4: Number(row.total_cadre4 || 0),
      total_cadre5: Number(row.total_cadre5 || 0),
      total_cadre8: Number(row.total_cadre8 || 0),
      total_a_payer: Number(row.total_a_payer || 0),
      quittance_date: row.quittance_date || null,
    },
  };
};

const fetchDeclarationContext = async (db, companyId, declarationType, declarationId) => {
  switch (declarationType) {
    case "GN12":
      return fetchGn12Declaration(db, companyId, declarationId);
    case "GN12BIS":
      return fetchGn12BisDeclaration(db, companyId, declarationId);
    case "G50TER":
      return fetchG50terDeclaration(db, companyId, declarationId);
    case "G50":
      return fetchG50Declaration(db, companyId, declarationId);
    default:
      throw new ApiError(400, "Type de declaration fiscal invalide.");
  }
};

const buildPenaltyBreakdown = ({
  declarationType,
  droitsDus,
  dateLimite,
  dateDepot,
  miseEnDemeure = false,
  dateMiseEnDemeure = null,
  nbObligations = 1,
  isNeant = false,
}) => {
  const lateDays = Math.max(0, diffDays(dateLimite, dateDepot));
  const monthsLate = lateDays > 30 ? Math.ceil((lateDays - 30) / 30) : 0;
  const isLate = lateDays > 0;
  const isNeantObligation = declarationType === "G50" && (isNeant || droitsDus <= 0);
  const baseAmount = roundAmount(droitsDus);

  let latePenalty = isLate && baseAmount > 0 && !isNeantObligation
    ? roundAmount(baseAmount * 0.1)
    : 0;
  let medPenalty = miseEnDemeure && baseAmount > 0 && !isNeantObligation
    ? roundAmount(Math.max(baseAmount * 0.25 - latePenalty, 0))
    : 0;
  let monthlyPenalty = monthsLate > 0 && baseAmount > 0 && !isNeantObligation
    ? roundAmount(baseAmount * 0.03 * monthsLate)
    : 0;
  let neantPenalty = isNeantObligation && isLate ? roundAmount(500 * nbObligations) : 0;

  const rawPenaltyTotal = roundAmount(
    latePenalty + medPenalty + monthlyPenalty + neantPenalty,
  );
  const legalCap = baseAmount > 0 ? roundAmount(baseAmount * 0.25) : 0;
  const totalPenalty = neantPenalty
    ? neantPenalty
    : roundAmount(baseAmount > 0 ? Math.min(rawPenaltyTotal, legalCap) : 0);

  const earlyScenarioPenalty = isNeantObligation
    ? (isLate ? 500 : 0)
    : (lateDays > 0 ? roundAmount(baseAmount * 0.1) : 0);

  const rows = [
    {
      penalty_type: "retard_10pct",
      label: "Penalite de retard de depot",
      base_amount: baseAmount,
      penalty_rate: 10,
      penalty_amount: latePenalty,
      applicable: isLate && baseAmount > 0,
      note: "Applicable en cas de depot apres l'echeance.",
    },
    {
      penalty_type: "mise_en_demeure_25pct",
      label: "Majoration apres mise en demeure",
      base_amount: baseAmount,
      penalty_rate: 25,
      penalty_amount: medPenalty,
      applicable: Boolean(miseEnDemeure && baseAmount > 0),
      note: dateMiseEnDemeure
        ? `Mise en demeure constatee le ${dateMiseEnDemeure}.`
        : "Majoration complementaire jusqu'au seuil de 25%.",
    },
    {
      penalty_type: "astreinte_3pct",
      label: "Astreinte mensuelle",
      base_amount: baseAmount,
      penalty_rate: 3,
      penalty_amount: monthlyPenalty,
      applicable: Boolean(monthsLate > 0 && baseAmount > 0),
      note: monthsLate > 0 ? `${monthsLate} mois de retard comptabilise(s).` : "",
    },
    {
      penalty_type: "neant_500da",
      label: "Penalite G50 neant en retard",
      base_amount: roundAmount(nbObligations),
      penalty_rate: 500,
      penalty_amount: neantPenalty,
      applicable: Boolean(neantPenalty > 0),
      note: `${nbObligations} obligation(s) a 500 DA.`,
    },
  ];

  return {
    declaration_type: declarationType,
    declaration_type_label: getDeclarationTypeLabel(declarationType),
    droits_dus: baseAmount,
    date_limite: dateLimite,
    date_depot: dateDepot,
    mise_en_demeure: Boolean(miseEnDemeure),
    date_mise_en_demeure: dateMiseEnDemeure,
    nb_obligations: nbObligations,
    is_neant: isNeantObligation,
    late_days: lateDays,
    months_late: monthsLate,
    raw_penalty_total: rawPenaltyTotal,
    penalty_cap: legalCap,
    total_penalty: totalPenalty,
    total_to_regularize: roundAmount(baseAmount + totalPenalty),
    savings_if_within_7_days: roundAmount(Math.max(totalPenalty - earlyScenarioPenalty, 0)),
    rows,
  };
};

const buildPenaltyPdfReport = ({ company, declaration, calculation, generatedBy }) => ({
  title: "Calcul des pénalités fiscales",
  company,
  generated_at: getTodayIsoDate(),
  generated_by: generatedBy,
  parameters: [
    {
      label: "Declaration",
      value: declaration
        ? `${declaration.declaration_label} - ${declaration.period_label}`
        : calculation.declaration_type_label,
    },
    {
      label: "Date limite",
      value: calculation.date_limite,
    },
    {
      label: "Date de depot",
      value: calculation.date_depot,
    },
  ],
  summary: [
    { label: "Droits dus", value: calculation.droits_dus, type: "amount" },
    { label: "Penalite totale", value: calculation.total_penalty, type: "amount" },
    { label: "Total a regulariser", value: calculation.total_to_regularize, type: "amount" },
    { label: "Retard", value: `${calculation.late_days} jours / ${calculation.months_late} mois` },
  ],
  warnings: calculation.total_penalty >= calculation.penalty_cap && calculation.penalty_cap > 0
    ? [
        {
          tone: "warning",
          message: "Le plafond legal de 25% des droits dus est atteint.",
        },
      ]
    : [],
  sections: [
    {
      title: "Detail du calcul",
      columns: [
        { key: "label", label: "Type de penalite" },
        { key: "base_amount", label: "Base", type: "amount" },
        { key: "penalty_rate", label: "Taux" },
        { key: "penalty_amount", label: "Montant", type: "amount" },
        { key: "applicable_label", label: "Applicable" },
      ],
      rows: calculation.rows.map((row) => ({
        ...row,
        penalty_rate:
          row.penalty_type === "neant_500da" ? "500 DA" : `${row.penalty_rate}%`,
        applicable_label: row.applicable ? "Oui" : "Non",
      })),
      summary: [
        {
          label: "Economie si depot dans les 7 jours",
          value: calculation.savings_if_within_7_days,
          type: "amount",
        },
      ],
    },
    {
      title: "Reference legale",
      columns: [
        { key: "infraction", label: "Infraction" },
        { key: "penalty", label: "Penalite" },
        { key: "article", label: "Article" },
      ],
      rows: PENALTY_REFERENCE_ROWS,
    },
  ],
});

const buildEntryPreview = ({
  entryKind,
  label,
  amount,
  entryDate,
  pieceRef,
  debitAccount,
  creditAccount,
  journal,
}) => ({
  entry_kind: entryKind,
  entry_kind_label: getEntryKindLabel(entryKind),
  label,
  amount: roundAmount(amount),
  formatted_amount: formatDzdAmount(amount),
  entry_date: entryDate,
  piece_ref: pieceRef || null,
  journal: {
    id: Number(journal.id),
    code: journal.code,
    label: journal.label,
  },
  lines: [
    {
      side: "debit",
      account_id: Number(debitAccount.id),
      account_code: debitAccount.code,
      account_label: debitAccount.label,
      debit: roundAmount(amount),
      credit: 0,
      formatted_amount: formatDzdAmount(amount),
      label,
    },
    {
      side: "credit",
      account_id: Number(creditAccount.id),
      account_code: creditAccount.code,
      account_label: creditAccount.label,
      debit: 0,
      credit: roundAmount(amount),
      formatted_amount: formatDzdAmount(amount),
      label,
    },
  ],
});

const buildDeclarationEntryPreviews = async ({
  db,
  companyId,
  declarationType,
  declaration,
  bankAccount,
  penaltyPayload = null,
}) => {
  const journal = await fetchJournalByType(db, companyId, "bank");
  const bankCreditAccount = await resolveBankCreditAccount(db, companyId, bankAccount);
  const previews = [];

  const addPreview = async ({ entryKind, label, amount, entryDate, pieceRef, debitAccount }) => {
    const normalizedAmount = roundAmount(amount);

    if (normalizedAmount <= 0) {
      return;
    }

    const accountingContext = await resolveAccountingContext(db, companyId, entryDate);
    previews.push({
      ...buildEntryPreview({
        entryKind,
        label,
        amount: normalizedAmount,
        entryDate,
        pieceRef,
        debitAccount,
        creditAccount: bankCreditAccount,
        journal,
      }),
      accounting_context: accountingContext,
    });
  };

  if (penaltyPayload) {
    const debitAccount = await fetchPostingAccount(db, companyId, {
      exactCode: "671000",
      fallbackPrefixes: ["671", "67"],
      label: "Compte penalites fiscales",
    });
    await addPreview({
      entryKind: "penalty",
      label:
        penaltyPayload.label ||
        `Penalite retard ${getDeclarationTypeLabel(declarationType)} ${declaration.period_label}`,
      amount: Number(penaltyPayload.amount || 0),
      entryDate: penaltyPayload.entry_date || declaration.date_reference || TODAY,
      pieceRef: penaltyPayload.piece_ref || declaration.piece_ref || null,
      debitAccount,
    });

    return previews;
  }

  if (declaration.statut === "brouillon") {
    throw new ApiError(
      409,
      "La declaration doit etre deposee ou validee avant generation des ecritures.",
    );
  }

  if (declarationType === "GN12") {
    const debitAccount = await fetchPostingAccount(db, companyId, {
      exactCode: "447000",
      fallbackPrefixes: ["447"],
      label: "Compte IFU a payer",
    });
    const paymentMode = declaration.payload.mode_paiement || "integral";

    if (paymentMode === "fractionne") {
      const installments = [
        {
          entryKind: "gn12_tranche1",
          tranche: 1,
          amount: declaration.payload.tranche1_montant,
          entryDate: declaration.payload.tranche1_date || `${declaration.annee}-06-30`,
          pieceRef: declaration.payload.tranche1_quittance || declaration.piece_ref,
        },
        {
          entryKind: "gn12_tranche2",
          tranche: 2,
          amount: declaration.payload.tranche2_montant,
          entryDate: declaration.payload.tranche2_date || `${declaration.annee}-09-15`,
          pieceRef: declaration.payload.tranche2_quittance || declaration.piece_ref,
        },
        {
          entryKind: "gn12_tranche3",
          tranche: 3,
          amount: declaration.payload.tranche3_montant,
          entryDate: declaration.payload.tranche3_date || `${declaration.annee}-12-15`,
          pieceRef: declaration.payload.tranche3_quittance || declaration.piece_ref,
        },
      ];

      for (const installment of installments) {
        await addPreview({
          entryKind: installment.entryKind,
          label: `IFU previsionnel ${declaration.annee} GN°12 tranche ${installment.tranche}`,
          amount: installment.amount,
          entryDate: installment.entryDate,
          pieceRef: installment.pieceRef,
          debitAccount,
        });
      }
    } else {
      const entryKind = paymentMode === "minimum" ? "gn12_minimum" : "gn12_integral";
      const amount =
        paymentMode === "minimum"
          ? declaration.payload.minimum_imposition
          : declaration.payload.paiement_integral_montant || declaration.droits_dus;
      const pieceRef =
        declaration.payload.paiement_integral_quittance || declaration.piece_ref || null;

      await addPreview({
        entryKind,
        label:
          paymentMode === "minimum"
            ? `Minimum d'imposition IFU ${declaration.annee} GN°12`
            : `IFU previsionnel ${declaration.annee} GN°12`,
        amount,
        entryDate:
          declaration.payload.paiement_integral_date ||
          declaration.date_depot ||
          declaration.date_reference ||
          TODAY,
        pieceRef,
        debitAccount,
      });
    }

    return previews;
  }

  if (declarationType === "G50TER") {
    const debitAccount = await fetchPostingAccount(db, companyId, {
      exactCode: "422000",
      fallbackPrefixes: ["422"],
      label: "Compte IRG salaires",
    });

    await addPreview({
      entryKind: "g50ter_irg",
      label: `IRG Salaires T${declaration.trimestre} ${declaration.annee} G50ter`,
      amount: declaration.payload.montant_irg,
      entryDate:
        declaration.payload.date_versement ||
        declaration.payload.quittance_date ||
        declaration.date_reference ||
        TODAY,
      pieceRef: declaration.piece_ref,
      debitAccount,
    });

    return previews;
  }

  if (declarationType === "G50") {
    const tvaAccount = await fetchPostingAccount(db, companyId, {
      exactCode: "445500",
      fallbackPrefixes: ["4455", "445"],
      label: "Compte TVA a decaisser",
    });
    const ibsAccount = await fetchPostingAccount(db, companyId, {
      exactCode: "444000",
      fallbackPrefixes: ["444"],
      label: "Compte IBS a payer",
    });
    const periodLabel = declaration.period_label;
    const ibsAmount = roundAmount(
      Number(declaration.payload.total_cadre1 || 0) + Number(declaration.payload.total_cadre2 || 0),
    );

    await addPreview({
      entryKind: "g50_tva",
      label: `Paiement TVA ${periodLabel} G50`,
      amount: declaration.payload.total_cadre8,
      entryDate: declaration.payload.quittance_date || declaration.date_reference || TODAY,
      pieceRef: declaration.piece_ref,
      debitAccount: tvaAccount,
    });

    await addPreview({
      entryKind: "g50_ibs",
      label: `Acompte IBS ${periodLabel} G50`,
      amount: ibsAmount,
      entryDate: declaration.payload.quittance_date || declaration.date_reference || TODAY,
      pieceRef: declaration.piece_ref,
      debitAccount: ibsAccount,
    });

    return previews;
  }

  throw new ApiError(
    400,
    "La generation automatique d'ecritures n'est pas disponible pour ce type de declaration.",
  );
};

const filterOutExistingEntryKinds = (previews, existingEntryKinds) =>
  previews.filter((item) => !existingEntryKinds.has(item.entry_kind));

const createJournalEntryFromPreview = async ({
  db,
  companyId,
  declarationType,
  declaration,
  preview,
  userId,
}) => {
  const entryNumber = await buildEntryNumber(db, {
    companyId,
    journal: preview.journal,
    fiscalYearId: preview.accounting_context.fiscal_year_id,
    fiscalYearLabel: preview.accounting_context.fiscal_year_label,
  });
  const entryInsert = await db.query(
    `
      INSERT INTO journal_entries (
        company_id,
        journal_id,
        fiscal_year_id,
        period_id,
        entry_number,
        entry_date,
        label,
        status,
        piece_ref,
        created_by,
        created_at,
        updated_at
      )
      VALUES (
        $1,
        $2,
        $3,
        $4,
        $5,
        $6::date,
        $7,
        'draft',
        $8,
        $9,
        CURRENT_TIMESTAMP,
        CURRENT_TIMESTAMP
      )
      RETURNING id, entry_number, TO_CHAR(entry_date, 'YYYY-MM-DD') AS entry_date, label, status
    `,
    [
      companyId,
      preview.journal.id,
      preview.accounting_context.fiscal_year_id,
      preview.accounting_context.period_id,
      entryNumber,
      preview.entry_date,
      preview.label,
      preview.piece_ref,
      userId,
    ],
  );

  const entry = entryInsert.rows[0];

  await insertEntryLines(
    db,
    Number(entry.id),
    preview.lines.map((line) => ({
      account_id: line.account_id,
      label: line.label,
      debit: line.debit,
      credit: line.credit,
    })),
  );

  await db.query(
    `
      INSERT INTO fiscal_declaration_entries (
        company_id,
        declaration_type,
        declaration_id,
        declaration_year,
        entry_kind,
        declaration_label,
        period_label,
        amount,
        journal_entry_id,
        generated_by,
        metadata
      )
      VALUES (
        $1,
        $2,
        $3,
        $4,
        $5,
        $6,
        $7,
        $8,
        $9,
        $10,
        $11::jsonb
      )
    `,
    [
      companyId,
      declarationType,
      declaration.id,
      declaration.annee || null,
      preview.entry_kind,
      declaration.declaration_label,
      declaration.period_label,
      preview.amount,
      entry.id,
      userId,
      JSON.stringify({
        piece_ref: preview.piece_ref || null,
        journal_code: preview.journal.code,
        label: preview.label,
      }),
    ],
  );

  return {
    declaration_type: declarationType,
    declaration_label: declaration.declaration_label,
    period_label: declaration.period_label,
    entry_kind: preview.entry_kind,
    entry_kind_label: preview.entry_kind_label,
    amount: preview.amount,
    formatted_amount: preview.formatted_amount,
    journal_entry_id: Number(entry.id),
    entry_number: entry.entry_number,
    entry_date: entry.entry_date,
    journal: preview.journal,
    status: entry.status,
  };
};

const listDeclarationsForType = async (db, companyId, declarationType, annee) => {
  switch (declarationType) {
    case "GN12": {
      const params = [companyId];
      const whereYear = annee ? "AND annee = $2" : "";
      if (annee) {
        params.push(annee);
      }
      const { rows } = await db.query(
        `
          SELECT id, annee, statut
          FROM ifu_declarations_previsionnelles
          WHERE company_id = $1
            ${whereYear}
          ORDER BY annee DESC
        `,
        params,
      );
      return rows.map((row) => ({
        id: Number(row.id),
        declaration_type: "GN12",
        label: `GN°12 ${row.annee}`,
        period_label: `Exercice ${row.annee}`,
        annee: Number(row.annee),
        statut: row.statut,
      }));
    }
    case "GN12BIS": {
      const params = [companyId];
      const whereYear = annee ? "AND annee = $2" : "";
      if (annee) {
        params.push(annee);
      }
      const { rows } = await db.query(
        `
          SELECT id, annee, statut
          FROM ifu_declarations_definitives
          WHERE company_id = $1
            ${whereYear}
          ORDER BY annee DESC
        `,
        params,
      );
      return rows.map((row) => ({
        id: Number(row.id),
        declaration_type: "GN12BIS",
        label: `GN°12 Bis ${row.annee}`,
        period_label: `Exercice ${row.annee}`,
        annee: Number(row.annee),
        statut: row.statut,
      }));
    }
    case "G50TER": {
      const params = [companyId];
      const whereYear = annee ? "AND annee = $2" : "";
      if (annee) {
        params.push(annee);
      }
      const { rows } = await db.query(
        `
          SELECT id, annee, trimestre, statut
          FROM g50ter_declarations
          WHERE company_id = $1
            ${whereYear}
          ORDER BY annee DESC, trimestre DESC
        `,
        params,
      );
      return rows.map((row) => ({
        id: Number(row.id),
        declaration_type: "G50TER",
        label: `G50ter T${row.trimestre} ${row.annee}`,
        period_label: `T${row.trimestre} ${row.annee}`,
        annee: Number(row.annee),
        statut: row.statut,
      }));
    }
    case "G50": {
      const params = [companyId];
      const whereYear = annee ? "AND annee = $2" : "";
      if (annee) {
        params.push(annee);
      }
      const { rows } = await db.query(
        `
          SELECT id, annee, mois, trimestre, periodicite, statut
          FROM g50_declarations
          WHERE company_id = $1
            ${whereYear}
          ORDER BY annee DESC, COALESCE(mois, trimestre) DESC
        `,
        params,
      );
      return rows.map((row) => ({
        id: Number(row.id),
        declaration_type: "G50",
        label: `G50 ${buildG50PeriodLabel(row)}`,
        period_label: buildG50PeriodLabel(row),
        annee: Number(row.annee),
        statut: row.statut,
      }));
    }
    default:
      throw new ApiError(400, "Type de declaration fiscal invalide.");
  }
};

export const fiscalPenaltyController = {
  calculate: async (req, res) => {
    const companyId = getCompanyId(req);
    const declarationType = cleanString(req.body.declaration_type).toUpperCase();
    const declarationId = parseInteger(
      req.body.declaration_id,
      "Identifiant de declaration",
      0,
    );
    const declaration =
      declarationId > 0
        ? await fetchDeclarationContext(pool, companyId, declarationType, declarationId)
        : null;
    const droitsDus = parseDecimalField(
      req.body.droits_dus,
      "Les droits dus",
      declaration?.droits_dus || 0,
    );
    const dateLimite =
      parseOptionalIsoDate(req.body.date_limite, "La date limite reglementaire") ||
      declaration?.date_limite;
    if (!dateLimite) {
      throw new ApiError(400, "La date limite reglementaire est obligatoire.");
    }
    const dateDepot =
      parseOptionalIsoDate(req.body.date_depot, "La date de depot effectif") ||
      declaration?.date_depot ||
      getTodayIsoDate();
    const dateMiseEnDemeure = parseOptionalIsoDate(
      req.body.date_mise_en_demeure,
      "La date de mise en demeure",
    );
    const miseEnDemeure = Boolean(req.body.mise_en_demeure || dateMiseEnDemeure);
    const nbObligations = parseInteger(req.body.nb_obligations, "Le nombre d'obligations", 1);
    const calculation = buildPenaltyBreakdown({
      declarationType,
      droitsDus,
      dateLimite,
      dateDepot,
      miseEnDemeure,
      dateMiseEnDemeure,
      nbObligations,
      isNeant: Boolean(req.body.is_neant),
    });

    if (declarationId > 0) {
      const client = await pool.connect();

      try {
        await client.query("BEGIN");
        await client.query(
          `
            DELETE FROM fiscal_penalties
            WHERE company_id = $1
              AND declaration_type = $2
              AND declaration_id = $3
          `,
          [companyId, declarationType, declarationId],
        );

        for (const row of calculation.rows.filter((item) => item.applicable && item.penalty_amount > 0)) {
          await client.query(
            `
              INSERT INTO fiscal_penalties (
                company_id,
                declaration_id,
                declaration_type,
                penalty_type,
                base_amount,
                penalty_rate,
                penalty_amount,
                months_late,
                calculated_at,
                created_at
              )
              VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
            `,
            [
              companyId,
              declarationId,
              declarationType,
              row.penalty_type,
              row.base_amount,
              row.penalty_rate,
              row.penalty_amount,
              calculation.months_late,
            ],
          );
        }

        await logAudit(client, {
          companyId,
          userId: req.user?.sub || req.user?.id || null,
          action: "UPDATE",
          entityType: "fiscal_penalty",
          entityId: declarationId,
          oldData: null,
          newData: {
            declaration_type: declarationType,
            declaration_id: declarationId,
            calculation,
          },
          ipAddress: req.ip,
        });

        await client.query("COMMIT");
      } catch (error) {
        await client.query("ROLLBACK").catch(() => {});
        throw error;
      } finally {
        client.release();
      }
    }

    res.json({
      item: {
        ...calculation,
        declaration_id: declarationId || null,
        declaration_label: declaration?.declaration_label || null,
        period_label: declaration?.period_label || null,
      },
    });
  },

  exportPdf: async (req, res) => {
    const companyId = getCompanyId(req);
    const declarationType = cleanString(req.body.declaration_type).toUpperCase();
    const declarationId = parseInteger(
      req.body.declaration_id,
      "Identifiant de declaration",
      0,
    );
    const declaration =
      declarationId > 0
        ? await fetchDeclarationContext(pool, companyId, declarationType, declarationId)
        : null;
    const rightsDue = parseDecimalField(
      req.body.droits_dus,
      "Les droits dus",
      declaration?.droits_dus || 0,
    );
    const dateLimite =
      parseOptionalIsoDate(req.body.date_limite, "La date limite reglementaire") ||
      declaration?.date_limite;
    if (!dateLimite) {
      throw new ApiError(400, "La date limite reglementaire est obligatoire.");
    }
    const dateDepot =
      parseOptionalIsoDate(req.body.date_depot, "La date de depot effectif") ||
      declaration?.date_depot ||
      getTodayIsoDate();
    const dateMiseEnDemeure = parseOptionalIsoDate(
      req.body.date_mise_en_demeure,
      "La date de mise en demeure",
    );
    const calculation = buildPenaltyBreakdown({
      declarationType,
      droitsDus: rightsDue,
      dateLimite,
      dateDepot,
      miseEnDemeure: Boolean(req.body.mise_en_demeure || dateMiseEnDemeure),
      dateMiseEnDemeure,
      nbObligations: parseInteger(req.body.nb_obligations, "Le nombre d'obligations", 1),
      isNeant: Boolean(req.body.is_neant),
    });
    const company = await fetchCompany(pool, companyId);
    const buffer = await exportReportAsPdf(
      buildPenaltyPdfReport({
        company,
        declaration,
        calculation,
        generatedBy: req.user?.fullName || req.user?.email || "Systeme",
      }),
    );

    res.setHeader("Content-Type", "application/pdf");
    res.setHeader("Content-Disposition", 'inline; filename="penalites-fiscales.pdf"');
    res.send(Buffer.from(buffer));
  },

  generateEntries: async (req, res) => {
    const companyId = getCompanyId(req);
    const declarationType = cleanString(req.body.declaration_type).toUpperCase();
    const declarationId = parseInteger(
      req.params.id,
      "Identifiant de declaration",
    );
    const bankAccountId = parseInteger(
      req.body.bank_account_id,
      "Le compte bancaire",
      0,
    );
    const previewOnly = Boolean(req.body.preview);

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

    const declaration = await fetchDeclarationContext(
      pool,
      companyId,
      declarationType,
      declarationId,
    );
    const bankAccount = await fetchBankAccountById(pool, companyId, bankAccountId);
    const penaltyAmount = parseDecimalField(
      req.body.penalty_amount,
      "Le montant de penalite",
      0,
    );
    const penaltyPayload =
      penaltyAmount > 0
        ? {
            amount: penaltyAmount,
            entry_date:
              parseOptionalIsoDate(req.body.penalty_date, "La date de penalite") ||
              declaration.date_reference ||
              TODAY,
            label:
              cleanString(req.body.penalty_label) ||
              `Penalite retard ${getDeclarationTypeLabel(declarationType)} ${declaration.period_label}`,
            piece_ref: cleanString(req.body.penalty_piece_ref) || declaration.piece_ref || null,
          }
        : null;

    const existingEntryKinds = await fetchExistingEntryKinds(
      pool,
      companyId,
      declarationType,
      declarationId,
    );

    const rawPreviews = await buildDeclarationEntryPreviews({
      db: pool,
      companyId,
      declarationType,
      declaration,
      bankAccount,
      penaltyPayload,
    });
    const previews = filterOutExistingEntryKinds(rawPreviews, existingEntryKinds);

    if (previewOnly) {
      res.json({
        item: {
          declaration_type: declarationType,
          declaration_id: declaration.id,
          declaration_label: declaration.declaration_label,
          period_label: declaration.period_label,
          bank_account: {
            id: bankAccount.id,
            bank_name: bankAccount.bank_name,
            account_number: bankAccount.account_number,
            accounting_code: bankAccount.accounting_code || null,
          },
          entries: previews.map((item) => ({
            entry_kind: item.entry_kind,
            entry_kind_label: item.entry_kind_label,
            label: item.label,
            amount: item.amount,
            formatted_amount: item.formatted_amount,
            entry_date: item.entry_date,
            journal: item.journal,
            piece_ref: item.piece_ref,
            lines: item.lines,
          })),
          existing_entry_kinds: [...existingEntryKinds],
        },
      });
      return;
    }

    if (!previews.length) {
      throw new ApiError(
        409,
        "Aucune nouvelle ecriture n'est a generer pour cette declaration.",
      );
    }

    const client = await pool.connect();

    try {
      await client.query("BEGIN");
      const createdItems = [];

      for (const preview of previews) {
        const createdItem = await createJournalEntryFromPreview({
          db: client,
          companyId,
          declarationType,
          declaration,
          preview,
          userId: req.user?.sub || req.user?.id || null,
        });
        createdItems.push(createdItem);

        await logAudit(client, {
          companyId,
          userId: req.user?.sub || req.user?.id || null,
          action: "CREATE",
          entityType: "fiscal_declaration_entry",
          entityId: createdItem.journal_entry_id,
          oldData: null,
          newData: createdItem,
          ipAddress: req.ip,
        });
      }

      await client.query("COMMIT");

      res.json({
        message: "Ecritures fiscales generees en brouillon.",
        items: createdItems,
      });
    } catch (error) {
      await client.query("ROLLBACK").catch(() => {});
      throw error;
    } finally {
      client.release();
    }
  },

  listEntriesLink: async (req, res) => {
    const companyId = getCompanyId(req, req.query);
    const annee = parseInteger(req.query.annee, "L'annee", 0);
    const declarationType = cleanString(req.query.declaration_type).toUpperCase();
    const declarationId = parseInteger(req.query.declaration_id, "Identifiant declaration", 0);
    const conditions = ["fde.company_id = $1"];
    const params = [companyId];

    if (annee) {
      params.push(annee);
      conditions.push(`fde.declaration_year = $${params.length}`);
    }

    if (declarationType) {
      params.push(declarationType);
      conditions.push(`fde.declaration_type = $${params.length}`);
    }

    if (declarationId) {
      params.push(declarationId);
      conditions.push(`fde.declaration_id = $${params.length}`);
    }

    const { rows } = await pool.query(
      `
        SELECT
          fde.id,
          fde.declaration_type,
          fde.declaration_id,
          fde.declaration_year,
          fde.entry_kind,
          fde.declaration_label,
          fde.period_label,
          fde.amount,
          fde.journal_entry_id,
          fde.metadata,
          TO_CHAR(fde.created_at, 'YYYY-MM-DD') AS created_at,
          je.entry_number,
          TO_CHAR(je.entry_date, 'YYYY-MM-DD') AS entry_date,
          je.status AS entry_status,
          j.code AS journal_code,
          j.label AS journal_label
        FROM fiscal_declaration_entries fde
        JOIN journal_entries je
          ON je.id = fde.journal_entry_id
        LEFT JOIN journals j
          ON j.id = je.journal_id
        WHERE ${conditions.join(" AND ")}
        ORDER BY je.entry_date DESC, fde.created_at DESC, fde.id DESC
      `,
      params,
    );

    res.json({
      items: rows.map((row) => ({
        id: Number(row.id),
        declaration_type: row.declaration_type,
        declaration_type_label: getDeclarationTypeLabel(row.declaration_type),
        declaration_id: Number(row.declaration_id),
        declaration_year: row.declaration_year ? Number(row.declaration_year) : null,
        declaration_label: row.declaration_label,
        period_label: row.period_label || "",
        entry_kind: row.entry_kind,
        entry_kind_label: getEntryKindLabel(row.entry_kind),
        amount: Number(row.amount || 0),
        formatted_amount: formatDzdAmount(row.amount || 0),
        journal_entry_id: Number(row.journal_entry_id),
        entry_number: row.entry_number,
        entry_date: row.entry_date || null,
        journal_code: row.journal_code || "",
        journal_label: row.journal_label || "",
        entry_status: row.entry_status,
        entry_status_label:
          {
            draft: "Brouillon",
            validated: "Validee",
            cancelled: "Annulee",
          }[row.entry_status] || row.entry_status,
        created_at: row.created_at || null,
        metadata: row.metadata || {},
      })),
    });
  },

  listDeclarationsForType: async (req, res) => {
    const companyId = getCompanyId(req, req.query);
    const declarationType = cleanString(req.query.declaration_type).toUpperCase();
    const annee = parseInteger(req.query.annee, "L'annee", 0);
    const items = await listDeclarationsForType(pool, companyId, declarationType, annee);

    res.json({ items });
  },
};
