import { createCompanyNotifications, emitNotifications } from "./notifications.js";
import { exportReportAsExcel, exportReportAsPdf } from "./reportExport.js";
import { formatDzdAmount } from "./formatters.js";
import { buildG50PeriodMeta, buildExpectedG50Periods, deriveFiscalDeclarationYear as deriveG50Year, ensureG50Declarations } from "./fiscalG50.js";
import { buildG50terQuarterMeta, deriveFiscalDeclarationYear as deriveG50terYear, ensureG50terDeclarations, G50TER_QUARTERS } from "./fiscalG50ter.js";

const DEADLINE_TYPE_META = {
  GN12: {
    label: "GN°12",
    route: "/fiscal/gn12",
  },
  GN12BIS: {
    label: "GN°12 Bis",
    route: "/fiscal/gn12bis",
  },
  G50TER: {
    label: "G50ter",
    route: "/fiscal/g50ter",
  },
  G50: {
    label: "G50",
    route: "/fiscal/g50",
  },
};

const STATUS_LABELS = {
  upcoming: "A deposer",
  due_soon: "Echeance proche",
  overdue: "En retard",
  filed: "Deposee",
};

const STATUS_ICONS = {
  upcoming: "hourglass",
  due_soon: "warning",
  overdue: "danger",
  filed: "check",
};

const MONTH_LABELS = [
  "Janvier",
  "Fevrier",
  "Mars",
  "Avril",
  "Mai",
  "Juin",
  "Juillet",
  "Aout",
  "Septembre",
  "Octobre",
  "Novembre",
  "Decembre",
];

const DEFAULT_PROFILE = {
  obligation_gn12_previsionnelle: true,
  obligation_gn12_bis: true,
  obligation_g50ter: false,
  obligation_g50_tva: false,
  obligation_g50_tls: false,
  obligation_g50_ibs_icbs: false,
  g50_periodicite: "mensuel",
};

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

const formatDateOnly = (value) => {
  if (!value) {
    return "";
  }

  const normalized = String(value).slice(0, 10);
  const [year, month, day] = normalized.split("-");

  if (!year || !month || !day) {
    return normalized;
  }

  return `${day}/${month}/${year}`;
};

const toDate = (value) => {
  if (!value) {
    return null;
  }

  const normalized = String(value).slice(0, 10);
  const date = new Date(`${normalized}T00:00:00Z`);
  return Number.isNaN(date.getTime()) ? null : date;
};

const diffDays = (from, to) => {
  const fromDate = from instanceof Date ? from : toDate(from);
  const toDateValue = to instanceof Date ? to : toDate(to);

  if (!fromDate || !toDateValue) {
    return null;
  }

  return Math.ceil((toDateValue.getTime() - fromDate.getTime()) / (1000 * 60 * 60 * 24));
};

const getDeadlineTypeMeta = (type) => DEADLINE_TYPE_META[type] || DEADLINE_TYPE_META.G50;

const normalizeProfile = (profile) => ({
  ...DEFAULT_PROFILE,
  ...(profile || {}),
  g50_periodicite:
    cleanString(profile?.g50_periodicite) === "trimestriel" ? "trimestriel" : "mensuel",
});

const createPeriodLabelForQuarter = (annee, trimestre) => `T${trimestre} ${annee}`;

const createPeriodLabelForMonth = (annee, mois) =>
  `${MONTH_LABELS[Math.max(Number(mois) - 1, 0)] || "Mois"} ${annee}`;

const buildDeadlineKey = (item) =>
  `${item.declaration_type}::${Number(item.fiscal_year_id || 0)}::${cleanString(item.periode_label)}`;

const shouldCreateG50Deadlines = (profile) =>
  Boolean(
    profile?.obligation_g50_tva ||
      profile?.obligation_g50_tls ||
      profile?.obligation_g50_ibs_icbs,
  );

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

const fetchFiscalProfile = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT
        company_id,
        obligation_gn12_previsionnelle,
        obligation_gn12_bis,
        obligation_g50ter,
        obligation_g50_tva,
        obligation_g50_tls,
        obligation_g50_ibs_icbs,
        g50_periodicite
      FROM fiscal_profiles
      WHERE company_id = $1
      LIMIT 1
    `,
    [companyId],
  );

  return normalizeProfile(rows[0]);
};

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

  return rows[0] || null;
};

const fetchExistingDeadlines = async (db, companyId, fiscalYearId) => {
  const { rows } = await db.query(
    `
      SELECT
        id,
        company_id,
        fiscal_year_id,
        declaration_type,
        TO_CHAR(deadline_date, 'YYYY-MM-DD') AS deadline_date,
        periode_label,
        statut,
        declaration_id
      FROM fiscal_deadlines
      WHERE company_id = $1
        AND fiscal_year_id = $2
      ORDER BY deadline_date ASC, declaration_type ASC
    `,
    [companyId, fiscalYearId],
  );

  return rows.map((row) => ({
    id: Number(row.id),
    company_id: Number(row.company_id),
    fiscal_year_id: row.fiscal_year_id ? Number(row.fiscal_year_id) : null,
    declaration_type: row.declaration_type,
    deadline_date: row.deadline_date,
    periode_label: row.periode_label,
    statut: row.statut,
    declaration_id: row.declaration_id === null ? null : Number(row.declaration_id),
  }));
};

const fetchDeclarationLinks = async (db, companyId, fiscalYear) => {
  const annee = deriveG50terYear(fiscalYear);
  const [gn12Rows, gn12BisRows, g50terRows, g50Rows] = await Promise.all([
    db.query(
      `
        SELECT
          id,
          fiscal_year_id,
          statut
        FROM ifu_declarations_previsionnelles
        WHERE company_id = $1
          AND fiscal_year_id = $2
      `,
      [companyId, fiscalYear.id],
    ),
    db.query(
      `
        SELECT
          id,
          fiscal_year_id,
          statut
        FROM ifu_declarations_definitives
        WHERE company_id = $1
          AND fiscal_year_id = $2
      `,
      [companyId, fiscalYear.id],
    ),
    db.query(
      `
        SELECT
          id,
          fiscal_year_id,
          trimestre,
          annee,
          statut
        FROM g50ter_declarations
        WHERE company_id = $1
          AND fiscal_year_id = $2
      `,
      [companyId, fiscalYear.id],
    ),
    db.query(
      `
        SELECT
          id,
          annee,
          periodicite,
          mois,
          trimestre,
          statut
        FROM g50_declarations
        WHERE company_id = $1
          AND annee = $2
      `,
      [companyId, annee],
    ),
  ]);

  return {
    gn12: gn12Rows.rows[0]
      ? {
          id: Number(gn12Rows.rows[0].id),
          statut: gn12Rows.rows[0].statut,
        }
      : null,
    gn12bis: gn12BisRows.rows[0]
      ? {
          id: Number(gn12BisRows.rows[0].id),
          statut: gn12BisRows.rows[0].statut,
        }
      : null,
    g50terByQuarter: new Map(
      g50terRows.rows.map((row) => [
        Number(row.trimestre),
        {
          id: Number(row.id),
          statut: row.statut,
          trimestre: Number(row.trimestre),
          annee: Number(row.annee),
        },
      ]),
    ),
    g50ByKey: new Map(
      g50Rows.rows.map((row) => [
        `${row.periodicite}:${Number(row.annee)}:${Number(row.mois || row.trimestre || 0)}`,
        {
          id: Number(row.id),
          statut: row.statut,
          periodicite: row.periodicite,
          mois: row.mois === null ? null : Number(row.mois),
          trimestre: row.trimestre === null ? null : Number(row.trimestre),
        },
      ]),
    ),
  };
};

export const computeFiscalDeadlineState = ({
  currentStatus = "upcoming",
  deadlineDate,
  referenceDate = new Date(),
}) => {
  if (currentStatus === "filed") {
    return "filed";
  }

  const remainingDays = diffDays(referenceDate, deadlineDate);

  if (remainingDays === null) {
    return currentStatus || "upcoming";
  }

  if (remainingDays < 0) {
    return "overdue";
  }

  if (remainingDays <= 7) {
    return "due_soon";
  }

  return "upcoming";
};

const buildExpectedDeadlines = ({ fiscalYear, profile, declarationLinks, referenceDate }) => {
  const annee = deriveG50terYear(fiscalYear);
  const items = [];

  if (profile.obligation_gn12_previsionnelle) {
    items.push({
      company_id: null,
      fiscal_year_id: fiscalYear.id,
      declaration_type: "GN12",
      deadline_date: `${annee}-06-30`,
      periode_label: `Exercice ${annee}`,
      declaration_id: declarationLinks.gn12?.id || null,
      statut: computeFiscalDeadlineState({
        currentStatus:
          declarationLinks.gn12 &&
          ["deposee", "validee"].includes(declarationLinks.gn12.statut)
            ? "filed"
            : "upcoming",
        deadlineDate: `${annee}-06-30`,
        referenceDate,
      }),
    });
  }

  if (profile.obligation_gn12_bis) {
    const deadlineDate = `${annee + 1}-01-20`;
    items.push({
      company_id: null,
      fiscal_year_id: fiscalYear.id,
      declaration_type: "GN12BIS",
      deadline_date: deadlineDate,
      periode_label: `Exercice ${annee}`,
      declaration_id: declarationLinks.gn12bis?.id || null,
      statut: computeFiscalDeadlineState({
        currentStatus:
          declarationLinks.gn12bis &&
          ["deposee", "validee"].includes(declarationLinks.gn12bis.statut)
            ? "filed"
            : "upcoming",
        deadlineDate,
        referenceDate,
      }),
    });
  }

  if (profile.obligation_g50ter) {
    G50TER_QUARTERS.forEach((quarter) => {
      const meta = buildG50terQuarterMeta(annee, quarter.trimestre);
      const linkedDeclaration = declarationLinks.g50terByQuarter.get(quarter.trimestre);

      items.push({
        company_id: null,
        fiscal_year_id: fiscalYear.id,
        declaration_type: "G50TER",
        deadline_date: meta.deadline_date,
        periode_label: createPeriodLabelForQuarter(annee, quarter.trimestre),
        declaration_id: linkedDeclaration?.id || null,
        statut: computeFiscalDeadlineState({
          currentStatus:
            linkedDeclaration && ["deposee", "validee"].includes(linkedDeclaration.statut)
              ? "filed"
              : "upcoming",
          deadlineDate: meta.deadline_date,
          referenceDate,
        }),
      });
    });
  }

  if (shouldCreateG50Deadlines(profile)) {
    buildExpectedG50Periods({
      annee,
      periodicite: profile.g50_periodicite,
    }).forEach((period) => {
      const key = `${period.periodicite}:${period.annee}:${Number(period.mois || period.trimestre || 0)}`;
      const linkedDeclaration = declarationLinks.g50ByKey.get(key);

      items.push({
        company_id: null,
        fiscal_year_id: fiscalYear.id,
        declaration_type: "G50",
        deadline_date: period.deadline_date,
        periode_label:
          period.periodicite === "trimestriel"
            ? createPeriodLabelForQuarter(period.annee, period.trimestre)
            : createPeriodLabelForMonth(period.annee, period.mois),
        declaration_id: linkedDeclaration?.id || null,
        statut: computeFiscalDeadlineState({
          currentStatus:
            linkedDeclaration && ["deposee", "validee"].includes(linkedDeclaration.statut)
              ? "filed"
              : "upcoming",
          deadlineDate: period.deadline_date,
          referenceDate,
        }),
      });
    });
  }

  return items;
};

const createDeadlineNotificationMessage = (deadline) => {
  const meta = getDeadlineTypeMeta(deadline.declaration_type);
  return `Echeance fiscale proche: ${meta.label} ${deadline.periode_label} avant le ${formatDateOnly(deadline.deadline_date)}.`;
};

const emitDeadlineApproachingNotifications = async (db, { companyId, deadlines, io }) => {
  if (!io || !Array.isArray(deadlines) || !deadlines.length) {
    return;
  }

  for (const deadline of deadlines) {
    const { rows } = await db.query(
      `
        SELECT 1
        FROM notifications
        WHERE type = 'fiscal_deadline_approaching'
          AND entity_id = $1
        LIMIT 1
      `,
      [deadline.id],
    );

    if (rows.length) {
      continue;
    }

    const notifications = await createCompanyNotifications(db, {
      companyId,
      type: "fiscal_deadline_approaching",
      message: createDeadlineNotificationMessage(deadline),
      entityId: deadline.id,
    });

    emitNotifications(io, notifications);
    const payload = {
      deadline_id: deadline.id,
      declaration_type: deadline.declaration_type,
      periode_label: deadline.periode_label,
      deadline_date: deadline.deadline_date,
      status: deadline.statut,
      message: createDeadlineNotificationMessage(deadline),
    };

    io.to(`company:${companyId}`).emit("fiscal:deadline_approaching", payload);
    io.to(`company:${companyId}`).emit("fiscal:deadline_alert", payload);
  }
};

export const generateFiscalDeadlines = async (
  db,
  { companyId, fiscalYear, profile = null, userId = null, io = null, referenceDate = new Date() },
) => {
  const normalizedProfile = normalizeProfile(profile || (await fetchFiscalProfile(db, companyId)));

  if (normalizedProfile.obligation_g50ter) {
    await ensureG50terDeclarations(db, {
      companyId,
      fiscalYear,
      userId,
    });
  }

  if (shouldCreateG50Deadlines(normalizedProfile)) {
    await ensureG50Declarations({
      db,
      companyId,
      fiscalYears: [fiscalYear],
      periodicite: normalizedProfile.g50_periodicite,
      userId,
    });
  }

  const declarationLinks = await fetchDeclarationLinks(db, companyId, fiscalYear);
  const existingRows = await fetchExistingDeadlines(db, companyId, fiscalYear.id);
  const existingByKey = new Map(existingRows.map((row) => [buildDeadlineKey(row), row]));
  const expectedDeadlines = buildExpectedDeadlines({
    fiscalYear,
    profile: normalizedProfile,
    declarationLinks,
    referenceDate,
  });
  const expectedKeys = new Set(expectedDeadlines.map((item) => buildDeadlineKey(item)));
  const upsertedRows = [];

  for (const expectedItem of expectedDeadlines) {
    const key = buildDeadlineKey(expectedItem);
    const existingItem = existingByKey.get(key);

    if (existingItem) {
      const { rows } = await db.query(
        `
          UPDATE fiscal_deadlines
          SET
            deadline_date = $2,
            statut = $3,
            declaration_id = $4,
            periode_label = $5
          WHERE id = $1
          RETURNING
            id,
            company_id,
            fiscal_year_id,
            declaration_type,
            TO_CHAR(deadline_date, 'YYYY-MM-DD') AS deadline_date,
            periode_label,
            statut,
            declaration_id
        `,
        [
          existingItem.id,
          expectedItem.deadline_date,
          expectedItem.statut,
          expectedItem.declaration_id,
          expectedItem.periode_label,
        ],
      );

      upsertedRows.push({
        id: Number(rows[0].id),
        company_id: Number(rows[0].company_id),
        fiscal_year_id: Number(rows[0].fiscal_year_id),
        declaration_type: rows[0].declaration_type,
        deadline_date: rows[0].deadline_date,
        periode_label: rows[0].periode_label,
        statut: rows[0].statut,
        declaration_id: rows[0].declaration_id === null ? null : Number(rows[0].declaration_id),
      });
      continue;
    }

    const { rows } = await db.query(
      `
        INSERT INTO fiscal_deadlines (
          company_id,
          fiscal_year_id,
          declaration_type,
          deadline_date,
          periode_label,
          statut,
          declaration_id
        )
        VALUES ($1, $2, $3, $4, $5, $6, $7)
        RETURNING
          id,
          company_id,
          fiscal_year_id,
          declaration_type,
          TO_CHAR(deadline_date, 'YYYY-MM-DD') AS deadline_date,
          periode_label,
          statut,
          declaration_id
      `,
      [
        companyId,
        fiscalYear.id,
        expectedItem.declaration_type,
        expectedItem.deadline_date,
        expectedItem.periode_label,
        expectedItem.statut,
        expectedItem.declaration_id,
      ],
    );

    upsertedRows.push({
      id: Number(rows[0].id),
      company_id: Number(rows[0].company_id),
      fiscal_year_id: Number(rows[0].fiscal_year_id),
      declaration_type: rows[0].declaration_type,
      deadline_date: rows[0].deadline_date,
      periode_label: rows[0].periode_label,
      statut: rows[0].statut,
      declaration_id: rows[0].declaration_id === null ? null : Number(rows[0].declaration_id),
    });
  }

  for (const existingItem of existingRows) {
    const key = buildDeadlineKey(existingItem);

    if (expectedKeys.has(key) || existingItem.statut === "filed") {
      continue;
    }

    await db.query(`DELETE FROM fiscal_deadlines WHERE id = $1`, [existingItem.id]);
  }

  const dueSoonRows = upsertedRows.filter((item) => item.statut === "due_soon");
  await emitDeadlineApproachingNotifications(db, {
    companyId,
    deadlines: dueSoonRows,
    io,
  });

  return upsertedRows;
};

export const syncCompanyFiscalDeadlines = async (
  db,
  { companyId, userId = null, io = null, referenceDate = new Date() },
) => {
  const [profile, fiscalYears] = await Promise.all([
    fetchFiscalProfile(db, companyId),
    fetchFiscalYears(db, companyId),
  ]);

  const results = [];

  for (const fiscalYear of fiscalYears) {
    const rows = await generateFiscalDeadlines(db, {
      companyId,
      fiscalYear,
      profile,
      userId,
      io,
      referenceDate,
    });
    results.push(...rows);
  }

  return {
    profile: normalizeProfile(profile),
    fiscalYears,
    deadlines: results,
  };
};

export const refreshFiscalDeadlines = async (
  db,
  { companyId, deadlineYear = null, io = null, referenceDate = new Date() },
) => {
  const params = [companyId];
  const where = ["company_id = $1"];

  if (deadlineYear) {
    params.push(Number(deadlineYear));
    where.push(`EXTRACT(YEAR FROM deadline_date) = $${params.length}`);
  }

  const { rows } = await db.query(
    `
      SELECT
        id,
        company_id,
        fiscal_year_id,
        declaration_type,
        TO_CHAR(deadline_date, 'YYYY-MM-DD') AS deadline_date,
        periode_label,
        statut,
        declaration_id
      FROM fiscal_deadlines
      WHERE ${where.join(" AND ")}
      ORDER BY deadline_date ASC, declaration_type ASC
    `,
    params,
  );

  const updatedRows = [];

  for (const row of rows) {
    const nextStatus = computeFiscalDeadlineState({
      currentStatus: row.statut,
      deadlineDate: row.deadline_date,
      referenceDate,
    });

    if (nextStatus !== row.statut) {
      const { rows: updated } = await db.query(
        `
          UPDATE fiscal_deadlines
          SET statut = $2
          WHERE id = $1
          RETURNING
            id,
            company_id,
            fiscal_year_id,
            declaration_type,
            TO_CHAR(deadline_date, 'YYYY-MM-DD') AS deadline_date,
            periode_label,
            statut,
            declaration_id
        `,
        [row.id, nextStatus],
      );

      updatedRows.push({
        id: Number(updated[0].id),
        company_id: Number(updated[0].company_id),
        fiscal_year_id: updated[0].fiscal_year_id ? Number(updated[0].fiscal_year_id) : null,
        declaration_type: updated[0].declaration_type,
        deadline_date: updated[0].deadline_date,
        periode_label: updated[0].periode_label,
        statut: updated[0].statut,
        declaration_id:
          updated[0].declaration_id === null ? null : Number(updated[0].declaration_id),
      });
    } else {
      updatedRows.push({
        id: Number(row.id),
        company_id: Number(row.company_id),
        fiscal_year_id: row.fiscal_year_id ? Number(row.fiscal_year_id) : null,
        declaration_type: row.declaration_type,
        deadline_date: row.deadline_date,
        periode_label: row.periode_label,
        statut: row.statut,
        declaration_id: row.declaration_id === null ? null : Number(row.declaration_id),
      });
    }
  }

  await emitDeadlineApproachingNotifications(db, {
    companyId,
    deadlines: updatedRows.filter((item) => item.statut === "due_soon"),
    io,
  });

  return updatedRows;
};

export const markFiscalDeadlineAsFiled = async (
  db,
  { companyId, declarationType, fiscalYearId, periodeLabel, declarationId },
) => {
  const params = [companyId, declarationType, periodeLabel, declarationId];
  const where = ["company_id = $1", "declaration_type = $2", "periode_label = $3"];
  const declarationIdParam = 4;

  if (fiscalYearId) {
    params.push(fiscalYearId);
    where.push(`fiscal_year_id = $${params.length}`);
  }

  const { rows } = await db.query(
    `
      UPDATE fiscal_deadlines
      SET
        statut = 'filed',
        declaration_id = $${declarationIdParam}
      WHERE ${where.join(" AND ")}
      RETURNING
        id,
        company_id,
        fiscal_year_id,
        declaration_type,
        TO_CHAR(deadline_date, 'YYYY-MM-DD') AS deadline_date,
        periode_label,
        statut,
        declaration_id
    `,
    params,
  );

  return rows[0]
    ? {
        id: Number(rows[0].id),
        company_id: Number(rows[0].company_id),
        fiscal_year_id: rows[0].fiscal_year_id ? Number(rows[0].fiscal_year_id) : null,
        declaration_type: rows[0].declaration_type,
        deadline_date: rows[0].deadline_date,
        periode_label: rows[0].periode_label,
        statut: rows[0].statut,
        declaration_id: rows[0].declaration_id === null ? null : Number(rows[0].declaration_id),
      }
    : null;
};

const getActionForDeadline = (item) => {
  const meta = getDeadlineTypeMeta(item.declaration_type);

  if (item.declaration_id) {
    return {
      label: item.statut === "filed" ? "Voir" : "Completer",
      path: `${meta.route}/${item.declaration_id}`,
    };
  }

  return {
    label: "Creer",
    path: `${meta.route}/new`,
  };
};

const getDeclarationLabel = (item) => {
  const meta = getDeadlineTypeMeta(item.declaration_type);

  if (item.declaration_type === "G50TER") {
    return `${meta.label} ${item.periode_label}`;
  }

  if (item.declaration_type === "G50") {
    return `${meta.label} ${item.periode_label}`;
  }

  return `${meta.label} ${item.periode_label.replace(/^Exercice\s+/, "")}`;
};

const mapDeadlineItem = (row) => {
  const remainingDays = diffDays(new Date(), row.deadline_date);
  const deadlineYear = Number(String(row.deadline_date).slice(0, 4));
  const deadlineMonth = Number(String(row.deadline_date).slice(5, 7));
  const deadlineDay = Number(String(row.deadline_date).slice(8, 10));

  return {
    id: Number(row.id),
    company_id: Number(row.company_id),
    fiscal_year_id: row.fiscal_year_id ? Number(row.fiscal_year_id) : null,
    declaration_type: row.declaration_type,
    declaration_label: getDeclarationLabel(row),
    declaration_short_label: getDeadlineTypeMeta(row.declaration_type).label,
    periode_label: row.periode_label,
    deadline_date: row.deadline_date,
    deadline_year: deadlineYear,
    deadline_month: deadlineMonth,
    deadline_day: deadlineDay,
    statut: row.statut,
    statut_label: STATUS_LABELS[row.statut] || row.statut,
    statut_icon: STATUS_ICONS[row.statut] || "hourglass",
    declaration_id: row.declaration_id === null ? null : Number(row.declaration_id),
    remaining_days: remainingDays,
    is_urgent: ["due_soon", "overdue"].includes(row.statut),
    action: getActionForDeadline(row),
  };
};

const attachCalendarAmounts = async (db, companyId, items) => {
  const deadlineIds = items.map((item) => Number(item.declaration_id || 0)).filter(Boolean);
  const gn12Ids = items
    .filter((item) => item.declaration_type === "GN12" && item.declaration_id)
    .map((item) => Number(item.declaration_id));
  const gn12BisIds = items
    .filter((item) => item.declaration_type === "GN12BIS" && item.declaration_id)
    .map((item) => Number(item.declaration_id));
  const g50terIds = items
    .filter((item) => item.declaration_type === "G50TER" && item.declaration_id)
    .map((item) => Number(item.declaration_id));
  const g50Ids = items
    .filter((item) => item.declaration_type === "G50" && item.declaration_id)
    .map((item) => Number(item.declaration_id));

  const amountByCompositeKey = new Map();

  if (gn12Ids.length) {
    const { rows } = await db.query(
      `
        SELECT id, montant_effectif_a_payer
        FROM ifu_declarations_previsionnelles
        WHERE company_id = $1
          AND id = ANY($2::bigint[])
      `,
      [companyId, gn12Ids],
    );

    rows.forEach((row) => {
      amountByCompositeKey.set(`GN12:${Number(row.id)}`, Number(row.montant_effectif_a_payer || 0));
    });
  }

  if (gn12BisIds.length) {
    const { rows } = await db.query(
      `
        SELECT id, total_ifu_complementaire
        FROM ifu_declarations_definitives
        WHERE company_id = $1
          AND id = ANY($2::bigint[])
      `,
      [companyId, gn12BisIds],
    );

    rows.forEach((row) => {
      amountByCompositeKey.set(`GN12BIS:${Number(row.id)}`, Number(row.total_ifu_complementaire || 0));
    });
  }

  if (g50terIds.length) {
    const { rows } = await db.query(
      `
        SELECT id, montant_irg
        FROM g50ter_declarations
        WHERE company_id = $1
          AND id = ANY($2::bigint[])
      `,
      [companyId, g50terIds],
    );

    rows.forEach((row) => {
      amountByCompositeKey.set(`G50TER:${Number(row.id)}`, Number(row.montant_irg || 0));
    });
  }

  if (g50Ids.length) {
    const { rows } = await db.query(
      `
        SELECT id, total_a_payer
        FROM g50_declarations
        WHERE company_id = $1
          AND id = ANY($2::bigint[])
      `,
      [companyId, g50Ids],
    );

    rows.forEach((row) => {
      amountByCompositeKey.set(`G50:${Number(row.id)}`, Number(row.total_a_payer || 0));
    });
  }

  return items.map((item) => {
    const amount = item.declaration_id
      ? Number(amountByCompositeKey.get(`${item.declaration_type}:${item.declaration_id}`) || 0)
      : null;

    return {
      ...item,
      amount,
      formatted_amount:
        amount === null || amount === undefined ? "—" : formatDzdAmount(amount),
    };
  });
};

const buildSummaryCard = (type, items) => {
  const typeItems = items.filter((item) => item.declaration_type === type);
  const nextItem =
    typeItems.find((item) => item.statut !== "filed") ||
    typeItems[0] ||
    null;
  const meta = getDeadlineTypeMeta(type);

  if (!nextItem) {
    return {
      type,
      label: `Prochaine ${meta.label}`,
      status: "filed",
      status_label: STATUS_LABELS.filed,
      primary_value: "Aucune",
      secondary_value: "Aucune echeance",
      remaining_days: null,
    };
  }

  if (nextItem.statut === "filed") {
    return {
      type,
      label: `Prochaine ${meta.label}`,
      status: "filed",
      status_label: STATUS_LABELS.filed,
      primary_value: "Deposee",
      secondary_value: `Derniere echeance ${formatDateOnly(nextItem.deadline_date)}`,
      remaining_days: null,
      deadline_date: nextItem.deadline_date,
    };
  }

  const primaryValue =
    type === "G50TER"
      ? nextItem.periode_label
      : type === "G50"
        ? nextItem.periode_label
        : `${nextItem.remaining_days ?? "—"} j`;

  const secondaryValue =
    type === "GN12" || type === "GN12BIS"
      ? `Echeance ${formatDateOnly(nextItem.deadline_date)}`
      : `${nextItem.remaining_days ?? "—"} j avant le ${formatDateOnly(nextItem.deadline_date)}`;

  return {
    type,
    label: `Prochaine ${meta.label}`,
    status: nextItem.statut,
    status_label: nextItem.statut_label,
    primary_value: primaryValue,
    secondary_value: secondaryValue,
    remaining_days: nextItem.remaining_days,
    deadline_date: nextItem.deadline_date,
  };
};

export const getFiscalCalendarData = async (
  db,
  { companyId, annee = null, userId = null, io = null, referenceDate = new Date() },
) => {
  const calendarYear = Number(annee || new Date().getUTCFullYear());
  const syncResult = await syncCompanyFiscalDeadlines(db, {
    companyId,
    userId,
    io,
    referenceDate,
  });
  await refreshFiscalDeadlines(db, {
    companyId,
    deadlineYear: calendarYear,
    io,
    referenceDate,
  });

  const { rows } = await db.query(
    `
      SELECT
        id,
        company_id,
        fiscal_year_id,
        declaration_type,
        TO_CHAR(deadline_date, 'YYYY-MM-DD') AS deadline_date,
        periode_label,
        statut,
        declaration_id
      FROM fiscal_deadlines
      WHERE company_id = $1
        AND EXTRACT(YEAR FROM deadline_date) = $2
      ORDER BY deadline_date ASC, declaration_type ASC
    `,
    [companyId, calendarYear],
  );

  const mappedItems = await attachCalendarAmounts(
    db,
    companyId,
    rows.map(mapDeadlineItem),
  );
  const availableYears = [
    ...new Set(
      [
        calendarYear,
        ...syncResult.fiscalYears.map((item) => Number(item.annee)),
        ...rows.map((row) => Number(String(row.deadline_date).slice(0, 4))),
        ...syncResult.fiscalYears.map((item) => Number(item.annee) + 1),
      ].filter(Boolean),
    ),
  ].sort((left, right) => right - left);

  return {
    annee: calendarYear,
    available_years: availableYears,
    items: mappedItems,
    summary_cards: [
      buildSummaryCard("GN12", mappedItems),
      buildSummaryCard("GN12BIS", mappedItems),
      buildSummaryCard("G50TER", mappedItems),
      buildSummaryCard("G50", mappedItems),
    ],
    urgent_count: mappedItems.filter((item) => item.statut === "due_soon" || item.statut === "overdue").length,
  };
};

const getFiscalYearByDeclarationYear = (fiscalYears, annee) =>
  fiscalYears.find((item) => Number(item.annee) === Number(annee)) || null;

const fetchPenaltyMap = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT
        declaration_type,
        declaration_id,
        SUM(penalty_amount)::numeric AS total_penalty
      FROM fiscal_penalties
      WHERE company_id = $1
      GROUP BY declaration_type, declaration_id
    `,
    [companyId],
  );

  return new Map(
    rows.map((row) => [
      `${row.declaration_type}:${Number(row.declaration_id)}`,
      Number(row.total_penalty || 0),
    ]),
  );
};

const buildAnnualSummaryItems = async (db, companyId, fiscalYear, deadlines) => {
  const penaltyMap = await fetchPenaltyMap(db, companyId);
  const annee = Number(fiscalYear.annee);
  const [gn12Rows, gn12BisRows, g50terRows, g50Rows] = await Promise.all([
    db.query(
      `
        SELECT
          id,
          montant_effectif_a_payer,
          total_ifu_du,
          statut,
          TO_CHAR(date_depot, 'YYYY-MM-DD') AS date_depot
        FROM ifu_declarations_previsionnelles
        WHERE company_id = $1
          AND fiscal_year_id = $2
      `,
      [companyId, fiscalYear.id],
    ),
    db.query(
      `
        SELECT
          id,
          total_ifu_complementaire,
          montant_acquitte,
          statut,
          TO_CHAR(date_depot, 'YYYY-MM-DD') AS date_depot
        FROM ifu_declarations_definitives
        WHERE company_id = $1
          AND fiscal_year_id = $2
      `,
      [companyId, fiscalYear.id],
    ),
    db.query(
      `
        SELECT
          id,
          trimestre,
          montant_irg,
          statut,
          COALESCE(
            TO_CHAR(quittance_date, 'YYYY-MM-DD'),
            TO_CHAR(date_versement, 'YYYY-MM-DD')
          ) AS date_depot
        FROM g50ter_declarations
        WHERE company_id = $1
          AND fiscal_year_id = $2
      `,
      [companyId, fiscalYear.id],
    ),
    db.query(
      `
        SELECT
          id,
          periodicite,
          mois,
          trimestre,
          total_a_payer,
          total_cadre4,
          total_cadre5,
          total_cadre8,
          statut,
          COALESCE(
            TO_CHAR(quittance_date, 'YYYY-MM-DD'),
            TO_CHAR(submitted_at AT TIME ZONE 'UTC', 'YYYY-MM-DD')
          ) AS date_depot
        FROM g50_declarations
        WHERE company_id = $1
          AND annee = $2
      `,
      [companyId, annee],
    ),
  ]);

  const gn12 = gn12Rows.rows[0] || null;
  const gn12Bis = gn12BisRows.rows[0] || null;
  const g50terByQuarter = new Map(
    g50terRows.rows.map((row) => [Number(row.trimestre), row]),
  );
  const g50ByKey = new Map(
    g50Rows.rows.map((row) => [
      `${row.periodicite}:${Number(row.mois || row.trimestre || 0)}`,
      row,
    ]),
  );

  return deadlines.map((deadline) => {
    let amount = null;
    let dateDepot = "";
    let declarationStatus = deadline.statut;
    let declarationId = deadline.declaration_id;
    let penaltyAmount = 0;

    if (deadline.declaration_type === "GN12" && gn12) {
      amount = Number(gn12.montant_effectif_a_payer || gn12.total_ifu_du || 0);
      dateDepot = gn12.date_depot || "";
      declarationStatus = ["deposee", "validee"].includes(gn12.statut) ? "filed" : deadline.statut;
      declarationId = Number(gn12.id);
      penaltyAmount = Number(penaltyMap.get(`GN12:${gn12.id}`) || 0);
    }

    if (deadline.declaration_type === "GN12BIS" && gn12Bis) {
      amount = Number(gn12Bis.montant_acquitte || gn12Bis.total_ifu_complementaire || 0);
      dateDepot = gn12Bis.date_depot || "";
      declarationStatus =
        ["deposee", "validee"].includes(gn12Bis.statut) ? "filed" : deadline.statut;
      declarationId = Number(gn12Bis.id);
      penaltyAmount = Number(penaltyMap.get(`GN12BIS:${gn12Bis.id}`) || 0);
    }

    if (deadline.declaration_type === "G50TER") {
      const quarterMatch = /^T(\d+)\s+/.exec(deadline.periode_label);
      const declaration = g50terByQuarter.get(Number(quarterMatch?.[1] || 0));

      if (declaration) {
        amount = Number(declaration.montant_irg || 0);
        dateDepot = declaration.date_depot || "";
        declarationStatus =
          ["deposee", "validee"].includes(declaration.statut) ? "filed" : deadline.statut;
        declarationId = Number(declaration.id);
        penaltyAmount = Number(penaltyMap.get(`G50TER:${declaration.id}`) || 0);
      }
    }

    if (deadline.declaration_type === "G50") {
      const declaration = /^T\d+\s+/.test(deadline.periode_label)
        ? g50ByKey.get(`trimestriel:${Number(/^T(\d+)/.exec(deadline.periode_label)?.[1] || 0)}`)
        : g50ByKey.get(
            `mensuel:${Math.max(
              MONTH_LABELS.findIndex((month) => deadline.periode_label.startsWith(month)) + 1,
              0,
            )}`,
          );

      if (declaration) {
        amount = Number(declaration.total_a_payer || 0);
        dateDepot = declaration.date_depot || "";
        declarationStatus =
          ["deposee", "validee"].includes(declaration.statut) ? "filed" : deadline.statut;
        declarationId = Number(declaration.id);
        penaltyAmount = Number(penaltyMap.get(`G50:${declaration.id}`) || 0);
      }
    }

    const statusLabel =
      declarationStatus === "filed"
        ? "Deposee"
        : deadline.statut === "overdue"
          ? "Manquante"
          : "A deposer";
    const penaltyLabel =
      penaltyAmount > 0
        ? formatDzdAmount(penaltyAmount)
        : deadline.statut === "overdue"
          ? "10% estimee"
          : "—";

    return {
      declaration_type: deadline.declaration_type,
      type_label:
        deadline.declaration_type === "GN12"
          ? `GN°12 ${annee}`
          : deadline.declaration_type === "GN12BIS"
            ? `GN°12 Bis ${annee}`
            : `${getDeadlineTypeMeta(deadline.declaration_type).label} ${deadline.periode_label}`,
      periode_label: deadline.periode_label,
      amount,
      formatted_amount: amount === null ? "—" : formatDzdAmount(amount),
      status: declarationStatus,
      status_label: statusLabel,
      date_depot: dateDepot,
      formatted_date_depot: dateDepot ? formatDateOnly(dateDepot) : "—",
      penalty_amount: penaltyAmount,
      penalty_label: penaltyLabel,
      declaration_id: declarationId,
    };
  });
};

export const getFiscalAnnualSummaryData = async (
  db,
  { companyId, annee = null, userId = null, io = null, referenceDate = new Date() },
) => {
  const [profile, fiscalYears, company] = await Promise.all([
    fetchFiscalProfile(db, companyId),
    fetchFiscalYears(db, companyId),
    fetchCompany(db, companyId),
  ]);
  const requestedYear =
    Number(annee || 0) ||
    Number(fiscalYears.find((item) => item.is_current)?.annee || fiscalYears[0]?.annee || new Date().getUTCFullYear());
  const fiscalYear = getFiscalYearByDeclarationYear(fiscalYears, requestedYear);

  if (!fiscalYear) {
    return {
      company,
      profile,
      annee: requestedYear,
      available_years: fiscalYears.map((item) => Number(item.annee)).sort((left, right) => right - left),
      items: [],
      totals: {
        ifu: 0,
        irg_salaires: 0,
        tva: 0,
        tls: 0,
        total_fiscal: 0,
      },
    };
  }

  await generateFiscalDeadlines(db, {
    companyId,
    fiscalYear,
    profile,
    userId,
    io,
    referenceDate,
  });

  const deadlines = await fetchExistingDeadlines(db, companyId, fiscalYear.id);
  const items = await buildAnnualSummaryItems(db, companyId, fiscalYear, deadlines);
  const totals = {
    ifu: roundAmount(
      items
        .filter((item) => item.declaration_type === "GN12" || item.declaration_type === "GN12BIS")
        .reduce((sum, item) => sum + Number(item.amount || 0), 0),
    ),
    irg_salaires: roundAmount(
      items
        .filter((item) => item.declaration_type === "G50TER")
        .reduce((sum, item) => sum + Number(item.amount || 0), 0),
    ),
    tva: 0,
    tls: 0,
    total_fiscal: roundAmount(items.reduce((sum, item) => sum + Number(item.amount || 0), 0)),
  };

  const { rows: g50TotalsRows } = await db.query(
    `
      SELECT
        COALESCE(SUM(total_cadre8), 0)::numeric AS total_tva,
        COALESCE(SUM(total_cadre5), 0)::numeric AS total_tls,
        COALESCE(SUM(total_cadre4), 0)::numeric AS total_irg_salaires
      FROM g50_declarations
      WHERE company_id = $1
        AND annee = $2
    `,
    [companyId, requestedYear],
  );

  if (g50TotalsRows[0]) {
    totals.tva = Number(g50TotalsRows[0].total_tva || 0);
    totals.tls = Number(g50TotalsRows[0].total_tls || 0);
    totals.irg_salaires = roundAmount(
      totals.irg_salaires + Number(g50TotalsRows[0].total_irg_salaires || 0),
    );
  }

  return {
    company,
    profile,
    annee: requestedYear,
    available_years: fiscalYears.map((item) => Number(item.annee)).sort((left, right) => right - left),
    items,
    totals: {
      ...totals,
      formatted_ifu: formatDzdAmount(totals.ifu),
      formatted_irg_salaires: formatDzdAmount(totals.irg_salaires),
      formatted_tva: formatDzdAmount(totals.tva),
      formatted_tls: formatDzdAmount(totals.tls),
      formatted_total_fiscal: formatDzdAmount(totals.total_fiscal),
    },
  };
};

const buildAnnualSummaryReport = ({ company, annee, items, totals, generatedBy }) => ({
  title: `Recapitulatif Fiscal Annuel ${annee}`,
  description: "Synthese annuelle des declarations IFU, G50ter et G50.",
  company,
  generated_at: new Intl.DateTimeFormat("fr-FR", {
    day: "2-digit",
    month: "2-digit",
    year: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  }).format(new Date()),
  generated_by: generatedBy || "Systeme",
  parameters: [{ label: "Annee", value: String(annee) }],
  cards: [
    { label: "Total IFU paye", value: totals.ifu, type: "amount" },
    { label: "Total IRG/Salaires", value: totals.irg_salaires, type: "amount" },
    { label: "Total TVA", value: totals.tva, type: "amount" },
    { label: "Total TLS", value: totals.tls, type: "amount" },
  ],
  table: {
    columns: [
      { key: "type_label", label: "Type" },
      { key: "periode_label", label: "Periode" },
      { key: "amount", label: "IFU/Montant", type: "amount" },
      { key: "status_label", label: "Statut" },
      { key: "date_depot", label: "Date depot", type: "date" },
      { key: "penalty_label", label: "Penalite calculee" },
    ],
    rows: items,
    emptyMessage: "Aucune declaration fiscale sur l'annee selectionnee.",
  },
  summary: [{ label: "Total fiscal annee", value: totals.total_fiscal, type: "amount" }],
});

export const exportFiscalAnnualSummaryPdf = async ({
  company,
  annee,
  items,
  totals,
  generatedBy,
}) =>
  exportReportAsPdf(
    buildAnnualSummaryReport({
      company,
      annee,
      items,
      totals,
      generatedBy,
    }),
  );

export const exportFiscalAnnualSummaryExcel = async ({
  company,
  annee,
  items,
  totals,
  generatedBy,
}) =>
  exportReportAsExcel(
    buildAnnualSummaryReport({
      company,
      annee,
      items,
      totals,
      generatedBy,
    }),
  );
