import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import { logAudit } from "../utils/auditLog.js";
import { syncCompanyFiscalDeadlines } from "../utils/fiscalDeadlines.js";
import {
  FISCAL_ACTIVITY_CODES,
  FISCAL_EXONERATION_OPTIONS,
  FISCAL_G50_PERIODICITIES,
  FISCAL_LEGAL_FORMS,
  FISCAL_TAXPAYER_STATUSES,
} from "../utils/fiscalConstants.js";

const LEGAL_FORM_SET = new Set(FISCAL_LEGAL_FORMS);
const TAXPAYER_STATUS_SET = new Set(FISCAL_TAXPAYER_STATUSES);
const EXONERATION_SET = new Set(FISCAL_EXONERATION_OPTIONS);
const G50_PERIODICITY_SET = new Set(FISCAL_G50_PERIODICITIES);

const cleanString = (value) => String(value ?? "").trim();

const normalizeDigits = (value, maxLength) =>
  cleanString(value)
    .replace(/\D+/g, "")
    .slice(0, maxLength);

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

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

  const normalized = cleanString(value).toLowerCase();

  if (["true", "1", "oui", "yes", "on"].includes(normalized)) {
    return true;
  }

  if (["false", "0", "non", "no", "off"].includes(normalized)) {
    return false;
  }

  return fallback;
};

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

  if (!normalized) {
    return null;
  }

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

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

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

  return normalized;
};

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

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

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

  if (parsed < 0) {
    throw new ApiError(400, `${label} ne peut pas être négatif.`);
  }

  return parsed;
};

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

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

  return companyId;
};

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

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

  return rows[0];
};

const FISCAL_PROFILE_SELECT = `
  SELECT
    id,
    company_id,
    nif,
    nin,
    article_imposition,
    forme_juridique,
    code_activite,
    activity_description,
    regime,
    statut_contribuable,
    exoneration_type,
    exoneration_flags,
    taux_ifu_marchandises,
    taux_ifu_services,
    taux_ifu_auto_entrepreneur,
    minimum_imposition_standard,
    minimum_imposition_auto_entrepreneur,
    option_regime_reel,
    TO_CHAR(date_notification_regime_reel, 'YYYY-MM-DD') AS date_notification_regime_reel,
    obligation_gn12_previsionnelle,
    obligation_gn12_bis,
    obligation_g50ter,
    obligation_g50_tva,
    obligation_g50_tls,
    obligation_g50_ibs_icbs,
    g50_periodicite,
    is_assujetti_tva,
    is_assujetti_tls,
    created_at,
    updated_at
  FROM fiscal_profiles
  WHERE company_id = $1
  LIMIT 1
`;

const normalizeProfileRow = (row) => {
  if (!row) {
    return null;
  }

  return {
    id: row.id,
    company_id: row.company_id,
    nif: row.nif || "",
    nin: row.nin || "",
    article_imposition: row.article_imposition || "",
    forme_juridique: row.forme_juridique || "",
    code_activite: row.code_activite || "",
    activity_description: row.activity_description || "",
    regime: row.regime || "IFU",
    statut_contribuable: row.statut_contribuable || "normal",
    exoneration_type: row.exoneration_type || "none",
    exoneration_flags: Array.isArray(row.exoneration_flags) ? row.exoneration_flags : [],
    taux_ifu_marchandises: Number(row.taux_ifu_marchandises || 0),
    taux_ifu_services: Number(row.taux_ifu_services || 0),
    taux_ifu_auto_entrepreneur: Number(row.taux_ifu_auto_entrepreneur || 0),
    minimum_imposition_standard: Number(row.minimum_imposition_standard || 0),
    minimum_imposition_auto_entrepreneur: Number(
      row.minimum_imposition_auto_entrepreneur || 0,
    ),
    option_regime_reel: Boolean(row.option_regime_reel),
    date_notification_regime_reel: row.date_notification_regime_reel || "",
    obligation_gn12_previsionnelle: Boolean(row.obligation_gn12_previsionnelle),
    obligation_gn12_bis: Boolean(row.obligation_gn12_bis),
    obligation_g50ter: Boolean(row.obligation_g50ter),
    obligation_g50_tva: Boolean(row.obligation_g50_tva),
    obligation_g50_tls: Boolean(row.obligation_g50_tls),
    obligation_g50_ibs_icbs: Boolean(row.obligation_g50_ibs_icbs),
    g50_periodicite: row.g50_periodicite || "mensuel",
    is_assujetti_tva: Boolean(row.is_assujetti_tva),
    is_assujetti_tls: Boolean(row.is_assujetti_tls),
    created_at: row.created_at,
    updated_at: row.updated_at,
  };
};

const fetchProfileByCompanyId = async (db, companyId) => {
  const { rows } = await db.query(FISCAL_PROFILE_SELECT, [companyId]);
  return normalizeProfileRow(rows[0]);
};

const buildDefaultProfile = (company) => ({
  id: null,
  company_id: company.id,
  nif: cleanString(company.nif),
  nin: "",
  article_imposition: cleanString(company.ai),
  forme_juridique: "",
  code_activite: "",
  activity_description: "",
  regime: "IFU",
  statut_contribuable: "normal",
  exoneration_type: "none",
  exoneration_flags: [],
  taux_ifu_marchandises: 5,
  taux_ifu_services: 12,
  taux_ifu_auto_entrepreneur: 0.5,
  minimum_imposition_standard: 30000,
  minimum_imposition_auto_entrepreneur: 10000,
  option_regime_reel: false,
  date_notification_regime_reel: "",
  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",
  is_assujetti_tva: false,
  is_assujetti_tls: false,
  created_at: null,
  updated_at: null,
});

const sanitizeExonerationFlags = (payload) => {
  const source =
    payload.exoneration_flags ?? payload.exonerationFlags ?? payload.exonerations ?? [];

  const rawValues = Array.isArray(source) ? source : [source];
  const normalizedValues = [...new Set(rawValues.map((value) => cleanString(value)).filter(Boolean))];

  const invalidValue = normalizedValues.find((value) => !EXONERATION_SET.has(value));

  if (invalidValue) {
    throw new ApiError(400, "Une exonération fiscale sélectionnée est invalide.");
  }

  return normalizedValues;
};

const resolveActivityDescription = (codeActivite, activityDescription) => {
  if (activityDescription) {
    return activityDescription;
  }

  const exactMatch = FISCAL_ACTIVITY_CODES.find((item) => item.code === codeActivite);
  return exactMatch?.description || "";
};

const buildProfilePayload = (payload, company) => {
  const statutContribuable = cleanString(
    payload.statut_contribuable ?? payload.statutContribuable ?? "normal",
  );

  if (!TAXPAYER_STATUS_SET.has(statutContribuable)) {
    throw new ApiError(400, "Le statut contribuable est invalide.");
  }

  const formeJuridique = cleanString(
    payload.forme_juridique ?? payload.formeJuridique ?? "",
  );

  if (formeJuridique && !LEGAL_FORM_SET.has(formeJuridique)) {
    throw new ApiError(400, "La forme juridique est invalide.");
  }

  const g50Periodicite = cleanString(
    payload.g50_periodicite ?? payload.g50Periodicite ?? "mensuel",
  );

  if (!G50_PERIODICITY_SET.has(g50Periodicite)) {
    throw new ApiError(400, "La périodicité G50 est invalide.");
  }

  const optionRegimeReel = normalizeBoolean(
    payload.option_regime_reel ?? payload.optionRegimeReel,
    false,
  );
  const dateNotificationRegimeReel = parseOptionalIsoDate(
    payload.date_notification_regime_reel ?? payload.dateNotificationRegimeReel,
    "La date de notification",
  );

  if (optionRegimeReel && !dateNotificationRegimeReel) {
    throw new ApiError(
      400,
      "La date de notification est obligatoire en cas d'option pour le régime réel.",
    );
  }

  const codeActivite = normalizeDigits(
    payload.code_activite ?? payload.codeActivite,
    4,
  );
  const exonerationFlags = sanitizeExonerationFlags(payload);
  const obligationGn12Previsionnelle =
    statutContribuable === "nouveau"
      ? false
      : normalizeBoolean(
          payload.obligation_gn12_previsionnelle ?? payload.obligationGn12Previsionnelle,
          true,
        );

  const nextPayload = {
    company_id: company.id,
    nif: normalizeDigits(payload.nif || company.nif, 32),
    nin: normalizeDigits(payload.nin, 32),
    article_imposition: cleanString(
      payload.article_imposition ?? payload.articleImposition ?? company.ai ?? "",
    ),
    forme_juridique: formeJuridique,
    code_activite: codeActivite,
    activity_description: resolveActivityDescription(
      codeActivite,
      cleanString(payload.activity_description ?? payload.activityDescription),
    ),
    regime: optionRegimeReel ? "reel" : "IFU",
    statut_contribuable: statutContribuable,
    exoneration_type: exonerationFlags[0] || "none",
    exoneration_flags: exonerationFlags,
    taux_ifu_marchandises: parseDecimalField(
      payload.taux_ifu_marchandises ?? payload.tauxIfuMarchandises,
      "Le taux IFU marchandises",
      5,
    ),
    taux_ifu_services: parseDecimalField(
      payload.taux_ifu_services ?? payload.tauxIfuServices,
      "Le taux IFU services",
      12,
    ),
    taux_ifu_auto_entrepreneur: parseDecimalField(
      payload.taux_ifu_auto_entrepreneur ?? payload.tauxIfuAutoEntrepreneur,
      "Le taux IFU auto-entrepreneur",
      0.5,
    ),
    minimum_imposition_standard: parseDecimalField(
      payload.minimum_imposition_standard ?? payload.minimumImpositionStandard,
      "Le minimum d'imposition standard",
      30000,
    ),
    minimum_imposition_auto_entrepreneur: parseDecimalField(
      payload.minimum_imposition_auto_entrepreneur ??
        payload.minimumImpositionAutoEntrepreneur,
      "Le minimum d'imposition auto-entrepreneur",
      10000,
    ),
    option_regime_reel: optionRegimeReel,
    date_notification_regime_reel: dateNotificationRegimeReel,
    obligation_gn12_previsionnelle: obligationGn12Previsionnelle,
    obligation_gn12_bis: normalizeBoolean(
      payload.obligation_gn12_bis ?? payload.obligationGn12Bis,
      true,
    ),
    obligation_g50ter: normalizeBoolean(
      payload.obligation_g50ter ?? payload.obligationG50ter,
      false,
    ),
    obligation_g50_tva: normalizeBoolean(
      payload.obligation_g50_tva ?? payload.obligationG50Tva,
      false,
    ),
    obligation_g50_tls: normalizeBoolean(
      payload.obligation_g50_tls ?? payload.obligationG50Tls,
      false,
    ),
    obligation_g50_ibs_icbs: normalizeBoolean(
      payload.obligation_g50_ibs_icbs ?? payload.obligationG50IbsIcbs,
      false,
    ),
    g50_periodicite: g50Periodicite,
  };

  if (!nextPayload.nif) {
    throw new ApiError(400, "Le NIF fiscal est obligatoire.");
  }

  nextPayload.is_assujetti_tva = nextPayload.obligation_g50_tva;
  nextPayload.is_assujetti_tls = nextPayload.obligation_g50_tls;

  return nextPayload;
};

const PROFILE_UPSERT_COLUMNS = [
  "nif",
  "nin",
  "article_imposition",
  "forme_juridique",
  "code_activite",
  "activity_description",
  "regime",
  "statut_contribuable",
  "exoneration_type",
  "exoneration_flags",
  "taux_ifu_marchandises",
  "taux_ifu_services",
  "taux_ifu_auto_entrepreneur",
  "minimum_imposition_standard",
  "minimum_imposition_auto_entrepreneur",
  "option_regime_reel",
  "date_notification_regime_reel",
  "obligation_gn12_previsionnelle",
  "obligation_gn12_bis",
  "obligation_g50ter",
  "obligation_g50_tva",
  "obligation_g50_tls",
  "obligation_g50_ibs_icbs",
  "g50_periodicite",
  "is_assujetti_tva",
  "is_assujetti_tls",
];

export const fiscalController = {
  getProfile: async (req, res) => {
    const companyId = getCompanyId(req);
    const company = await fetchCompany(pool, companyId);
    const profile = await fetchProfileByCompanyId(pool, companyId);

    res.json({
      exists: Boolean(profile),
      item: profile || buildDefaultProfile(company),
    });
  },

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

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

      const company = await fetchCompany(client, companyId);
      const currentProfile = await fetchProfileByCompanyId(client, companyId);
      const nextPayload = buildProfilePayload(req.body, company);

      if (currentProfile) {
        const values = PROFILE_UPSERT_COLUMNS.map((column) => {
          if (column === "exoneration_flags") {
            return JSON.stringify(nextPayload[column]);
          }

          return nextPayload[column];
        });

        await client.query(
          `
            UPDATE fiscal_profiles
            SET
              nif = $2,
              nin = $3,
              article_imposition = $4,
              forme_juridique = $5,
              code_activite = $6,
              activity_description = $7,
              regime = $8,
              statut_contribuable = $9,
              exoneration_type = $10,
              exoneration_flags = $11::jsonb,
              taux_ifu_marchandises = $12,
              taux_ifu_services = $13,
              taux_ifu_auto_entrepreneur = $14,
              minimum_imposition_standard = $15,
              minimum_imposition_auto_entrepreneur = $16,
              option_regime_reel = $17,
              date_notification_regime_reel = $18,
              obligation_gn12_previsionnelle = $19,
              obligation_gn12_bis = $20,
              obligation_g50ter = $21,
              obligation_g50_tva = $22,
              obligation_g50_tls = $23,
              obligation_g50_ibs_icbs = $24,
              g50_periodicite = $25,
              is_assujetti_tva = $26,
              is_assujetti_tls = $27,
              updated_at = CURRENT_TIMESTAMP
            WHERE company_id = $1
          `,
          [companyId, ...values],
        );
      } else {
        const values = [
          companyId,
          ...PROFILE_UPSERT_COLUMNS.map((column) =>
            column === "exoneration_flags"
              ? JSON.stringify(nextPayload[column])
              : nextPayload[column],
          ),
        ];

        await client.query(
          `
            INSERT INTO fiscal_profiles (
              company_id,
              ${PROFILE_UPSERT_COLUMNS.join(", ")}
            )
            VALUES (
              $1,
              $2,
              $3,
              $4,
              $5,
              $6,
              $7,
              $8,
              $9,
              $10,
              $11::jsonb,
              $12,
              $13,
              $14,
              $15,
              $16,
              $17,
              $18,
              $19,
              $20,
              $21,
              $22,
              $23,
              $24,
              $25,
              $26,
              $27
            )
          `,
          values,
        );
      }

      const savedProfile = await fetchProfileByCompanyId(client, companyId);
      await syncCompanyFiscalDeadlines(client, {
        companyId,
        userId: req.user?.sub || req.user?.id || null,
        io: req.app.get("io"),
      });

      await logAudit(client, {
        companyId,
        userId: req.user?.id || null,
        action: currentProfile ? "UPDATE" : "CREATE",
        entityType: "fiscal_profile",
        entityId: savedProfile?.id || null,
        oldData: currentProfile,
        newData: savedProfile,
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.status(currentProfile ? 200 : 201).json({
        exists: true,
        item: savedProfile,
      });
    } catch (error) {
      await client.query("ROLLBACK").catch(() => {});
      throw error;
    } finally {
      client.release();
    }
  },

  searchActivityCodes: async (req, res) => {
    const search = cleanString(req.query.search).toLowerCase();
    const limit = Math.min(Math.max(Number(req.query.limit || 12), 1), 25);

    const filteredItems = FISCAL_ACTIVITY_CODES.filter((item) => {
      if (!search) {
        return true;
      }

      return (
        item.code.includes(search) ||
        item.description.toLowerCase().includes(search)
      );
    }).slice(0, limit);

    res.json({
      items: filteredItems.map((item) => ({
        id: item.code,
        ...item,
        label: `${item.code} · ${item.description}`,
      })),
    });
  },
};
