import { ApiError } from "../utils/ApiError.js";
import { ADMIN_ROLES, fetchRolePermissions, hasPermission } from "../utils/permissions.js";
import { createCompanyNotifications, emitNotifications } from "../utils/notifications.js";
import { markFiscalDeadlineAsFiled } from "../utils/fiscalDeadlines.js";
import { buildG50PdfBuffer } from "../utils/g50Pdf.js";

const RULE_TYPES = new Set([
  "account_range",
  "account_exact",
  "journal_type",
  "entry_label_pattern",
  "auxiliary_type",
]);
const VALIDATION_ROLES = new Set(["super_admin", "manager", "chief_accountant"]);
const G50_ADJUSTMENT_REASON_TYPES = new Set([
  "facturation_error",
  "missing_entry",
  "rate_correction",
  "credit_note_missing",
  "other",
]);
const MONTH_LABELS = [
  "Janvier",
  "Fevrier",
  "Mars",
  "Avril",
  "Mai",
  "Juin",
  "Juillet",
  "Aout",
  "Septembre",
  "Octobre",
  "Novembre",
  "Decembre",
];
const TVA_9_CODES = new Set(["E3B11", "E3B12", "E3B13", "E3B14", "E3B15", "E3B16"]);
const TVA_19_CODES = new Set([
  "E3B21",
  "E3B22",
  "E3B23",
  "E3B24",
  "E3B25",
  "E3B26",
  "E3B28",
  "E3B31",
  "E3B34",
  "E3B35",
  "E3B36",
  "E3B37",
]);
const TVA_BASE_CODES = new Set([...TVA_9_CODES, ...TVA_19_CODES]);
const TVA_DEDUCTION_CODES = new Set(["E3B91", "E3B92", "E3B93", "E3B94", "E3B95", "E3B96"]);
const TVA_PLUS_CODES = new Set(["E3B97", "E3B98"]);
const AUTO_STATUS_SEQUENCE = ["draft", "adjusted", "validated", "locked"];

const STATUS_CACHE = new Map();

const cleanString = (value) => String(value ?? "").trim();
const roundAmount = (value) => Number(Number(value || 0).toFixed(2));
const toNullableText = (value) => {
  const normalized = cleanString(value);
  return normalized || null;
};
const parseDecimal = (value, label, { min = 0 } = {}) => {
  const normalized = cleanString(value).replace(/\s+/g, "").replace(",", ".");

  if (!normalized) {
    throw new ApiError(400, `${label} est obligatoire.`);
  }

  const parsed = Number(normalized);

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

  if (parsed < min) {
    throw new ApiError(400, `${label} est invalide.`);
  }

  return roundAmount(parsed);
};
const ensureIsoDate = (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 formatPeriodLabel = ({ period_type, annee, mois = null, trimestre = null }) => {
  if (period_type === "monthly") {
    return `${MONTH_LABELS[Math.max(Number(mois || 1) - 1, 0)] || "Mois"} ${annee}`;
  }

  return `T${trimestre} ${annee}`;
};
const toDateTimeString = (value) =>
  value ? new Date(value).toISOString() : null;

const toDateKey = (value) => {
  const date = value instanceof Date ? value : new Date(value);

  if (Number.isNaN(date.getTime())) {
    return "";
  }

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

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

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

  const date = new Date(`${normalized}T00:00:00Z`);

  if (Number.isNaN(date.getTime())) {
    throw new Error(`${label} invalide.`);
  }

  return normalized;
};

const getMonthEnd = (year, monthIndex) => {
  const date = new Date(Date.UTC(year, monthIndex + 1, 0));
  return toDateKey(date);
};

const convertLikePatternToRegex = (pattern) => {
  const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  return new RegExp(`^${escaped.replace(/%/g, ".*").replace(/_/g, ".")}$`, "i");
};

const getAmountBySide = (line, amountSide = "credit") => {
  const debit = Number(line.debit || 0);
  const credit = Number(line.credit || 0);

  if (amountSide === "debit") {
    return debit;
  }

  if (amountSide === "credit") {
    return credit;
  }

  if (amountSide === "absolute") {
    return Math.abs(debit - credit);
  }

  return debit - credit;
};

const buildStatusEntry = (headerId = null) => ({
  header_id: headerId,
  status: "pending",
  progress_percent: 0,
  current_step: "",
  steps: [],
  started_at: new Date().toISOString(),
  updated_at: new Date().toISOString(),
});

const updateStatusEntry = (key, patch) => {
  const current = STATUS_CACHE.get(key) || buildStatusEntry();
  const next = {
    ...current,
    ...patch,
    updated_at: new Date().toISOString(),
  };

  if (patch.step_message) {
    next.current_step = patch.step_message;
    next.steps = [...(current.steps || []), patch.step_message];
  }

  STATUS_CACHE.set(key, next);
  return next;
};

const getStatusEntry = (key) => STATUS_CACHE.get(key) || null;

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

  if (!rows[0]) {
    throw new Error("Société 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] || { g50_periodicite: "mensuel" };
};

const resolveFiscalYearForPeriod = async (db, companyId, periodFrom, periodTo) => {
  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
      FROM fiscal_years
      WHERE company_id = $1
        AND start_date <= $2::date
        AND end_date >= $3::date
      ORDER BY start_date DESC
      LIMIT 1
    `,
    [companyId, periodFrom, periodTo],
  );

  if (!rows[0]) {
    throw new Error("Aucun exercice fiscal ne couvre la période demandée.");
  }

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

const inferPeriodDescriptor = ({ periodFrom, periodTo, preferredPeriodicity = "mensuel" }) => {
  const fromDate = new Date(`${periodFrom}T00:00:00Z`);
  const toDate = new Date(`${periodTo}T00:00:00Z`);

  if (Number.isNaN(fromDate.getTime()) || Number.isNaN(toDate.getTime()) || periodTo < periodFrom) {
    throw new Error("La période G50 est invalide.");
  }

  const fromYear = fromDate.getUTCFullYear();
  const fromMonth = fromDate.getUTCMonth() + 1;
  const toYear = toDate.getUTCFullYear();
  const toMonth = toDate.getUTCMonth() + 1;
  const isSameMonth = fromYear === toYear && fromMonth === toMonth;

  if (preferredPeriodicity === "mensuel" || isSameMonth) {
    return {
      period_type: "monthly",
      periodicite: "mensuel",
      annee: fromYear,
      mois: fromMonth,
      trimestre: null,
      date_from: periodFrom,
      date_to: periodTo,
      period_label: `${fromDate.toLocaleString("fr-FR", { month: "long", timeZone: "UTC" })} ${fromYear}`,
    };
  }

  const quarter = Math.floor((fromMonth - 1) / 3) + 1;

  return {
    period_type: "quarterly",
    periodicite: "trimestriel",
    annee: fromYear,
    mois: null,
    trimestre: quarter,
    date_from: periodFrom,
    date_to: periodTo,
    period_label: `T${quarter} ${fromYear}`,
  };
};

const ensureFiscalPeriodRecord = async ({
  db,
  companyId,
  fiscalYearId,
  descriptor,
  userId,
}) => {
  const existing = await db.query(
    `
      SELECT
        id,
        company_id,
        fiscal_year_id,
        period_type,
        mois,
        trimestre,
        annee,
        TO_CHAR(date_from, 'YYYY-MM-DD') AS date_from,
        TO_CHAR(date_to, 'YYYY-MM-DD') AS date_to,
        TO_CHAR(deadline_g50, 'YYYY-MM-DD') AS deadline_g50,
        is_locked
      FROM fiscal_periods
      WHERE company_id = $1
        AND fiscal_year_id = $2
        AND period_type = $3
        AND annee = $4
        AND COALESCE(mois, 0) = COALESCE($5, 0)
        AND COALESCE(trimestre, 0) = COALESCE($6, 0)
      LIMIT 1
    `,
    [
      companyId,
      fiscalYearId,
      descriptor.period_type,
      descriptor.annee,
      descriptor.mois,
      descriptor.trimestre,
    ],
  );

  if (existing.rows[0]) {
    return {
      id: Number(existing.rows[0].id),
      company_id: Number(existing.rows[0].company_id),
      fiscal_year_id: Number(existing.rows[0].fiscal_year_id),
      period_type: existing.rows[0].period_type,
      mois: existing.rows[0].mois === null ? null : Number(existing.rows[0].mois),
      trimestre:
        existing.rows[0].trimestre === null ? null : Number(existing.rows[0].trimestre),
      annee: Number(existing.rows[0].annee),
      date_from: existing.rows[0].date_from,
      date_to: existing.rows[0].date_to,
      deadline_g50: existing.rows[0].deadline_g50,
      is_locked: Boolean(existing.rows[0].is_locked),
    };
  }

  const deadlineDate =
    descriptor.period_type === "monthly"
      ? (() => {
          const nextMonth = new Date(
            Date.UTC(descriptor.annee, Number(descriptor.mois), 20),
          );
          return toDateKey(nextMonth);
        })()
      : (() => {
          const quarterEndMonth = descriptor.trimestre * 3;
          const nextMonth = new Date(
            Date.UTC(descriptor.annee, quarterEndMonth, 20),
          );
          return toDateKey(nextMonth);
        })();

  const inserted = await db.query(
    `
      INSERT INTO fiscal_periods (
        company_id,
        fiscal_year_id,
        period_type,
        mois,
        trimestre,
        annee,
        date_from,
        date_to,
        deadline_g50,
        is_locked,
        locked_at,
        locked_by
      )
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, FALSE, NULL, $10)
      RETURNING
        id,
        company_id,
        fiscal_year_id,
        period_type,
        mois,
        trimestre,
        annee,
        TO_CHAR(date_from, 'YYYY-MM-DD') AS date_from,
        TO_CHAR(date_to, 'YYYY-MM-DD') AS date_to,
        TO_CHAR(deadline_g50, 'YYYY-MM-DD') AS deadline_g50,
        is_locked
    `,
    [
      companyId,
      fiscalYearId,
      descriptor.period_type,
      descriptor.mois,
      descriptor.trimestre,
      descriptor.annee,
      descriptor.date_from,
      descriptor.date_to,
      deadlineDate,
      userId || null,
    ],
  );

  return {
    id: Number(inserted.rows[0].id),
    company_id: Number(inserted.rows[0].company_id),
    fiscal_year_id: Number(inserted.rows[0].fiscal_year_id),
    period_type: inserted.rows[0].period_type,
    mois: inserted.rows[0].mois === null ? null : Number(inserted.rows[0].mois),
    trimestre:
      inserted.rows[0].trimestre === null ? null : Number(inserted.rows[0].trimestre),
    annee: Number(inserted.rows[0].annee),
    date_from: inserted.rows[0].date_from,
    date_to: inserted.rows[0].date_to,
    deadline_g50: inserted.rows[0].deadline_g50,
    is_locked: Boolean(inserted.rows[0].is_locked),
  };
};

const buildPreviousDescriptor = (descriptor) => {
  if (descriptor.period_type === "monthly") {
    const current = new Date(Date.UTC(descriptor.annee, descriptor.mois - 1, 1));
    current.setUTCMonth(current.getUTCMonth() - 1);
    const year = current.getUTCFullYear();
    const month = current.getUTCMonth() + 1;

    return {
      period_type: "monthly",
      periodicite: "mensuel",
      annee: year,
      mois: month,
      trimestre: null,
      date_from: `${year}-${String(month).padStart(2, "0")}-01`,
      date_to: getMonthEnd(year, month - 1),
      period_label: `${current.toLocaleString("fr-FR", { month: "long", timeZone: "UTC" })} ${year}`,
    };
  }

  let quarter = descriptor.trimestre - 1;
  let year = descriptor.annee;

  if (quarter < 1) {
    quarter = 4;
    year -= 1;
  }

  const startMonth = (quarter - 1) * 3 + 1;
  const endMonth = startMonth + 2;

  return {
    period_type: "quarterly",
    periodicite: "trimestriel",
    annee: year,
    mois: null,
    trimestre: quarter,
    date_from: `${year}-${String(startMonth).padStart(2, "0")}-01`,
    date_to: getMonthEnd(year, endMonth - 1),
    period_label: `T${quarter} ${year}`,
  };
};

const fetchLastCalculatedHeader = async (db, fiscalPeriodId) => {
  const { rows } = await db.query(
    `
      SELECT
        id,
        version,
        status,
        TO_CHAR(calculated_at, 'YYYY-MM-DD"T"HH24:MI:SS"Z"') AS calculated_at
      FROM g50_headers
      WHERE fiscal_period_id = $1
      ORDER BY version DESC, created_at DESC
      LIMIT 1
    `,
    [fiscalPeriodId],
  );

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

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

const fetchValidatedEntryStats = async (db, companyId, periodFrom, periodTo) => {
  const [validatedEntries, draftEntries] = await Promise.all([
    db.query(
      `
        SELECT COUNT(*)::int AS count
        FROM journal_entries
        WHERE company_id = $1
          AND status = 'validated'
          AND entry_date BETWEEN $2 AND $3
      `,
      [companyId, periodFrom, periodTo],
    ),
    db.query(
      `
        SELECT COUNT(*)::int AS count
        FROM journal_entries
        WHERE company_id = $1
          AND status = 'draft'
          AND entry_date BETWEEN $2 AND $3
      `,
      [companyId, periodFrom, periodTo],
    ),
  ]);

  return {
    validated_entries_count: Number(validatedEntries.rows[0]?.count || 0),
    draft_entries_count: Number(draftEntries.rows[0]?.count || 0),
  };
};

const fetchAccountCoverage = async (db, companyId, periodFrom, periodTo) => {
  const [activeAccountsResult, movementResult] = await Promise.all([
    db.query(
      `
        SELECT COUNT(*)::int AS count
        FROM accounts
        WHERE company_id = $1
          AND is_active = TRUE
          AND LEFT(code, 1) BETWEEN '4' AND '7'
      `,
      [companyId],
    ),
    db.query(
      `
        WITH movement_accounts AS (
          SELECT DISTINCT a.id
          FROM entry_lines el
          JOIN journal_entries je
            ON je.id = el.entry_id
          JOIN accounts a
            ON a.id = el.account_id
          WHERE je.company_id = $1
            AND je.status = 'validated'
            AND je.entry_date BETWEEN $2 AND $3
            AND a.company_id = $1
            AND LEFT(a.code, 1) BETWEEN '4' AND '7'
        )
        SELECT
          COUNT(*)::int AS movement_accounts_count,
          COUNT(*) FILTER (
            WHERE EXISTS (
              SELECT 1
              FROM fiscal_account_mapping fam
              WHERE fam.company_id = $1
                AND fam.account_id = movement_accounts.id
                AND fam.is_active = TRUE
                AND fam.effective_from <= $3
                AND COALESCE(fam.effective_to, DATE '2999-12-31') >= $2
            )
          )::int AS mapped_movement_accounts_count
        FROM movement_accounts
      `,
      [companyId, periodFrom, periodTo],
    ),
  ]);

  const activeAccountsCount = Number(activeAccountsResult.rows[0]?.count || 0);
  const movementAccountsCount = Number(movementResult.rows[0]?.movement_accounts_count || 0);
  const mappedMovementAccountsCount = Number(
    movementResult.rows[0]?.mapped_movement_accounts_count || 0,
  );

  return {
    active_accounts_count: activeAccountsCount,
    movement_accounts_count: movementAccountsCount,
    mapped_movement_accounts_count: mappedMovementAccountsCount,
    mapped_percent: activeAccountsCount
      ? Math.round((mappedMovementAccountsCount / activeAccountsCount) * 100)
      : 0,
  };
};

const fetchUnmappedAccounts = async (db, companyId, periodFrom, periodTo) => {
  const { rows } = await db.query(
    `
      WITH movement_accounts AS (
        SELECT
          a.id,
          a.code,
          a.label,
          COUNT(*)::int AS lines_count,
          COUNT(DISTINCT je.id)::int AS entries_count
        FROM entry_lines el
        JOIN journal_entries je
          ON je.id = el.entry_id
        JOIN accounts a
          ON a.id = el.account_id
        WHERE je.company_id = $1
          AND je.status = 'validated'
          AND je.entry_date BETWEEN $2 AND $3
          AND a.company_id = $1
          AND LEFT(a.code, 1) BETWEEN '4' AND '7'
        GROUP BY a.id, a.code, a.label
      )
      SELECT
        mov.*
      FROM movement_accounts mov
      WHERE NOT EXISTS (
        SELECT 1
        FROM fiscal_account_mapping fam
        WHERE fam.company_id = $1
          AND fam.account_id = mov.id
          AND fam.is_active = TRUE
          AND fam.effective_from <= $3
          AND COALESCE(fam.effective_to, DATE '2999-12-31') >= $2
      )
      ORDER BY mov.code ASC
    `,
    [companyId, periodFrom, periodTo],
  );

  return rows.map((row) => ({
    id: Number(row.id),
    code: row.code,
    label: row.label,
    lines_count: Number(row.lines_count || 0),
    entries_count: Number(row.entries_count || 0),
  }));
};

const fetchActiveRulesCount = async (db, companyId, periodFrom, periodTo) => {
  const { rows } = await db.query(
    `
      SELECT COUNT(*)::int AS count
      FROM fiscal_rules
      WHERE company_id = $1
        AND is_active = TRUE
        AND effective_from <= $3
        AND COALESCE(effective_to, DATE '2999-12-31') >= $2
    `,
    [companyId, periodFrom, periodTo],
  );

  return Number(rows[0]?.count || 0);
};

const fetchPreviousHeaderContext = async (db, companyId, previousDescriptor) => {
  if (!previousDescriptor) {
    return {
      previous_period_locked: true,
      previous_precompte_amount: 0,
      previous_header_id: null,
    };
  }

  const previousFiscalYear = await resolveFiscalYearForPeriod(
    db,
    companyId,
    previousDescriptor.date_from,
    previousDescriptor.date_to,
  ).catch(() => null);

  if (!previousFiscalYear) {
    return {
      previous_period_locked: true,
      previous_precompte_amount: 0,
      previous_header_id: null,
    };
  }

  const previousFiscalPeriod = await ensureFiscalPeriodRecord({
    db,
    companyId,
    fiscalYearId: previousFiscalYear.id,
    descriptor: previousDescriptor,
    userId: null,
  });

  const [previousHeaderResult, accountingPeriodsResult] = await Promise.all([
    db.query(
      `
        SELECT
          id,
          tva_precompte_reporter,
          status
        FROM g50_headers
        WHERE fiscal_period_id = $1
        ORDER BY version DESC, created_at DESC
        LIMIT 1
      `,
      [previousFiscalPeriod.id],
    ),
    previousDescriptor.period_type === "monthly"
      ? db.query(
          `
            SELECT COALESCE(BOOL_AND(is_closed), FALSE) AS is_locked
            FROM accounting_periods
            WHERE company_id = $1
              AND year = $2
              AND month = $3
          `,
          [companyId, previousDescriptor.annee, previousDescriptor.mois],
        )
      : db.query(
          `
            SELECT COALESCE(BOOL_AND(is_closed), FALSE) AS is_locked
            FROM accounting_periods
            WHERE company_id = $1
              AND year = $2
              AND month = ANY($3::int[])
          `,
          [
            companyId,
            previousDescriptor.annee,
            [
              (previousDescriptor.trimestre - 1) * 3 + 1,
              (previousDescriptor.trimestre - 1) * 3 + 2,
              (previousDescriptor.trimestre - 1) * 3 + 3,
            ],
          ],
        ),
  ]);

  return {
    previous_period_locked:
      Boolean(previousFiscalPeriod.is_locked) ||
      Boolean(accountingPeriodsResult.rows[0]?.is_locked),
    previous_precompte_amount: Number(
      previousHeaderResult.rows[0]?.tva_precompte_reporter || 0,
    ),
    previous_header_id: previousHeaderResult.rows[0]
      ? Number(previousHeaderResult.rows[0].id)
      : null,
    previous_header_status: previousHeaderResult.rows[0]?.status || null,
  };
};

export const runG50Preflight = async ({
  db,
  companyId,
  periodFrom,
  periodTo,
}) => {
  await ensureCompany(db, companyId);
  const fiscalProfile = await fetchFiscalProfile(db, companyId);
  const descriptor = inferPeriodDescriptor({
    periodFrom,
    periodTo,
    preferredPeriodicity: fiscalProfile.g50_periodicite || "mensuel",
  });
  const fiscalYear = await resolveFiscalYearForPeriod(db, companyId, periodFrom, periodTo);
  const fiscalPeriod = await ensureFiscalPeriodRecord({
    db,
    companyId,
    fiscalYearId: fiscalYear.id,
    descriptor,
    userId: null,
  });
  const previousDescriptor = buildPreviousDescriptor(descriptor);

  const [
    entryStats,
    coverage,
    unmappedAccounts,
    activeRulesCount,
    previousContext,
    lastCalculatedHeader,
  ] = await Promise.all([
    fetchValidatedEntryStats(db, companyId, periodFrom, periodTo),
    fetchAccountCoverage(db, companyId, periodFrom, periodTo),
    fetchUnmappedAccounts(db, companyId, periodFrom, periodTo),
    fetchActiveRulesCount(db, companyId, periodFrom, periodTo),
    fetchPreviousHeaderContext(db, companyId, previousDescriptor),
    fetchLastCalculatedHeader(db, fiscalPeriod.id),
  ]);

  return {
    company_id: companyId,
    period_from: periodFrom,
    period_to: periodTo,
    fiscal_year: fiscalYear,
    fiscal_period: fiscalPeriod,
    descriptor,
    last_calculated: lastCalculatedHeader,
    checks: {
      validated_entries: {
        ok: entryStats.validated_entries_count > 0,
        count: entryStats.validated_entries_count,
        message: `${entryStats.validated_entries_count} écriture(s) validée(s)`,
      },
      draft_entries: {
        ok: entryStats.draft_entries_count === 0,
        count: entryStats.draft_entries_count,
        message:
          entryStats.draft_entries_count === 0
            ? "Aucune écriture en brouillon"
            : `${entryStats.draft_entries_count} écriture(s) en brouillon`,
      },
      account_mapping: {
        ok: coverage.mapped_percent >= 80,
        percent: coverage.mapped_percent,
        mapped_accounts: coverage.mapped_movement_accounts_count,
        total_accounts: coverage.active_accounts_count,
        message: `${coverage.mapped_percent}% des comptes actifs sont mappés`,
      },
      unmapped_accounts: {
        ok: unmappedAccounts.length === 0,
        count: unmappedAccounts.length,
        items: unmappedAccounts,
        message:
          unmappedAccounts.length === 0
            ? "Aucun compte non mappé avec mouvement"
            : `${unmappedAccounts.length} compte(s) non mappé(s) avec mouvement`,
      },
      active_rules: {
        ok: activeRulesCount > 0,
        count: activeRulesCount,
        message: `${activeRulesCount} règle(s) fiscale(s) active(s)`,
      },
      previous_precompte: {
        ok: true,
        amount: previousContext.previous_precompte_amount,
        previous_header_id: previousContext.previous_header_id,
        message: `${previousContext.previous_precompte_amount.toLocaleString("fr-DZ")} DA`,
      },
      previous_period_locked: {
        ok: Boolean(previousContext.previous_period_locked),
        message: previousContext.previous_period_locked
          ? "Période précédente verrouillée"
          : "Période précédente non verrouillée",
      },
    },
  };
};

const fetchValidatedLinesForPeriod = async (db, companyId, periodFrom, periodTo) => {
  const { rows } = await db.query(
    `
      SELECT
        el.id AS entry_line_id,
        el.entry_id,
        el.debit,
        el.credit,
        je.entry_date,
        je.label AS entry_label,
        COALESCE(el.label, je.label) AS line_label,
        je.journal_id,
        j.code AS journal_code,
        j.label AS journal_label,
        j.journal_type,
        a.id AS account_id,
        a.code AS account_code,
        a.label AS account_label,
        aa.third_party_type,
        aa.id AS auxiliary_account_id
      FROM entry_lines el
      JOIN journal_entries je
        ON je.id = el.entry_id
      JOIN accounts a
        ON a.id = el.account_id
      JOIN journals j
        ON j.id = je.journal_id
      LEFT JOIN auxiliary_accounts aa
        ON aa.id = el.auxiliary_account_id
      WHERE je.company_id = $1
        AND je.status = 'validated'
        AND je.entry_date BETWEEN $2 AND $3
        AND a.company_id = $1
      ORDER BY je.entry_date ASC, je.id ASC, el.id ASC
    `,
    [companyId, periodFrom, periodTo],
  );

  return rows.map((row) => ({
    entry_line_id: Number(row.entry_line_id),
    entry_id: Number(row.entry_id),
    debit: Number(row.debit || 0),
    credit: Number(row.credit || 0),
    entry_date: row.entry_date,
    entry_label: row.entry_label || "",
    line_label: row.line_label || "",
    journal_id: Number(row.journal_id),
    journal_code: row.journal_code,
    journal_label: row.journal_label,
    journal_type: row.journal_type,
    account_id: Number(row.account_id),
    account_code: row.account_code,
    account_label: row.account_label,
    third_party_type: row.third_party_type || "",
    auxiliary_account_id: row.auxiliary_account_id ? Number(row.auxiliary_account_id) : null,
  }));
};

const fetchAccountMappings = async (db, companyId, periodFrom, periodTo) => {
  const { rows } = await db.query(
    `
      SELECT
        fam.*,
        tc.code AS tax_code_code,
        tc.label AS tax_code_label,
        tc.cadre AS tax_code_cadre,
        tc.sub_cadre AS tax_code_sub_cadre,
        tc.tax_category AS tax_code_category,
        tc.rate AS tax_code_rate,
        tc.is_deduction AS tax_code_is_deduction
      FROM fiscal_account_mapping fam
      JOIN fiscal_tax_codes tc
        ON tc.id = fam.tax_code_id
      WHERE fam.company_id = $1
        AND fam.is_active = TRUE
        AND fam.effective_from <= $3
        AND COALESCE(fam.effective_to, DATE '2999-12-31') >= $2
    `,
    [companyId, periodFrom, periodTo],
  );

  return rows.map((row) => ({
    id: Number(row.id),
    account_id: Number(row.account_id),
    tax_code_id: Number(row.tax_code_id),
    amount_side: row.amount_side,
    sign: Number(row.sign || 1),
    effective_from: toDateKey(row.effective_from),
    effective_to: row.effective_to ? toDateKey(row.effective_to) : "",
    tax_code: {
      id: Number(row.tax_code_id),
      code: row.tax_code_code,
      label: row.tax_code_label,
      cadre: Number(row.tax_code_cadre),
      sub_cadre: row.tax_code_sub_cadre || "",
      tax_category: row.tax_code_category || "",
      rate: row.tax_code_rate === null ? null : Number(row.tax_code_rate),
      is_deduction: Boolean(row.tax_code_is_deduction),
    },
  }));
};

const fetchJournalMappings = async (db, companyId, periodFrom, periodTo) => {
  const { rows } = await db.query(
    `
      SELECT
        fjm.*,
        tc.code AS tax_code_code,
        tc.label AS tax_code_label,
        tc.cadre AS tax_code_cadre,
        tc.sub_cadre AS tax_code_sub_cadre,
        tc.tax_category AS tax_code_category,
        tc.rate AS tax_code_rate,
        tc.is_deduction AS tax_code_is_deduction
      FROM fiscal_journal_mapping fjm
      JOIN fiscal_tax_codes tc
        ON tc.id = fjm.tax_code_id
      WHERE fjm.company_id = $1
        AND fjm.is_active = TRUE
        AND fjm.effective_from <= $3
        AND COALESCE(fjm.effective_to, DATE '2999-12-31') >= $2
    `,
    [companyId, periodFrom, periodTo],
  );

  return rows.map((row) => ({
    id: Number(row.id),
    journal_id: Number(row.journal_id),
    tax_code_id: Number(row.tax_code_id),
    mapping_type: row.mapping_type,
    amount_side: row.amount_side,
    sign: Number(row.sign || 1),
    effective_from: toDateKey(row.effective_from),
    effective_to: row.effective_to ? toDateKey(row.effective_to) : "",
    tax_code: {
      id: Number(row.tax_code_id),
      code: row.tax_code_code,
      label: row.tax_code_label,
      cadre: Number(row.tax_code_cadre),
      sub_cadre: row.tax_code_sub_cadre || "",
      tax_category: row.tax_code_category || "",
      rate: row.tax_code_rate === null ? null : Number(row.tax_code_rate),
      is_deduction: Boolean(row.tax_code_is_deduction),
    },
  }));
};

const fetchFiscalRules = async (db, companyId, periodFrom, periodTo) => {
  const { rows } = await db.query(
    `
      SELECT
        fr.*,
        tc.code AS tax_code_code,
        tc.label AS tax_code_label,
        tc.cadre AS tax_code_cadre,
        tc.sub_cadre AS tax_code_sub_cadre,
        tc.tax_category AS tax_code_category,
        tc.rate AS tax_code_rate,
        tc.is_deduction AS tax_code_is_deduction
      FROM fiscal_rules fr
      JOIN fiscal_tax_codes tc
        ON tc.id = fr.tax_code_id
      WHERE fr.company_id = $1
        AND fr.is_active = TRUE
        AND fr.effective_from <= $3
        AND COALESCE(fr.effective_to, DATE '2999-12-31') >= $2
    `,
    [companyId, periodFrom, periodTo],
  );

  return rows.map((row) => ({
    id: Number(row.id),
    rule_name: row.rule_name || "",
    rule_type: row.rule_type,
    description: row.description || "",
    legal_reference: row.legal_reference || "",
    account_code_from: row.account_code_from || "",
    account_code_to: row.account_code_to || "",
    account_code_exact: row.account_code_exact || "",
    account_code_exact_list: Array.isArray(row.account_code_exact_list)
      ? row.account_code_exact_list.map((value) => cleanString(value)).filter(Boolean)
      : [],
    journal_type: row.journal_type || "",
    journal_type_list: Array.isArray(row.journal_type_list)
      ? row.journal_type_list.map((value) => cleanString(value)).filter(Boolean)
      : [],
    entry_label_pattern: row.entry_label_pattern || "",
    auxiliary_type: row.auxiliary_type || "",
    amount_side: row.amount_side || "credit",
    sign: Number(row.sign || 1),
    effective_from: toDateKey(row.effective_from),
    effective_to: row.effective_to ? toDateKey(row.effective_to) : "",
    tax_code: {
      id: Number(row.tax_code_id),
      code: row.tax_code_code,
      label: row.tax_code_label,
      cadre: Number(row.tax_code_cadre),
      sub_cadre: row.tax_code_sub_cadre || "",
      tax_category: row.tax_code_category || "",
      rate: row.tax_code_rate === null ? null : Number(row.tax_code_rate),
      is_deduction: Boolean(row.tax_code_is_deduction),
    },
  }));
};

const isWithinEffectiveWindow = (entryDate, item) => {
  const dateKey = String(entryDate).slice(0, 10);
  return (
    item.effective_from <= dateKey &&
    (!item.effective_to || item.effective_to >= dateKey)
  );
};

const doesRuleMatchLine = (rule, line) => {
  if (!RULE_TYPES.has(rule.rule_type)) {
    return false;
  }

  if (!isWithinEffectiveWindow(line.entry_date, rule)) {
    return false;
  }

  if (rule.rule_type === "account_range") {
    return (
      cleanString(line.account_code) >= cleanString(rule.account_code_from) &&
      cleanString(line.account_code) <= cleanString(rule.account_code_to)
    );
  }

  if (rule.rule_type === "account_exact") {
    const candidates = rule.account_code_exact_list.length
      ? rule.account_code_exact_list
      : [rule.account_code_exact].filter(Boolean);

    return candidates.includes(cleanString(line.account_code));
  }

  if (rule.rule_type === "journal_type") {
    const candidates = rule.journal_type_list.length
      ? rule.journal_type_list
      : [rule.journal_type].filter(Boolean);

    return candidates.includes(cleanString(line.journal_type));
  }

  if (rule.rule_type === "entry_label_pattern") {
    const regex = convertLikePatternToRegex(rule.entry_label_pattern || "%");
    return regex.test(line.entry_label || "") || regex.test(line.line_label || "");
  }

  if (rule.rule_type === "auxiliary_type") {
    return cleanString(line.third_party_type) === cleanString(rule.auxiliary_type);
  }

  return false;
};

const createAccumulatorLine = (taxCode) => ({
  tax_code_id: taxCode.id,
  code: taxCode.code,
  cadre: taxCode.cadre,
  label: taxCode.label,
  tax_category: taxCode.tax_category || "",
  is_deduction: Boolean(taxCode.is_deduction),
  ca_global: 0,
  ca_imposable: 0,
  ca_exonere: 0,
  base_imposable: 0,
  taux: taxCode.rate,
  montant_calcule: 0,
  montant_final: 0,
  source_entry_ids: new Set(),
  source_entry_count: 0,
});

const isTvaBaseCode = (taxCode) =>
  taxCode.tax_category === "TVA" &&
  /^E3B(1[1-6]|2[1-9]|3[1-7])$/.test(taxCode.code);

const isTvaDeductionCode = (taxCode) =>
  taxCode.tax_category === "TVA" && /^E3B9[1-6]$/.test(taxCode.code);

const isDirectVatAccount = (line) => /^445/.test(cleanString(line.account_code));

const applyContribution = (accumulator, contribution) => {
  const { taxCode, amount, line } = contribution;
  const current = accumulator.get(taxCode.id) || createAccumulatorLine(taxCode);

  if (isTvaBaseCode(taxCode) && !isDirectVatAccount(line)) {
    current.ca_global = roundAmount(current.ca_global + amount);
    current.ca_imposable = roundAmount(current.ca_imposable + amount);
    current.base_imposable = roundAmount(current.base_imposable + amount);
  } else {
    current.montant_calcule = roundAmount(current.montant_calcule + amount);
  }

  current.source_entry_ids.add(line.entry_id);
  current.source_entry_count = current.source_entry_ids.size;

  accumulator.set(taxCode.id, current);
};

const buildSummary = ({ lines, header, descriptor, previousPrecompteAmount }) => {
  const valuesByCode = new Map(lines.map((line) => [line.code, line]));
  const sumByCodes = (codes) =>
    roundAmount(
      codes.reduce(
        (total, code) => total + Number(valuesByCode.get(code)?.montant_final || 0),
        0,
      ),
    );
  const countByCodes = (codes) =>
    codes.reduce(
      (total, code) => total + Number(valuesByCode.get(code)?.source_entry_count || 0),
      0,
    );

  const tva19Codes = [
    "E3B21",
    "E3B22",
    "E3B23",
    "E3B24",
    "E3B25",
    "E3B26",
    "E3B28",
    "E3B31",
    "E3B34",
    "E3B35",
    "E3B36",
    "E3B37",
  ];
  const tva9Codes = ["E3B11", "E3B12", "E3B13", "E3B14", "E3B15", "E3B16"];
  const deductionCodes = ["E3B92", "E3B93", "E3B94", "E3B95", "E3B96"];
  const irgCodes = ["E1L20"];

  return {
    period_label: descriptor.period_label,
    rows: [
      {
        key: "tva_collectee_19",
        label: "TVA collectée 19%",
        amount: sumByCodes(tva19Codes),
        source_count: countByCodes(tva19Codes),
      },
      {
        key: "tva_collectee_9",
        label: "TVA collectée 9%",
        amount: sumByCodes(tva9Codes),
        source_count: countByCodes(tva9Codes),
      },
      {
        key: "tva_deductible",
        label: "TVA déductible (achats)",
        amount: sumByCodes(deductionCodes),
        source_count: countByCodes(deductionCodes),
      },
      {
        key: "precompte_precedent",
        label: "Précompte reporté du mois préc.",
        amount: roundAmount(previousPrecompteAmount),
        source_count: Number(valuesByCode.get("E3B91")?.source_entry_count || 0),
      },
      {
        key: "tva_a_payer",
        label: "TVA à payer",
        amount: roundAmount(header.tva_a_payer),
        source_count: 0,
      },
      {
        key: "irg_salaires",
        label: "IRG/Salaires",
        amount: sumByCodes(irgCodes),
        source_count: countByCodes(irgCodes),
      },
      {
        key: "total_g50",
        label: "TOTAL G50",
        amount: roundAmount(header.total_a_payer),
        source_count: 0,
      },
    ],
  };
};

export const calculateG50 = async ({
  db,
  companyId,
  periodFrom,
  periodTo,
  userId,
  io,
  ipAddress,
}) => {
  const processKey = `company:${companyId}:${periodFrom}:${periodTo}`;
  updateStatusEntry(processKey, {
    status: "running",
    progress_percent: 5,
    step_message: "Initialisation du calcul G50",
  });

  const preflight = await runG50Preflight({
    db,
    companyId,
    periodFrom,
    periodTo,
  });

  const descriptor = preflight.descriptor;
  const fiscalYear = preflight.fiscal_year;
  const fiscalPeriod = preflight.fiscal_period;
  const previousPrecompteAmount = Number(
    preflight.checks.previous_precompte.amount || 0,
  );

  updateStatusEntry(processKey, {
    progress_percent: 20,
    step_message: `Lecture des écritures... (${preflight.checks.validated_entries.count}/${preflight.checks.validated_entries.count})`,
  });

  const lines = await fetchValidatedLinesForPeriod(db, companyId, periodFrom, periodTo);
  const includedEntryIds = [...new Set(lines.map((line) => line.entry_id))];
  const entriesSumDebit = roundAmount(lines.reduce((total, line) => total + Number(line.debit || 0), 0));
  const entriesSumCredit = roundAmount(lines.reduce((total, line) => total + Number(line.credit || 0), 0));

  updateStatusEntry(processKey, {
    progress_percent: 35,
    step_message: `Application des règles fiscales... (${preflight.checks.active_rules.count} règles)`,
  });

  const [accountMappings, journalMappings, fiscalRules] = await Promise.all([
    fetchAccountMappings(db, companyId, periodFrom, periodTo),
    fetchJournalMappings(db, companyId, periodFrom, periodTo),
    fetchFiscalRules(db, companyId, periodFrom, periodTo),
  ]);

  const accountMappingsByAccountId = accountMappings.reduce((accumulator, item) => {
    accumulator[item.account_id] ||= [];
    accumulator[item.account_id].push(item);
    return accumulator;
  }, {});
  const journalMappingsByJournalId = journalMappings.reduce((accumulator, item) => {
    accumulator[item.journal_id] ||= [];
    accumulator[item.journal_id].push(item);
    return accumulator;
  }, {});

  const accumulator = new Map();

  lines.forEach((line) => {
    const activeAccountMappings = (accountMappingsByAccountId[line.account_id] || []).filter((item) =>
      isWithinEffectiveWindow(line.entry_date, item),
    );
    const activeJournalMappings = (journalMappingsByJournalId[line.journal_id] || []).filter((item) =>
      isWithinEffectiveWindow(line.entry_date, item),
    );
    const matchingRules = fiscalRules.filter((rule) => doesRuleMatchLine(rule, line));

    activeAccountMappings.forEach((mapping) => {
      const amount = roundAmount(getAmountBySide(line, mapping.amount_side) * mapping.sign);

      if (amount !== 0) {
        applyContribution(accumulator, {
          taxCode: mapping.tax_code,
          amount,
          line,
        });
      }
    });

    activeJournalMappings.forEach((mapping) => {
      const multiplier = mapping.mapping_type === "exclude" ? -1 : 1;
      const amount = roundAmount(
        getAmountBySide(line, mapping.amount_side) * mapping.sign * multiplier,
      );

      if (amount !== 0) {
        applyContribution(accumulator, {
          taxCode: mapping.tax_code,
          amount,
          line,
        });
      }
    });

    matchingRules.forEach((rule) => {
      const amount = roundAmount(getAmountBySide(line, rule.amount_side) * rule.sign);

      if (amount !== 0) {
        applyContribution(accumulator, {
          taxCode: rule.tax_code,
          amount,
          line,
        });
      }
    });
  });

  updateStatusEntry(processKey, {
    progress_percent: 55,
    step_message: "Calcul TVA... ✅",
  });

  const { rows: taxCodeRows } = await db.query(buildMergedTaxCodesQuery(), [companyId]);
  const mergedTaxCodes = taxCodeRows.map((row) => ({
    id: Number(row.id),
    code: row.code,
    label: row.label,
    cadre: Number(row.cadre),
    sub_cadre: row.sub_cadre || "",
    tax_category: row.tax_category || "",
    rate: row.rate === null ? null : Number(row.rate),
    is_deduction: Boolean(row.is_deduction),
  }));
  const taxCodesByCode = new Map(mergedTaxCodes.map((item) => [item.code, item]));

  accumulator.forEach((lineItem) => {
    if (isTvaBaseCode(lineItem)) {
      const rate = Number(lineItem.taux || 0);
      lineItem.montant_calcule = roundAmount(
        lineItem.montant_calcule + (lineItem.base_imposable * rate) / 100,
      );
    }

    lineItem.montant_final = roundAmount(lineItem.montant_calcule);
  });

  if (previousPrecompteAmount > 0 && taxCodesByCode.has("E3B91")) {
    const taxCode = taxCodesByCode.get("E3B91");
    const current = accumulator.get(taxCode.id) || createAccumulatorLine(taxCode);
    current.montant_calcule = roundAmount(current.montant_calcule + previousPrecompteAmount);
    current.montant_final = roundAmount(current.montant_calcule);
    accumulator.set(taxCode.id, current);
  }

  const calculatedLines = [...accumulator.values()];
  const totalTvaCollectee = roundAmount(
    calculatedLines
      .filter((lineItem) => {
        const taxCode = taxCodesByCode.get(lineItem.code);
        return taxCode && isTvaBaseCode(taxCode);
      })
      .reduce((total, lineItem) => total + Number(lineItem.montant_final || 0), 0),
  );
  const totalTvaDeductible = roundAmount(
    calculatedLines
      .filter((lineItem) => {
        const taxCode = taxCodesByCode.get(lineItem.code);
        return taxCode && isTvaDeductionCode(taxCode);
      })
      .reduce((total, lineItem) => total + Number(lineItem.montant_final || 0), 0),
  );
  const tvaAPayer = roundAmount(Math.max(totalTvaCollectee - totalTvaDeductible, 0));
  const tvaPrecompteReporter = roundAmount(
    Math.max(totalTvaDeductible - totalTvaCollectee, 0),
  );

  if (tvaAPayer > 0 && taxCodesByCode.has("E3B99")) {
    const taxCode = taxCodesByCode.get("E3B99");
    const current = accumulator.get(taxCode.id) || createAccumulatorLine(taxCode);
    current.montant_calcule = tvaAPayer;
    current.montant_final = tvaAPayer;
    accumulator.set(taxCode.id, current);
  }

  if (tvaPrecompteReporter > 0 && taxCodesByCode.has("E3B00")) {
    const taxCode = taxCodesByCode.get("E3B00");
    const current = accumulator.get(taxCode.id) || createAccumulatorLine(taxCode);
    current.montant_calcule = tvaPrecompteReporter;
    current.montant_final = tvaPrecompteReporter;
    accumulator.set(taxCode.id, current);
  }

  updateStatusEntry(processKey, {
    progress_percent: 70,
    step_message: "Calcul IRG... ✅",
  });

  const finalLines = [...accumulator.values()]
    .map((lineItem) => ({
      ...lineItem,
      source_entry_ids: [...lineItem.source_entry_ids].sort((left, right) => left - right),
      source_entry_count: lineItem.source_entry_ids.size,
      montant_final: roundAmount(lineItem.montant_final || lineItem.montant_calcule || 0),
    }))
    .filter(
      (lineItem) =>
        lineItem.montant_final !== 0 ||
        lineItem.base_imposable !== 0 ||
        lineItem.ca_global !== 0 ||
        lineItem.ca_imposable !== 0,
    )
    .sort((left, right) => left.code.localeCompare(right.code));

  const totalsByCadre = finalLines.reduce(
    (accumulatorTotals, lineItem) => {
      accumulatorTotals[lineItem.cadre] = roundAmount(
        accumulatorTotals[lineItem.cadre] + Number(lineItem.montant_final || 0),
      );
      return accumulatorTotals;
    },
    { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 },
  );

  const totalAPayer = roundAmount(
    totalsByCadre[1] +
      totalsByCadre[2] +
      totalsByCadre[3] +
      totalsByCadre[4] +
      totalsByCadre[5] +
      totalsByCadre[6] +
      totalsByCadre[7] +
      tvaAPayer,
  );

  updateStatusEntry(processKey, {
    progress_percent: 85,
    step_message: "Génération du brouillon... ✅",
  });

  const versionResult = await db.query(
    `
      SELECT COALESCE(MAX(version), 0) + 1 AS next_version
      FROM g50_headers
      WHERE fiscal_period_id = $1
    `,
    [fiscalPeriod.id],
  );
  const nextVersion = Number(versionResult.rows[0]?.next_version || 1);

  const headerInsert = await db.query(
    `
      INSERT INTO g50_headers (
        company_id,
        fiscal_period_id,
        version,
        status,
        total_cadre1,
        total_cadre2,
        total_cadre3,
        total_cadre4,
        total_cadre5,
        total_cadre6,
        total_cadre7,
        total_tva_collectee,
        total_tva_deductible,
        tva_a_payer,
        tva_precompte_reporter,
        total_a_payer,
        calculated_at,
        calculated_by,
        entries_count,
        entries_sum_debit,
        entries_sum_credit
      )
      VALUES (
        $1, $2, $3, 'draft',
        $4, $5, $6, $7, $8, $9, $10,
        $11, $12, $13, $14, $15,
        NOW(), $16, $17, $18, $19
      )
      RETURNING id
    `,
    [
      companyId,
      fiscalPeriod.id,
      nextVersion,
      totalsByCadre[1],
      totalsByCadre[2],
      totalsByCadre[3],
      totalsByCadre[4],
      totalsByCadre[5],
      totalsByCadre[6],
      totalsByCadre[7],
      totalTvaCollectee,
      totalTvaDeductible,
      tvaAPayer,
      tvaPrecompteReporter,
      totalAPayer,
      userId || null,
      includedEntryIds.length,
      entriesSumDebit,
      entriesSumCredit,
    ],
  );

  const headerId = Number(headerInsert.rows[0].id);
  updateStatusEntry(processKey, {
    header_id: headerId,
    progress_percent: 90,
    status: "running",
  });
  STATUS_CACHE.set(String(headerId), { ...getStatusEntry(processKey), header_id: headerId });

  for (const lineItem of finalLines) {
    await db.query(
      `
        INSERT INTO g50_lines (
          g50_header_id,
          tax_code_id,
          code,
          cadre,
          label,
          ca_global,
          ca_imposable,
          ca_exonere,
          base_imposable,
          taux,
          montant_calcule,
          montant_final,
          source_entry_ids,
          source_entry_count
        )
        VALUES (
          $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13::bigint[], $14
        )
      `,
      [
        headerId,
        lineItem.tax_code_id,
        lineItem.code,
        lineItem.cadre,
        lineItem.label,
        roundAmount(lineItem.ca_global),
        roundAmount(lineItem.ca_imposable),
        roundAmount(lineItem.ca_exonere),
        roundAmount(lineItem.base_imposable),
        lineItem.taux,
        roundAmount(lineItem.montant_calcule),
        roundAmount(lineItem.montant_final),
        lineItem.source_entry_ids.length ? lineItem.source_entry_ids : null,
        lineItem.source_entry_count,
      ],
    );
  }

  await db.query(
    `
      INSERT INTO g50_audit_log (
        g50_header_id,
        action,
        actor_id,
        details,
        ip_address
      )
      VALUES ($1, 'calculated', $2, $3::jsonb, $4::inet)
    `,
    [
      headerId,
      userId || null,
      JSON.stringify({
        period_from: periodFrom,
        period_to: periodTo,
        included_entries_count: includedEntryIds.length,
        included_lines_count: lines.length,
        rules_count: fiscalRules.length,
        account_mappings_count: accountMappings.length,
        journal_mappings_count: journalMappings.length,
        total_a_payer: totalAPayer,
      }),
      ipAddress || null,
    ],
  );

  const header = {
    id: headerId,
    company_id: companyId,
    fiscal_period_id: fiscalPeriod.id,
    version: nextVersion,
    status: "draft",
    total_cadre1: totalsByCadre[1],
    total_cadre2: totalsByCadre[2],
    total_cadre3: totalsByCadre[3],
    total_cadre4: totalsByCadre[4],
    total_cadre5: totalsByCadre[5],
    total_cadre6: totalsByCadre[6],
    total_cadre7: totalsByCadre[7],
    total_tva_collectee: totalTvaCollectee,
    total_tva_deductible: totalTvaDeductible,
    tva_a_payer: tvaAPayer,
    tva_precompte_reporter: tvaPrecompteReporter,
    total_a_payer: totalAPayer,
    entries_count: includedEntryIds.length,
    entries_sum_debit: entriesSumDebit,
    entries_sum_credit: entriesSumCredit,
  };

  const notifications = await createCompanyNotifications(db, {
    companyId,
    type: "g50_calculated",
    message: `Brouillon G50 généré pour ${descriptor.period_label}`,
    entityId: headerId,
  });

  emitNotifications(io, notifications);
  io?.to(`company:${companyId}`)?.emit("g50:calculated", {
    header_id: headerId,
    total_a_payer: totalAPayer,
    period_label: descriptor.period_label,
  });

  updateStatusEntry(processKey, {
    header_id: headerId,
    status: "completed",
    progress_percent: 100,
    step_message: "Calcul terminé",
  });
  STATUS_CACHE.set(String(headerId), getStatusEntry(processKey));

  return {
    header,
    lines: finalLines,
    summary: buildSummary({
      lines: finalLines,
      header,
      descriptor,
      previousPrecompteAmount,
    }),
    descriptor,
    fiscal_year: fiscalYear,
    fiscal_period: fiscalPeriod,
    preflight,
  };
};

const buildMergedTaxCodesQuery = ({
  companyIdParamIndex = 1,
  orderClause = `
    ORDER BY
      cadre ASC,
      COALESCE(sub_cadre, '') ASC,
      COALESCE(sort_order, 999999) ASC,
      code ASC
  `,
} = {}) => `
  WITH official_codes AS (
    SELECT *
    FROM fiscal_tax_codes
    WHERE company_id IS NULL
  ),
  company_codes AS (
    SELECT *
    FROM fiscal_tax_codes
    WHERE company_id = $${companyIdParamIndex}
  ),
  merged_codes AS (
    SELECT
      COALESCE(cc.id, oc.id) AS id,
      cc.company_id AS company_id,
      COALESCE(cc.code, oc.code) AS code,
      COALESCE(cc.label, oc.label) AS label,
      COALESCE(cc.cadre, oc.cadre) AS cadre,
      COALESCE(cc.sub_cadre, oc.sub_cadre) AS sub_cadre,
      COALESCE(cc.tax_category, oc.tax_category) AS tax_category,
      COALESCE(cc.rate, oc.rate) AS rate,
      COALESCE(cc.rate_type, oc.rate_type) AS rate_type,
      COALESCE(cc.is_deduction, oc.is_deduction) AS is_deduction,
      COALESCE(cc.is_active, oc.is_active) AS is_active,
      COALESCE(cc.sort_order, oc.sort_order) AS sort_order
    FROM official_codes oc
    LEFT JOIN company_codes cc
      ON cc.code = oc.code

    UNION ALL

    SELECT
      cc.id,
      cc.company_id,
      cc.code,
      cc.label,
      cc.cadre,
      cc.sub_cadre,
      cc.tax_category,
      cc.rate,
      cc.rate_type,
      cc.is_deduction,
      cc.is_active,
      cc.sort_order
    FROM company_codes cc
    LEFT JOIN official_codes oc
      ON oc.code = cc.code
    WHERE oc.id IS NULL
  )
  SELECT *
  FROM merged_codes
  WHERE is_active = TRUE
  ${orderClause}
`;

export const getG50CalculationStatus = async ({ db, companyId, headerId }) => {
  const cached = getStatusEntry(String(headerId));

  const { rows: headerRows } = await db.query(
    `
      SELECT
        gh.id,
        gh.company_id,
        gh.fiscal_period_id,
        gh.version,
        gh.status,
        gh.total_cadre1,
        gh.total_cadre2,
        gh.total_cadre3,
        gh.total_cadre4,
        gh.total_cadre5,
        gh.total_cadre6,
        gh.total_cadre7,
        gh.total_tva_collectee,
        gh.total_tva_deductible,
        gh.tva_a_payer,
        gh.tva_precompte_reporter,
        gh.total_a_payer,
        gh.entries_count,
        gh.entries_sum_debit,
        gh.entries_sum_credit,
        TO_CHAR(gh.calculated_at, 'YYYY-MM-DD"T"HH24:MI:SS"Z"') AS calculated_at,
        TO_CHAR(fp.date_from, 'YYYY-MM-DD') AS period_from,
        TO_CHAR(fp.date_to, 'YYYY-MM-DD') AS period_to,
        fp.period_type,
        fp.mois,
        fp.trimestre,
        fp.annee
      FROM g50_headers gh
      JOIN fiscal_periods fp
        ON fp.id = gh.fiscal_period_id
      WHERE gh.id = $1
        AND gh.company_id = $2
      LIMIT 1
    `,
    [headerId, companyId],
  );

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

  const header = {
    id: Number(headerRows[0].id),
    company_id: Number(headerRows[0].company_id),
    fiscal_period_id: Number(headerRows[0].fiscal_period_id),
    version: Number(headerRows[0].version),
    status: headerRows[0].status,
    total_cadre1: Number(headerRows[0].total_cadre1 || 0),
    total_cadre2: Number(headerRows[0].total_cadre2 || 0),
    total_cadre3: Number(headerRows[0].total_cadre3 || 0),
    total_cadre4: Number(headerRows[0].total_cadre4 || 0),
    total_cadre5: Number(headerRows[0].total_cadre5 || 0),
    total_cadre6: Number(headerRows[0].total_cadre6 || 0),
    total_cadre7: Number(headerRows[0].total_cadre7 || 0),
    total_tva_collectee: Number(headerRows[0].total_tva_collectee || 0),
    total_tva_deductible: Number(headerRows[0].total_tva_deductible || 0),
    tva_a_payer: Number(headerRows[0].tva_a_payer || 0),
    tva_precompte_reporter: Number(headerRows[0].tva_precompte_reporter || 0),
    total_a_payer: Number(headerRows[0].total_a_payer || 0),
    entries_count: Number(headerRows[0].entries_count || 0),
    entries_sum_debit: Number(headerRows[0].entries_sum_debit || 0),
    entries_sum_credit: Number(headerRows[0].entries_sum_credit || 0),
    calculated_at: headerRows[0].calculated_at,
    descriptor: {
      period_type: headerRows[0].period_type,
      annee: Number(headerRows[0].annee),
      mois: headerRows[0].mois === null ? null : Number(headerRows[0].mois),
      trimestre: headerRows[0].trimestre === null ? null : Number(headerRows[0].trimestre),
      date_from: headerRows[0].period_from,
      date_to: headerRows[0].period_to,
      period_label:
        headerRows[0].period_type === "monthly"
          ? `${String(headerRows[0].mois).padStart(2, "0")}/${headerRows[0].annee}`
          : `T${headerRows[0].trimestre} ${headerRows[0].annee}`,
    },
  };

  const [linesResult, auditResult] = await Promise.all([
    db.query(
      `
        SELECT
          id,
          tax_code_id,
          code,
          cadre,
          label,
          ca_global,
          ca_imposable,
          ca_exonere,
          base_imposable,
          taux,
          montant_calcule,
          montant_final,
          source_entry_ids,
          source_entry_count
        FROM g50_lines
        WHERE g50_header_id = $1
        ORDER BY cadre ASC, code ASC
      `,
      [headerId],
    ),
    db.query(
      `
        SELECT
          id,
          action,
          actor_id,
          details,
          TO_CHAR(created_at, 'YYYY-MM-DD"T"HH24:MI:SS"Z"') AS created_at
        FROM g50_audit_log
        WHERE g50_header_id = $1
        ORDER BY created_at ASC, id ASC
      `,
      [headerId],
    ),
  ]);

  const lines = linesResult.rows.map((row) => ({
    id: Number(row.id),
    tax_code_id: Number(row.tax_code_id),
    code: row.code,
    cadre: Number(row.cadre),
    label: row.label,
    ca_global: Number(row.ca_global || 0),
    ca_imposable: Number(row.ca_imposable || 0),
    ca_exonere: Number(row.ca_exonere || 0),
    base_imposable: Number(row.base_imposable || 0),
    taux: row.taux === null ? null : Number(row.taux),
    montant_calcule: Number(row.montant_calcule || 0),
    montant_final: Number(row.montant_final || 0),
    source_entry_ids: Array.isArray(row.source_entry_ids)
      ? row.source_entry_ids.map((value) => Number(value))
      : [],
    source_entry_count: Number(row.source_entry_count || 0),
  }));

  return {
    header,
    lines,
    audit_log: auditResult.rows.map((row) => ({
      id: Number(row.id),
      action: row.action,
      actor_id: row.actor_id ? Number(row.actor_id) : null,
      details: row.details || {},
      created_at: row.created_at,
    })),
    progress: cached || {
      header_id: headerId,
      status: "completed",
      progress_percent: 100,
      current_step: "Calcul terminé",
      steps: ["Calcul terminé"],
    },
  };
};
