import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import { logAudit } from "../utils/auditLog.js";
import {
  formatDateOnly,
  getInclusiveMonthSpan,
  parseFiscalDate,
  syncFiscalYearPeriods,
} from "../utils/accountingPeriods.js";
import { generateFiscalDeadlines } from "../utils/fiscalDeadlines.js";
import { ensureG50terDeclarations } from "../utils/fiscalG50ter.js";

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

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

  return companyId;
};

const sanitizeFiscalYear = (fiscalYear) => ({
  id: fiscalYear.id,
  label: fiscalYear.label,
  start_date:
    typeof fiscalYear.start_date === "string"
      ? fiscalYear.start_date
      : formatDateOnly(fiscalYear.start_date),
  end_date:
    typeof fiscalYear.end_date === "string"
      ? fiscalYear.end_date
      : formatDateOnly(fiscalYear.end_date),
  is_closed: fiscalYear.is_closed,
  is_current: fiscalYear.is_current,
});

export const fiscalYearsController = {
  list: async (req, res) => {
    const companyId = getCompanyId(req);
    const { rows } = await pool.query(
      `
        SELECT
          fy.id,
          fy.label,
          TO_CHAR(fy.start_date, 'YYYY-MM-DD') AS start_date,
          TO_CHAR(fy.end_date, 'YYYY-MM-DD') AS end_date,
          fy.is_closed,
          fy.is_current,
          COUNT(ap.id)::int AS periods_count
        FROM fiscal_years fy
        LEFT JOIN accounting_periods ap
          ON ap.fiscal_year_id = fy.id
        WHERE fy.company_id = $1
        GROUP BY fy.id
        ORDER BY fy.start_date DESC
      `,
      [companyId],
    );

    res.json({
      company_id: companyId,
      items: rows.map((row) => ({
        id: Number(row.id),
        label: row.label,
        start_date: formatDateOnly(row.start_date),
        end_date: formatDateOnly(row.end_date),
        is_closed: row.is_closed,
        is_current: row.is_current,
        periods_count: Number(row.periods_count),
        status_label: row.is_closed ? "Clôturé" : "Ouvert",
      })),
    });
  },

  create: async (req, res) => {
    const companyId = getCompanyId(req, req.body);
    const label = String(req.body.label || "").trim();
    const startDate = parseFiscalDate(req.body.start_date, "Date de début");
    const endDate = parseFiscalDate(req.body.end_date, "Date de fin");
    const makeCurrent = Boolean(req.body.is_current);

    if (!label) {
      throw new ApiError(400, "Le libellé de l'exercice est obligatoire.");
    }

    if (endDate < startDate) {
      throw new ApiError(400, "La date de fin doit être postérieure à la date de début.");
    }

    if (getInclusiveMonthSpan(startDate, endDate) !== 12) {
      throw new ApiError(
        400,
        "L'exercice doit couvrir exactement 12 périodes mensuelles.",
      );
    }

    const client = await pool.connect();

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

      const { rows: currentRows } = await client.query(
        `
          SELECT id
          FROM fiscal_years
          WHERE company_id = $1
            AND is_current = TRUE
          LIMIT 1
        `,
        [companyId],
      );
      const shouldSetCurrent = makeCurrent || currentRows.length === 0;

      if (shouldSetCurrent) {
        await client.query(
          `
            UPDATE fiscal_years
            SET is_current = FALSE
            WHERE company_id = $1
          `,
          [companyId],
        );
      }

      const { rows } = await client.query(
        `
          INSERT INTO fiscal_years (
            company_id,
            label,
            start_date,
            end_date,
            is_closed,
            is_current
          )
          VALUES ($1, $2, $3, $4, FALSE, $5)
          RETURNING
            id,
            company_id,
            label,
            TO_CHAR(start_date, 'YYYY-MM-DD') AS start_date,
            TO_CHAR(end_date, 'YYYY-MM-DD') AS end_date,
            is_closed,
            is_current
        `,
        [companyId, label, formatDateOnly(startDate), formatDateOnly(endDate), shouldSetCurrent],
      );

      const fiscalYear = rows[0];
      const generatedPeriods = await syncFiscalYearPeriods(client, {
        id: fiscalYear.id,
        company_id: companyId,
        start_date: fiscalYear.start_date,
      });
      await ensureG50terDeclarations(client, {
        companyId,
        fiscalYear,
        userId: req.user?.sub || null,
      });
      await generateFiscalDeadlines(client, {
        companyId,
        fiscalYear,
        userId: req.user?.sub || req.user?.id || null,
        io: req.app.get("io"),
      });

      await logAudit(client, {
        companyId,
        userId: req.user?.sub || null,
        action: "fiscal_years.create",
        entityType: "fiscal_year",
        entityId: fiscalYear.id,
        newData: {
          ...sanitizeFiscalYear(fiscalYear),
          generated_periods: generatedPeriods.length,
        },
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.status(201).json({
        message: "Exercice créé avec succès.",
        item: {
          ...sanitizeFiscalYear(fiscalYear),
          generated_periods: generatedPeriods.length,
        },
      });
    } catch (error) {
      await client.query("ROLLBACK");

      if (error?.code === "23505") {
        throw new ApiError(409, "Cet exercice existe déjà pour cette société.");
      }

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