import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import { logAudit } from "../utils/auditLog.js";
import { markFiscalDeadlineAsFiled } from "../utils/fiscalDeadlines.js";
import { formatDzdAmount } from "../utils/formatters.js";
import {
  buildG50PeriodMeta,
  computeCadre1Totals,
  computeCadre2Totals,
  computeCadre3Totals,
  computeCadre4Totals,
  computeCadre5Totals,
  computeCadre6Totals,
  computeCadre7Totals,
  computeCadre8Totals,
  deriveFiscalDeclarationYear,
  ensureG50Declarations,
  getG50DateRange,
  getCurrentG50Period,
  getPreviousG50Period,
} from "../utils/fiscalG50.js";
import { buildG50PdfBuffer } from "../utils/g50Pdf.js";

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

const STATUS_LABELS = {
  brouillon: "Brouillon",
  deposee: "Deposee",
  validee: "Validee",
};

const PERIODICITE_LABELS = {
  mensuel: "Mensuel",
  trimestriel: "Trimestriel",
};

const getCompanyId = (req, source = req.query) => {
  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 parsedDate = new Date(`${normalized}T00:00:00Z`);
  const [year, month, day] = normalized.split("-").map(Number);

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

  return normalized;
};

const toNullableText = (value) => {
  const normalized = cleanString(value);
  return normalized || null;
};

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

  if (typeof value === "string") {
    try {
      const parsed = JSON.parse(value);
      return parsed && typeof parsed === "object" ? parsed : fallback;
    } catch {
      throw new ApiError(400, "Un detail G50 JSON est invalide.");
    }
  }

  if (typeof value === "object") {
    return value;
  }

  return fallback;
};

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

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

  return rows[0];
};

const fetchFiscalProfile = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT
        id,
        nif,
        nin,
        article_imposition,
        forme_juridique,
        code_activite,
        activity_description,
        g50_periodicite,
        obligation_g50_tva,
        obligation_g50_tls,
        obligation_g50_ibs_icbs,
        obligation_g50ter
      FROM fiscal_profiles
      WHERE company_id = $1
      LIMIT 1
    `,
    [companyId],
  );

  return rows[0] || null;
};

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
      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,
    annee: deriveFiscalDeclarationYear(row),
  }));
};

const resolveFiscalYearContext = async (db, companyId, options = {}) => {
  const fiscalYears = await fetchFiscalYears(db, companyId);
  const fiscalYearId = Number(options.fiscal_year_id || options.fiscalYearId || 0);
  const requestedYear = Number(options.annee || 0);

  if (!fiscalYears.length) {
    throw new ApiError(404, "Aucun exercice fiscal disponible.");
  }

  if (fiscalYearId) {
    const match = fiscalYears.find((item) => item.id === fiscalYearId);

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

    return match;
  }

  if (requestedYear) {
    const match = fiscalYears.find((item) => Number(item.annee) === requestedYear);

    if (match) {
      return match;
    }
  }

  return fiscalYears.find((item) => item.is_current) || fiscalYears[fiscalYears.length - 1];
};

const G50_SELECT = `
  SELECT
    id,
    company_id,
    mois,
    trimestre,
    annee,
    periodicite,
    nif,
    nin,
    article_imposition,
    forme_juridique,
    code_activite,
    cadre1_detail,
    e1m10_acompte_ibs,
    solde_liquidation_ibs,
    acompte_icbs,
    solde_liquidation_icbs,
    total_cadre1,
    cadre2_detail,
    e1m30_ibs_entreprises_etrangeres,
    taux_e1m30,
    e1m40_ibs_autres_retenues,
    taux_e1m40,
    total_cadre2,
    cadre3_detail,
    acompte_irg_bic_bnc,
    solde_liquidation_irg,
    total_cadre3,
    irg_retenues_detail,
    total_cadre4,
    tls_detail,
    total_cadre5,
    droits_timbre_detail,
    total_cadre6,
    autres_taxes_detail,
    total_cadre7,
    tva_ca_imposable_detail,
    tva_deductions_detail,
    tva_total_droits,
    tva_total_deductions,
    tva_a_payer,
    tva_precompte_reporter,
    total_cadre8,
    total_a_payer,
    mode_paiement,
    cheque_bancaire_no,
    cheque_bancaire_agence,
    cheque_postal_no,
    montant_numeraire,
    quittance_numero,
    TO_CHAR(quittance_date, 'YYYY-MM-DD') AS quittance_date,
    lieu_declaration,
    TO_CHAR(date_signature, 'YYYY-MM-DD') AS date_signature,
    statut,
    observations,
    submitted_at,
    created_by,
    created_at,
    updated_at
  FROM g50_declarations
`;

const normalizeG50Row = (row) => {
  const meta = buildG50PeriodMeta({
    periodicite: row.periodicite,
    annee: row.annee,
    mois: row.mois,
    trimestre: row.trimestre,
  });

  return {
    id: Number(row.id),
    company_id: Number(row.company_id),
    mois: row.mois ? Number(row.mois) : null,
    trimestre: row.trimestre ? Number(row.trimestre) : null,
    annee: Number(row.annee),
    periodicite: row.periodicite,
    periodicite_label: PERIODICITE_LABELS[row.periodicite] || row.periodicite,
    period_label: meta.period_label,
    deadline_date: meta.deadline_date,
    deadline_label: meta.deadline_label,
    nif: row.nif || "",
    nin: row.nin || "",
    article_imposition: row.article_imposition || "",
    forme_juridique: row.forme_juridique || "",
    code_activite: row.code_activite || "",
    cadre1_detail: row.cadre1_detail || {},
    e1m10_acompte_ibs: Number(row.e1m10_acompte_ibs || 0),
    solde_liquidation_ibs: Number(row.solde_liquidation_ibs || 0),
    acompte_icbs: Number(row.acompte_icbs || 0),
    solde_liquidation_icbs: Number(row.solde_liquidation_icbs || 0),
    total_cadre1: Number(row.total_cadre1 || 0),
    cadre2_detail: row.cadre2_detail || {},
    e1m30_ibs_entreprises_etrangeres: Number(row.e1m30_ibs_entreprises_etrangeres || 0),
    taux_e1m30: Number(row.taux_e1m30 || 0),
    e1m40_ibs_autres_retenues: Number(row.e1m40_ibs_autres_retenues || 0),
    taux_e1m40: Number(row.taux_e1m40 || 0),
    total_cadre2: Number(row.total_cadre2 || 0),
    cadre3_detail: row.cadre3_detail || {},
    acompte_irg_bic_bnc: Number(row.acompte_irg_bic_bnc || 0),
    solde_liquidation_irg: Number(row.solde_liquidation_irg || 0),
    total_cadre3: Number(row.total_cadre3 || 0),
    irg_retenues_detail: row.irg_retenues_detail || {},
    total_cadre4: Number(row.total_cadre4 || 0),
    tls_detail: row.tls_detail || {},
    total_cadre5: Number(row.total_cadre5 || 0),
    droits_timbre_detail: row.droits_timbre_detail || {},
    total_cadre6: Number(row.total_cadre6 || 0),
    autres_taxes_detail: row.autres_taxes_detail || {},
    total_cadre7: Number(row.total_cadre7 || 0),
    tva_ca_imposable_detail: row.tva_ca_imposable_detail || {},
    tva_deductions_detail: row.tva_deductions_detail || {},
    tva_total_droits: Number(row.tva_total_droits || 0),
    tva_total_deductions: Number(row.tva_total_deductions || 0),
    tva_a_payer: Number(row.tva_a_payer || 0),
    tva_precompte_reporter: Number(row.tva_precompte_reporter || 0),
    total_cadre8: Number(row.total_cadre8 || 0),
    total_a_payer: Number(row.total_a_payer || 0),
    mode_paiement: row.mode_paiement || "",
    cheque_bancaire_no: row.cheque_bancaire_no || "",
    cheque_bancaire_agence: row.cheque_bancaire_agence || "",
    cheque_postal_no: row.cheque_postal_no || "",
    montant_numeraire: Number(row.montant_numeraire || 0),
    quittance_numero: row.quittance_numero || "",
    quittance_date: row.quittance_date || "",
    lieu_declaration: row.lieu_declaration || "",
    date_signature: row.date_signature || "",
    statut: row.statut || "brouillon",
    statut_label: STATUS_LABELS[row.statut] || row.statut,
    formatted_total_a_payer: formatDzdAmount(row.total_a_payer || 0),
    formatted_total_cadre8: formatDzdAmount(row.total_cadre8 || 0),
    formatted_total_ibs: formatDzdAmount(
      Number(row.total_cadre1 || 0) + Number(row.total_cadre2 || 0),
    ),
    formatted_total_irg_sal: formatDzdAmount(row.total_cadre4 || 0),
    formatted_total_tls: formatDzdAmount(row.total_cadre5 || 0),
    observations: row.observations || "",
    submitted_at: row.submitted_at,
    created_at: row.created_at,
    updated_at: row.updated_at,
  };
};

const fetchDeclarationById = async (db, companyId, declarationId) => {
  const { rows } = await db.query(
    `${G50_SELECT}
      WHERE company_id = $1
        AND id = $2
      LIMIT 1`,
    [companyId, declarationId],
  );

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

  return normalizeG50Row(rows[0]);
};

const getProfilePeriodicity = (profile) =>
  profile?.g50_periodicite === "trimestriel" ? "trimestriel" : "mensuel";

const buildPayload = ({ body, company, profile, existingItem = null }) => {
  const periodicite =
    cleanString(
      body.periodicite || existingItem?.periodicite || getProfilePeriodicity(profile),
    ) || "mensuel";
  const annee = parseInteger(
    body.annee,
    "L'annee",
    existingItem?.annee || new Date().getUTCFullYear(),
  );
  const mois = periodicite === "mensuel"
    ? parseInteger(body.mois, "Le mois", existingItem?.mois || new Date().getUTCMonth() + 1)
    : null;
  const trimestre = periodicite === "trimestriel"
    ? parseInteger(
        body.trimestre,
        "Le trimestre",
        existingItem?.trimestre || Math.floor(new Date().getUTCMonth() / 3) + 1,
      )
    : null;

  if (periodicite === "mensuel" && (mois < 1 || mois > 12)) {
    throw new ApiError(400, "Le mois doit etre compris entre 1 et 12.");
  }

  if (periodicite === "trimestriel" && (trimestre < 1 || trimestre > 4)) {
    throw new ApiError(400, "Le trimestre doit etre compris entre 1 et 4.");
  }

  const cadre1Detail = parseJsonObject(body.cadre1_detail, existingItem?.cadre1_detail || {});
  const cadre2Detail = parseJsonObject(body.cadre2_detail, existingItem?.cadre2_detail || {});
  const cadre3Detail = parseJsonObject(body.cadre3_detail, existingItem?.cadre3_detail || {});
  const irgDetail = parseJsonObject(
    body.irg_retenues_detail,
    existingItem?.irg_retenues_detail || {},
  );
  const tlsDetail = parseJsonObject(body.tls_detail, existingItem?.tls_detail || {});
  const cadre6Detail = parseJsonObject(
    body.droits_timbre_detail,
    existingItem?.droits_timbre_detail || {},
  );
  const cadre7Detail = parseJsonObject(
    body.autres_taxes_detail,
    existingItem?.autres_taxes_detail || {},
  );
  const tvaCaDetail = parseJsonObject(
    body.tva_ca_imposable_detail,
    existingItem?.tva_ca_imposable_detail || {},
  );
  const tvaDeductionsDetail = parseJsonObject(
    body.tva_deductions_detail,
    existingItem?.tva_deductions_detail || {},
  );

  const cadre1 = computeCadre1Totals({
    e1m10_acompte_ibs: body.e1m10_acompte_ibs,
    solde_liquidation_ibs: body.solde_liquidation_ibs,
    acompte_icbs: body.acompte_icbs,
    solde_liquidation_icbs: body.solde_liquidation_icbs,
  });
  const cadre2 = computeCadre2Totals({
    e1m30_base_imposable: cadre2Detail.e1m30_base_imposable,
    taux_e1m30: body.taux_e1m30,
    e1m40_base_imposable: cadre2Detail.e1m40_base_imposable,
    taux_e1m40: body.taux_e1m40,
  });
  const cadre3 = computeCadre3Totals({
    acompte_irg_bic_bnc: body.acompte_irg_bic_bnc,
    solde_liquidation_irg: body.solde_liquidation_irg,
  });
  const cadre4 = computeCadre4Totals(irgDetail);
  const cadre5 = computeCadre5Totals(tlsDetail);
  const cadre6 = computeCadre6Totals(cadre6Detail);
  const cadre7 = computeCadre7Totals(cadre7Detail);
  const cadre8 = computeCadre8Totals({
    caDetail: tvaCaDetail,
    deductionsDetail: tvaDeductionsDetail,
  });
  const totalAPayer = roundAmount(
    cadre1.total_cadre1 +
      cadre2.total_cadre2 +
      cadre3.total_cadre3 +
      cadre4.total_cadre4 +
      cadre5.total_cadre5 +
      cadre6.total_cadre6 +
      cadre7.total_cadre7 +
      cadre8.total_cadre8,
  );

  return {
    mois,
    trimestre,
    annee,
    periodicite,
    nif: cleanString(profile?.nif || company.nif),
    nin: cleanString(profile?.nin),
    article_imposition: cleanString(profile?.article_imposition || company.ai),
    forme_juridique: cleanString(profile?.forme_juridique),
    code_activite: cleanString(profile?.code_activite),
    cadre1_detail: cadre1Detail,
    e1m10_acompte_ibs: Number(body.e1m10_acompte_ibs || 0),
    solde_liquidation_ibs: Number(body.solde_liquidation_ibs || 0),
    acompte_icbs: Number(body.acompte_icbs || 0),
    solde_liquidation_icbs: Number(body.solde_liquidation_icbs || 0),
    total_cadre1: cadre1.total_cadre1,
    cadre2_detail: cadre2Detail,
    e1m30_ibs_entreprises_etrangeres: cadre2.e1m30_ibs_entreprises_etrangeres,
    taux_e1m30: Number(body.taux_e1m30 || 0),
    e1m40_ibs_autres_retenues: cadre2.e1m40_ibs_autres_retenues,
    taux_e1m40: Number(body.taux_e1m40 || 0),
    total_cadre2: cadre2.total_cadre2,
    cadre3_detail: cadre3Detail,
    acompte_irg_bic_bnc: Number(body.acompte_irg_bic_bnc || 0),
    solde_liquidation_irg: Number(body.solde_liquidation_irg || 0),
    total_cadre3: cadre3.total_cadre3,
    irg_retenues_detail: cadre4.irg_retenues_detail,
    total_cadre4: cadre4.total_cadre4,
    tls_detail: cadre5.tls_detail,
    total_cadre5: cadre5.total_cadre5,
    droits_timbre_detail: cadre6.droits_timbre_detail,
    total_cadre6: cadre6.total_cadre6,
    autres_taxes_detail: cadre7.autres_taxes_detail,
    total_cadre7: cadre7.total_cadre7,
    tva_ca_imposable_detail: cadre8.tva_ca_imposable_detail,
    tva_deductions_detail: cadre8.tva_deductions_detail,
    tva_total_droits: cadre8.tva_total_droits,
    tva_total_deductions: cadre8.tva_total_deductions,
    tva_a_payer: cadre8.tva_a_payer,
    tva_precompte_reporter: cadre8.tva_precompte_reporter,
    total_cadre8: cadre8.total_cadre8,
    mode_paiement: toNullableText(body.mode_paiement),
    cheque_bancaire_no: toNullableText(body.cheque_bancaire_no),
    cheque_bancaire_agence: toNullableText(body.cheque_bancaire_agence),
    cheque_postal_no: toNullableText(body.cheque_postal_no),
    montant_numeraire: parseDecimalField(
      body.montant_numeraire,
      "Le montant en numeraire",
    ),
    quittance_numero: toNullableText(body.quittance_numero),
    quittance_date: parseOptionalIsoDate(body.quittance_date, "La date de quittance"),
    lieu_declaration: toNullableText(body.lieu_declaration || company.city),
    date_signature: parseOptionalIsoDate(body.date_signature, "La date de signature"),
    observations: toNullableText(body.observations),
    total_a_payer: totalAPayer,
    statut: existingItem?.statut || "brouillon",
  };
};

const fetchPreviousPrecompte = async (db, companyId, declaration) => {
  const previousPeriod = getPreviousG50Period({
    periodicite: declaration.periodicite,
    annee: declaration.annee,
    mois: declaration.mois,
    trimestre: declaration.trimestre,
  });

  const params = [companyId, previousPeriod.periodicite, previousPeriod.annee];
  const where = [
    "company_id = $1",
    "periodicite = $2",
    "annee = $3",
  ];

  if (previousPeriod.periodicite === "mensuel") {
    params.push(previousPeriod.mois);
    where.push(`mois = $${params.length}`);
  } else {
    params.push(previousPeriod.trimestre);
    where.push(`trimestre = $${params.length}`);
  }

  const { rows } = await db.query(
    `
      ${G50_SELECT}
      WHERE ${where.join(" AND ")}
      LIMIT 1
    `,
    params,
  );

  if (!rows[0]) {
    return {
      amount: 0,
      source_declaration_id: null,
      period_label: null,
    };
  }

  const source = normalizeG50Row(rows[0]);

  return {
    amount: Number(source.tva_precompte_reporter || 0),
    source_declaration_id: source.id,
    period_label: source.period_label,
    quittance_date: source.quittance_date || null,
  };
};

const fetchCoherenceLines = async ({
  db,
  companyId,
  dateFrom,
  dateTo,
  accountPrefixes = [],
  journalType = null,
}) => {
  const params = [companyId, dateFrom, dateTo];
  const conditions = [
    "je.company_id = $1",
    "je.status = 'validated'",
    "je.entry_date BETWEEN $2 AND $3",
  ];

  if (journalType) {
    params.push(journalType);
    conditions.push(`j.journal_type = $${params.length}`);
  }

  if (accountPrefixes.length) {
    const prefixConditions = accountPrefixes.map((prefix) => {
      params.push(`${prefix}%`);
      return `a.code LIKE $${params.length}`;
    });

    conditions.push(`(${prefixConditions.join(" OR ")})`);
  }

  const { rows } = await db.query(
    `
      SELECT
        je.id,
        je.entry_number,
        TO_CHAR(je.entry_date, 'YYYY-MM-DD') AS entry_date,
        je.piece_ref,
        je.label,
        j.label AS journal_label,
        COALESCE(SUM(ABS(COALESCE(el.debit, 0) - COALESCE(el.credit, 0))), 0) AS amount
      FROM entry_lines el
      INNER JOIN journal_entries je ON je.id = el.entry_id
      INNER JOIN journals j ON j.id = je.journal_id
      INNER JOIN accounts a ON a.id = el.account_id
      WHERE ${conditions.join(" AND ")}
      GROUP BY je.id, je.entry_number, je.entry_date, je.piece_ref, je.label, j.label
      ORDER BY je.entry_date DESC, je.id DESC
      LIMIT 10
    `,
    params,
  );

  const total = rows.reduce((sum, row) => sum + Number(row.amount || 0), 0);

  return {
    total: roundAmount(total),
    entries_count: rows.length,
    entries: rows.map((row) => ({
      id: Number(row.id),
      entry_number: row.entry_number,
      entry_date: row.entry_date,
      piece_ref: row.piece_ref,
      label: row.label,
      journal_label: row.journal_label,
      amount: Number(row.amount || 0),
    })),
  };
};

export const fiscalG50Controller = {
  list: async (req, res) => {
    const companyId = getCompanyId(req);
    const client = await pool.connect();

    try {
      const [profile, fiscalYears] = await Promise.all([
        fetchFiscalProfile(client, companyId),
        fetchFiscalYears(client, companyId),
      ]);
      const periodicite = cleanString(req.query.periodicite || getProfilePeriodicity(profile)) || "mensuel";

      await ensureG50Declarations({
        db: client,
        companyId,
        fiscalYears,
        periodicite,
        userId: req.user?.sub || null,
      });

      const params = [companyId];
      const where = ["company_id = $1"];

      if (req.query.annee) {
        params.push(Number(req.query.annee));
        where.push(`annee = $${params.length}`);
      }

      if (req.query.periodicite) {
        params.push(req.query.periodicite);
        where.push(`periodicite = $${params.length}`);
      } else {
        params.push(periodicite);
        where.push(`periodicite = $${params.length}`);
      }

      if (req.query.statut) {
        params.push(req.query.statut);
        where.push(`statut = $${params.length}`);
      }

      if (req.query.mois) {
        params.push(Number(req.query.mois));
        where.push(`mois = $${params.length}`);
      }

      if (req.query.trimestre) {
        params.push(Number(req.query.trimestre));
        where.push(`trimestre = $${params.length}`);
      }

      const { rows } = await client.query(
        `${G50_SELECT}
          WHERE ${where.join(" AND ")}
          ORDER BY annee DESC, COALESCE(mois, trimestre) ASC`,
        params,
      );

      res.json({
        company_id: companyId,
        items: rows.map(normalizeG50Row),
        meta: {
          periodicite,
          fiscal_years: fiscalYears,
          current_period: getCurrentG50Period(periodicite),
        },
      });
    } finally {
      client.release();
    }
  },

  getById: async (req, res) => {
    const companyId = getCompanyId(req);
    const declarationId = parseInteger(req.params.id, "Identifiant declaration");
    const client = await pool.connect();

    try {
      const item = await fetchDeclarationById(client, companyId, declarationId);
      res.json({
        company_id: companyId,
        item,
      });
    } finally {
      client.release();
    }
  },

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

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

      const [company, profile] = await Promise.all([
        fetchCompany(client, companyId),
        fetchFiscalProfile(client, companyId),
      ]);
      const payload = buildPayload({
        body: req.body,
        company,
        profile,
      });

      const { rows } = await client.query(
        `
          INSERT INTO g50_declarations (
            company_id,
            mois,
            trimestre,
            annee,
            periodicite,
            nif,
            nin,
            article_imposition,
            forme_juridique,
            code_activite,
            cadre1_detail,
            e1m10_acompte_ibs,
            solde_liquidation_ibs,
            acompte_icbs,
            solde_liquidation_icbs,
            total_cadre1,
            cadre2_detail,
            e1m30_ibs_entreprises_etrangeres,
            taux_e1m30,
            e1m40_ibs_autres_retenues,
            taux_e1m40,
            total_cadre2,
            cadre3_detail,
            acompte_irg_bic_bnc,
            solde_liquidation_irg,
            total_cadre3,
            irg_retenues_detail,
            total_cadre4,
            tls_detail,
            total_cadre5,
            droits_timbre_detail,
            total_cadre6,
            autres_taxes_detail,
            total_cadre7,
            tva_ca_imposable_detail,
            tva_deductions_detail,
            tva_total_droits,
            tva_total_deductions,
            tva_a_payer,
            tva_precompte_reporter,
            total_cadre8,
            mode_paiement,
            cheque_bancaire_no,
            cheque_bancaire_agence,
            cheque_postal_no,
            montant_numeraire,
            quittance_numero,
            quittance_date,
            lieu_declaration,
            date_signature,
            observations,
            total_a_payer,
            statut,
            created_by
          )
          VALUES (
            $1, $2, $3, $4, $5, $6, $7, $8, $9, $10,
            $11, $12, $13, $14, $15, $16, $17, $18, $19, $20,
            $21, $22, $23, $24, $25, $26, $27, $28, $29, $30,
            $31, $32, $33, $34, $35, $36, $37, $38, $39, $40,
            $41, $42, $43, $44, $45, $46, $47, $48, $49, $50,
            $51, $52, $53, $54, $55, $56, $57, $58, $59, $60
          )
          RETURNING id
        `,
        [
          companyId,
          payload.mois,
          payload.trimestre,
          payload.annee,
          payload.periodicite,
          payload.nif || null,
          payload.nin || null,
          payload.article_imposition || null,
          payload.forme_juridique || null,
          payload.code_activite || null,
          payload.cadre1_detail,
          payload.e1m10_acompte_ibs,
          payload.solde_liquidation_ibs,
          payload.acompte_icbs,
          payload.solde_liquidation_icbs,
          payload.total_cadre1,
          payload.cadre2_detail,
          payload.e1m30_ibs_entreprises_etrangeres,
          payload.taux_e1m30,
          payload.e1m40_ibs_autres_retenues,
          payload.taux_e1m40,
          payload.total_cadre2,
          payload.cadre3_detail,
          payload.acompte_irg_bic_bnc,
          payload.solde_liquidation_irg,
          payload.total_cadre3,
          payload.irg_retenues_detail,
          payload.total_cadre4,
          payload.tls_detail,
          payload.total_cadre5,
          payload.droits_timbre_detail,
          payload.total_cadre6,
          payload.autres_taxes_detail,
          payload.total_cadre7,
          payload.tva_ca_imposable_detail,
          payload.tva_deductions_detail,
          payload.tva_total_droits,
          payload.tva_total_deductions,
          payload.tva_a_payer,
          payload.tva_precompte_reporter,
          payload.total_cadre8,
          payload.mode_paiement,
          payload.cheque_bancaire_no,
          payload.cheque_bancaire_agence,
          payload.cheque_postal_no,
          payload.montant_numeraire,
          payload.quittance_numero,
          payload.quittance_date,
          payload.lieu_declaration,
          payload.date_signature,
          payload.observations,
          payload.total_a_payer,
          payload.statut,
          req.user?.sub || null,
        ],
      );

      const item = await fetchDeclarationById(client, companyId, rows[0].id);

      await logAudit(client, {
        companyId,
        userId: req.user?.sub || null,
        action: "fiscal.g50.create",
        entityType: "g50_declaration",
        entityId: item.id,
        newData: item,
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.status(201).json({
        message: "Declaration G50 creee avec succes.",
        item,
      });
    } catch (error) {
      await client.query("ROLLBACK");

      if (error?.code === "23505") {
        throw new ApiError(409, "Une declaration G50 existe deja pour cette periode.");
      }

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

  update: async (req, res) => {
    const companyId = getCompanyId(req, req.body);
    const declarationId = parseInteger(req.params.id, "Identifiant declaration");
    const client = await pool.connect();

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

      const [company, profile, existingItem] = await Promise.all([
        fetchCompany(client, companyId),
        fetchFiscalProfile(client, companyId),
        fetchDeclarationById(client, companyId, declarationId),
      ]);

      if (existingItem.statut !== "brouillon") {
        throw new ApiError(409, "Seules les declarations brouillon peuvent etre modifiees.");
      }

      const payload = buildPayload({
        body: req.body,
        company,
        profile,
        existingItem,
      });

      await client.query(
        `
          UPDATE g50_declarations
          SET
            mois = $2,
            trimestre = $3,
            annee = $4,
            periodicite = $5,
            nif = $6,
            nin = $7,
            article_imposition = $8,
            forme_juridique = $9,
            code_activite = $10,
            cadre1_detail = $11,
            e1m10_acompte_ibs = $12,
            solde_liquidation_ibs = $13,
            acompte_icbs = $14,
            solde_liquidation_icbs = $15,
            total_cadre1 = $16,
            cadre2_detail = $17,
            e1m30_ibs_entreprises_etrangeres = $18,
            taux_e1m30 = $19,
            e1m40_ibs_autres_retenues = $20,
            taux_e1m40 = $21,
            total_cadre2 = $22,
            cadre3_detail = $23,
            acompte_irg_bic_bnc = $24,
            solde_liquidation_irg = $25,
            total_cadre3 = $26,
            irg_retenues_detail = $27,
            total_cadre4 = $28,
            tls_detail = $29,
            total_cadre5 = $30,
            droits_timbre_detail = $31,
            total_cadre6 = $32,
            autres_taxes_detail = $33,
            total_cadre7 = $34,
            tva_ca_imposable_detail = $35,
            tva_deductions_detail = $36,
            tva_total_droits = $37,
            tva_total_deductions = $38,
            tva_a_payer = $39,
            tva_precompte_reporter = $40,
            total_cadre8 = $41,
            mode_paiement = $42,
            cheque_bancaire_no = $43,
            cheque_bancaire_agence = $44,
            cheque_postal_no = $45,
            montant_numeraire = $46,
            quittance_numero = $47,
            quittance_date = $48,
            lieu_declaration = $49,
            date_signature = $50,
            observations = $51,
            total_a_payer = $52,
            updated_at = CURRENT_TIMESTAMP
          WHERE id = $1
        `,
        [
          declarationId,
          payload.mois,
          payload.trimestre,
          payload.annee,
          payload.periodicite,
          payload.nif || null,
          payload.nin || null,
          payload.article_imposition || null,
          payload.forme_juridique || null,
          payload.code_activite || null,
          payload.cadre1_detail,
          payload.e1m10_acompte_ibs,
          payload.solde_liquidation_ibs,
          payload.acompte_icbs,
          payload.solde_liquidation_icbs,
          payload.total_cadre1,
          payload.cadre2_detail,
          payload.e1m30_ibs_entreprises_etrangeres,
          payload.taux_e1m30,
          payload.e1m40_ibs_autres_retenues,
          payload.taux_e1m40,
          payload.total_cadre2,
          payload.cadre3_detail,
          payload.acompte_irg_bic_bnc,
          payload.solde_liquidation_irg,
          payload.total_cadre3,
          payload.irg_retenues_detail,
          payload.total_cadre4,
          payload.tls_detail,
          payload.total_cadre5,
          payload.droits_timbre_detail,
          payload.total_cadre6,
          payload.autres_taxes_detail,
          payload.total_cadre7,
          payload.tva_ca_imposable_detail,
          payload.tva_deductions_detail,
          payload.tva_total_droits,
          payload.tva_total_deductions,
          payload.tva_a_payer,
          payload.tva_precompte_reporter,
          payload.total_cadre8,
          payload.mode_paiement,
          payload.cheque_bancaire_no,
          payload.cheque_bancaire_agence,
          payload.cheque_postal_no,
          payload.montant_numeraire,
          payload.quittance_numero,
          payload.quittance_date,
          payload.lieu_declaration,
          payload.date_signature,
          payload.observations,
          payload.total_a_payer,
        ],
      );

      const item = await fetchDeclarationById(client, companyId, declarationId);
      await markFiscalDeadlineAsFiled(client, {
        companyId,
        declarationType: "G50",
        fiscalYearId: null,
        periodeLabel: item.periodicite === "trimestriel"
          ? `T${item.trimestre} ${item.annee}`
          : `${[
              "Janvier",
              "Fevrier",
              "Mars",
              "Avril",
              "Mai",
              "Juin",
              "Juillet",
              "Aout",
              "Septembre",
              "Octobre",
              "Novembre",
              "Decembre",
            ][Math.max(Number(item.mois || 1) - 1, 0)]} ${item.annee}`,
        declarationId: item.id,
      });

      await logAudit(client, {
        companyId,
        userId: req.user?.sub || null,
        action: "fiscal.g50.update",
        entityType: "g50_declaration",
        entityId: item.id,
        oldData: existingItem,
        newData: item,
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.json({
        message: "Declaration G50 mise a jour.",
        item,
      });
    } catch (error) {
      await client.query("ROLLBACK");

      if (error?.code === "23505") {
        throw new ApiError(409, "Une declaration G50 existe deja pour cette periode.");
      }

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

  submit: async (req, res) => {
    const companyId = getCompanyId(req, req.body);
    const declarationId = parseInteger(req.params.id, "Identifiant declaration");
    const client = await pool.connect();

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

      const existingItem = await fetchDeclarationById(client, companyId, declarationId);

      if (existingItem.statut !== "brouillon") {
        throw new ApiError(409, "Cette declaration G50 est deja deposee.");
      }

      const quittanceDate =
        parseOptionalIsoDate(
          req.body.quittance_date || existingItem.quittance_date || new Date().toISOString().slice(0, 10),
          "La date de quittance",
        ) || null;

      await client.query(
        `
          UPDATE g50_declarations
          SET
            statut = 'deposee',
            quittance_numero = COALESCE($2, quittance_numero),
            quittance_date = COALESCE($3, quittance_date),
            submitted_at = CURRENT_TIMESTAMP,
            updated_at = CURRENT_TIMESTAMP
          WHERE id = $1
        `,
        [
          declarationId,
          toNullableText(req.body.quittance_numero) || existingItem.quittance_numero || null,
          quittanceDate,
        ],
      );

      const item = await fetchDeclarationById(client, companyId, declarationId);

      await logAudit(client, {
        companyId,
        userId: req.user?.sub || null,
        action: "fiscal.g50.submit",
        entityType: "g50_declaration",
        entityId: item.id,
        oldData: existingItem,
        newData: item,
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.json({
        message: "Declaration G50 marquee comme deposee.",
        item,
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },

  pdf: async (req, res) => {
    const companyId = getCompanyId(req);
    const declarationId = parseInteger(req.params.id, "Identifiant declaration");
    const client = await pool.connect();

    try {
      const item = await fetchDeclarationById(client, companyId, declarationId);
      const [company, profile, precompte] = await Promise.all([
        fetchCompany(client, companyId),
        fetchFiscalProfile(client, companyId),
        fetchPreviousPrecompte(client, companyId, item),
      ]);

      const buffer = await buildG50PdfBuffer({
        company,
        profile,
        declaration: item,
        previousPrecompte: precompte,
        generatedBy: req.user?.fullName || req.user?.email || "Systeme",
      });

      res.setHeader("Content-Type", "application/pdf");
      res.setHeader(
        "Content-Disposition",
        `inline; filename="g50-${item.annee}-${item.period_label.replace(/\s+/g, "-")}.pdf"`,
      );
      res.send(Buffer.from(buffer));
    } finally {
      client.release();
    }
  },

  coherenceCheck: async (req, res) => {
    const companyId = getCompanyId(req, req.body);
    const declarationId = parseInteger(req.params.id, "Identifiant declaration");
    const client = await pool.connect();

    try {
      const declaration = await fetchDeclarationById(client, companyId, declarationId);
      const { from, to } = getG50DateRange({
        periodicite: declaration.periodicite,
        annee: declaration.annee,
        mois: declaration.mois,
        trimestre: declaration.trimestre,
      });

      const [tvaLines, payrollLines] = await Promise.all([
        fetchCoherenceLines({
          db: client,
          companyId,
          dateFrom: from,
          dateTo: to,
          accountPrefixes: ["445"],
        }),
        fetchCoherenceLines({
          db: client,
          companyId,
          dateFrom: from,
          dateTo: to,
          accountPrefixes: ["442"],
          journalType: "payroll",
        }),
      ]);

      const tolerance = 1;
      const checks = [
        {
          key: "tva",
          label: "TVA declaree vs journaux",
          declared_amount: Number(declaration.total_cadre8 || 0),
          journal_amount: Number(tvaLines.total || 0),
          delta: roundAmount(Math.abs(Number(declaration.total_cadre8 || 0) - Number(tvaLines.total || 0))),
          is_match:
            roundAmount(
              Math.abs(Number(declaration.total_cadre8 || 0) - Number(tvaLines.total || 0)),
            ) <= tolerance,
          hint:
            tvaLines.entries_count > 0
              ? "Comparaison approchee sur les comptes TVA (445%)."
              : "Aucune ecriture TVA validee sur la periode.",
          entries: tvaLines.entries,
        },
        {
          key: "irg_salaires",
          label: "IRG/Salaires vs journal de paie",
          declared_amount: Number(declaration.irg_retenues_detail?.e1l20?.amount || 0),
          journal_amount: Number(payrollLines.total || 0),
          delta: roundAmount(
            Math.abs(
              Number(declaration.irg_retenues_detail?.e1l20?.amount || 0) -
                Number(payrollLines.total || 0),
            ),
          ),
          is_match:
            roundAmount(
              Math.abs(
                Number(declaration.irg_retenues_detail?.e1l20?.amount || 0) -
                  Number(payrollLines.total || 0),
              ),
            ) <= tolerance,
          hint:
            payrollLines.entries_count > 0
              ? "Comparaison approchee sur les comptes 442% du journal Paie."
              : "Aucune ecriture de paie validee sur la periode.",
          entries: payrollLines.entries,
        },
      ];

      res.json({
        item: {
          declaration_id: declaration.id,
          period_label: declaration.period_label,
          date_from: from,
          date_to: to,
          all_match: checks.every((check) => check.is_match),
          checks,
        },
      });
    } finally {
      client.release();
    }
  },

  getPreviousPrecompte: async (req, res) => {
    const companyId = getCompanyId(req);
    const declarationId = parseInteger(req.params.id, "Identifiant declaration");
    const client = await pool.connect();

    try {
      const declaration = await fetchDeclarationById(client, companyId, declarationId);
      const item = await fetchPreviousPrecompte(client, companyId, declaration);

      res.json({
        item,
      });
    } finally {
      client.release();
    }
  },
};
