import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import { logAudit } from "../utils/auditLog.js";

const TAX_CATEGORIES = new Set(["TVA", "IRG", "IBS", "TLS", "TIMBRE", "AUTRE"]);
const RATE_TYPES = new Set(["fixed", "bareme", "variable"]);
const SUB_CADRES = new Set(["A", "B", "C"]);
const RESET_MODES = new Set(["import", "reset"]);

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

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", "yes", "oui", "on", "active", "actif"].includes(normalized)) {
    return true;
  }

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

  return fallback;
};

const normalizeNullableInteger = (value, label, { min = null, max = null } = {}) => {
  if (value === undefined || value === null || value === "") {
    return null;
  }

  const parsed = Number(value);

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

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

  if (max !== null && parsed > max) {
    throw new ApiError(400, `${label} est invalide.`);
  }

  return parsed;
};

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

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

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

  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 ensureCompanyAccess = (req, companyId) => {
  if (req.user?.role === "super_admin") {
    return;
  }

  const allowedCompanyIds = new Set(
    [req.user?.companyId, ...(Array.isArray(req.user?.companyIds) ? req.user.companyIds : [])]
      .map((value) => Number(value || 0))
      .filter(Boolean),
  );

  if (!allowedCompanyIds.has(companyId)) {
    throw new ApiError(403, "Accès refusé à cette société.");
  }
};

const mapTaxCodeRow = (row) => ({
  id: Number(row.id),
  company_id: row.company_id ? Number(row.company_id) : null,
  company_row_id: row.company_row_id ? Number(row.company_row_id) : null,
  official_row_id: row.official_row_id ? Number(row.official_row_id) : null,
  code: row.code,
  label: row.label,
  cadre: Number(row.cadre),
  sub_cadre: row.sub_cadre || "",
  tax_category: row.tax_category || "",
  rate: row.rate === null || row.rate === undefined ? null : Number(row.rate),
  rate_type: row.rate_type || "",
  base_formula: row.base_formula || "",
  legal_reference: row.legal_reference || "",
  is_deduction: Boolean(row.is_deduction),
  is_active: Boolean(row.is_active),
  sort_order: row.sort_order === null || row.sort_order === undefined ? null : Number(row.sort_order),
  is_official: Boolean(row.is_official),
  is_custom: Boolean(row.is_custom),
  has_company_override: Boolean(row.has_company_override),
  is_locked: Boolean(row.is_official),
  can_delete: Boolean(row.is_custom),
  created_at: row.created_at,
  updated_at: row.updated_at,
});

const buildMergedTaxCodesQuery = ({
  companyIdParamIndex = 1,
  whereClause = "",
  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,
      cc.id AS company_row_id,
      oc.id AS official_row_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.base_formula, oc.base_formula) AS base_formula,
      COALESCE(cc.legal_reference, oc.legal_reference) AS legal_reference,
      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,
      COALESCE(cc.created_at, oc.created_at) AS created_at,
      COALESCE(cc.updated_at, oc.updated_at) AS updated_at,
      (oc.id IS NOT NULL) AS is_official,
      (oc.id IS NULL) AS is_custom,
      (cc.id IS NOT NULL AND oc.id IS NOT NULL) AS has_company_override
    FROM official_codes oc
    LEFT JOIN company_codes cc
      ON cc.code = oc.code

    UNION ALL

    SELECT
      cc.id AS id,
      cc.company_id AS company_id,
      cc.id AS company_row_id,
      NULL AS official_row_id,
      cc.code,
      cc.label,
      cc.cadre,
      cc.sub_cadre,
      cc.tax_category,
      cc.rate,
      cc.rate_type,
      cc.base_formula,
      cc.legal_reference,
      cc.is_deduction,
      cc.is_active,
      cc.sort_order,
      cc.created_at,
      cc.updated_at,
      FALSE AS is_official,
      TRUE AS is_custom,
      FALSE AS has_company_override
    FROM company_codes cc
    LEFT JOIN official_codes oc
      ON oc.code = cc.code
    WHERE oc.id IS NULL
  )
  SELECT *
  FROM merged_codes
  ${whereClause}
  ${orderClause}
`;

const getStoredTaxCodeById = async (db, taxCodeId) => {
  const { rows } = await db.query(
    `
      SELECT
        id,
        company_id,
        code,
        label,
        cadre,
        sub_cadre,
        tax_category,
        rate,
        rate_type,
        base_formula,
        legal_reference,
        is_deduction,
        is_active,
        sort_order,
        created_at,
        updated_at
      FROM fiscal_tax_codes
      WHERE id = $1
      LIMIT 1
    `,
    [taxCodeId],
  );

  return rows[0] || null;
};

const getMergedTaxCodeByCode = async (db, companyId, code) => {
  const query = buildMergedTaxCodesQuery({
    whereClause: "WHERE code = $2",
  });
  const { rows } = await db.query(query, [companyId, code]);
  return rows[0] ? mapTaxCodeRow(rows[0]) : null;
};

const getOfficialTaxCodeByCode = async (db, code) => {
  const { rows } = await db.query(
    `
      SELECT *
      FROM fiscal_tax_codes
      WHERE company_id IS NULL
        AND code = $1
      LIMIT 1
    `,
    [code],
  );

  return rows[0] || null;
};

const getCompanyTaxCodeByCode = async (db, companyId, code, excludeId = null) => {
  const params = [companyId, code];
  let whereClause = "company_id = $1 AND code = $2";

  if (excludeId) {
    params.push(excludeId);
    whereClause += ` AND id <> $${params.length}`;
  }

  const { rows } = await db.query(
    `
      SELECT *
      FROM fiscal_tax_codes
      WHERE ${whereClause}
      LIMIT 1
    `,
    params,
  );

  return rows[0] || null;
};

const ensureCodeAvailable = async ({
  db,
  companyId,
  code,
  excludeId = null,
  allowOfficialDuplicate = false,
}) => {
  const existingCompanyCode = await getCompanyTaxCodeByCode(db, companyId, code, excludeId);

  if (existingCompanyCode) {
    throw new ApiError(409, "Ce code fiscal existe déjà pour cette société.");
  }

  if (!allowOfficialDuplicate) {
    const officialCode = await getOfficialTaxCodeByCode(db, code);

    if (officialCode) {
      throw new ApiError(409, "Ce code fiscal officiel existe déjà.");
    }
  }
};

const normalizeTaxCodePayload = ({
  body,
  defaultCadre = null,
  defaultSubCadre = "",
  fallbackActive = true,
}) => {
  const code = cleanString(body.code).toUpperCase();
  const label = cleanString(body.label);
  const cadre = normalizeNullableInteger(body.cadre, "Le cadre", { min: 1, max: 8 }) ?? defaultCadre;
  const rawSubCadre = cleanString(body.sub_cadre || body.subCadre).toUpperCase();
  const subCadre = rawSubCadre || defaultSubCadre || "";
  const taxCategory = cleanString(body.tax_category || body.taxCategory).toUpperCase();
  const rateType = cleanString(body.rate_type || body.rateType).toLowerCase();
  const rate = normalizeNullableDecimal(body.rate, "Le taux");
  const baseFormula = cleanString(body.base_formula || body.baseFormula);
  const legalReference = cleanString(body.legal_reference || body.legalReference);
  const sortOrder = normalizeNullableInteger(body.sort_order || body.sortOrder, "L'ordre d'affichage", {
    min: 0,
    max: 999999,
  });
  const isDeduction = normalizeBoolean(body.is_deduction ?? body.isDeduction, false);
  const isActive = normalizeBoolean(body.is_active ?? body.isActive, fallbackActive);

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

  if (!/^[A-Z0-9]{2,10}$/.test(code)) {
    throw new ApiError(
      400,
      "Le code fiscal doit contenir entre 2 et 10 caractères alphanumériques.",
    );
  }

  if (!label) {
    throw new ApiError(400, "Le libellé du code fiscal est obligatoire.");
  }

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

  if (cadre === 8) {
    if (!SUB_CADRES.has(subCadre)) {
      throw new ApiError(400, "Le sous-cadre A, B ou C est requis pour le cadre 8.");
    }
  } else if (subCadre) {
    throw new ApiError(400, "Le sous-cadre est réservé au cadre 8.");
  }

  if (!TAX_CATEGORIES.has(taxCategory)) {
    throw new ApiError(400, "La catégorie fiscale est invalide.");
  }

  if (!RATE_TYPES.has(rateType)) {
    throw new ApiError(400, "Le type de taux est invalide.");
  }

  if (rateType === "fixed" && rate === null) {
    throw new ApiError(400, "Le taux fixe est obligatoire.");
  }

  if (rateType === "bareme" && rate !== null) {
    throw new ApiError(400, "Le taux ne doit pas être saisi pour un barème progressif.");
  }

  return {
    code,
    label,
    cadre,
    sub_cadre: cadre === 8 ? subCadre : null,
    tax_category: taxCategory,
    rate: rateType === "bareme" ? null : rate,
    rate_type: rateType,
    base_formula: baseFormula,
    legal_reference: legalReference,
    is_deduction: isDeduction,
    is_active: isActive,
    sort_order: sortOrder,
  };
};

const upsertCompanyOverrideFromOfficial = async ({
  db,
  companyId,
  officialCode,
  isActive,
}) => {
  const { rows } = await db.query(
    `
      INSERT INTO fiscal_tax_codes (
        company_id,
        code,
        label,
        cadre,
        sub_cadre,
        tax_category,
        rate,
        rate_type,
        base_formula,
        legal_reference,
        is_deduction,
        is_active,
        sort_order
      )
      VALUES (
        $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13
      )
      ON CONFLICT (company_id, code)
      WHERE company_id IS NOT NULL
      DO UPDATE SET
        label = EXCLUDED.label,
        cadre = EXCLUDED.cadre,
        sub_cadre = EXCLUDED.sub_cadre,
        tax_category = EXCLUDED.tax_category,
        rate = EXCLUDED.rate,
        rate_type = EXCLUDED.rate_type,
        base_formula = EXCLUDED.base_formula,
        legal_reference = EXCLUDED.legal_reference,
        is_deduction = EXCLUDED.is_deduction,
        is_active = EXCLUDED.is_active,
        sort_order = EXCLUDED.sort_order,
        updated_at = NOW()
      RETURNING id
    `,
    [
      companyId,
      officialCode.code,
      officialCode.label,
      officialCode.cadre,
      officialCode.sub_cadre,
      officialCode.tax_category,
      officialCode.rate,
      officialCode.rate_type,
      officialCode.base_formula,
      officialCode.legal_reference,
      officialCode.is_deduction,
      isActive,
      officialCode.sort_order,
    ],
  );

  return rows[0]?.id ? Number(rows[0].id) : null;
};

export const fiscalTaxCodesController = {
  list: async (req, res) => {
    const companyId = getCompanyId(req);
    ensureCompanyAccess(req, companyId);

    const search = cleanString(req.query.search);
    const cadre = normalizeNullableInteger(req.query.cadre, "Le cadre", { min: 1, max: 8 });
    const category = cleanString(req.query.category).toUpperCase();
    const active =
      req.query.active === undefined || req.query.active === null || req.query.active === ""
        ? null
        : normalizeBoolean(req.query.active, true);

    if (category && !TAX_CATEGORIES.has(category)) {
      throw new ApiError(400, "La catégorie fiscale est invalide.");
    }

    const params = [companyId];
    const whereParts = [];

    if (search) {
      params.push(search);
      const index = params.length;
      whereParts.push(`(code ILIKE '%' || $${index} || '%' OR label ILIKE '%' || $${index} || '%')`);
    }

    if (cadre) {
      params.push(cadre);
      whereParts.push(`cadre = $${params.length}`);
    }

    if (category) {
      params.push(category);
      whereParts.push(`tax_category = $${params.length}`);
    }

    if (active !== null) {
      params.push(active);
      whereParts.push(`is_active = $${params.length}`);
    }

    const query = buildMergedTaxCodesQuery({
      whereClause: whereParts.length ? `WHERE ${whereParts.join(" AND ")}` : "",
    });

    const { rows } = await pool.query(query, params);
    const items = rows.map(mapTaxCodeRow);

    res.json({
      items,
      meta: {
        company_id: companyId,
        total: items.length,
      },
    });
  },

  create: async (req, res) => {
    const companyId = getCompanyId(req, req.body);
    ensureCompanyAccess(req, companyId);

    const payload = normalizeTaxCodePayload({
      body: req.body,
      fallbackActive: true,
    });

    await ensureCodeAvailable({
      db: pool,
      companyId,
      code: payload.code,
    });

    const client = await pool.connect();

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

      const { rows } = await client.query(
        `
          INSERT INTO fiscal_tax_codes (
            company_id,
            code,
            label,
            cadre,
            sub_cadre,
            tax_category,
            rate,
            rate_type,
            base_formula,
            legal_reference,
            is_deduction,
            is_active,
            sort_order
          )
          VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
          RETURNING id
        `,
        [
          companyId,
          payload.code,
          payload.label,
          payload.cadre,
          payload.sub_cadre,
          payload.tax_category,
          payload.rate,
          payload.rate_type,
          payload.base_formula,
          payload.legal_reference,
          payload.is_deduction,
          payload.is_active,
          payload.sort_order,
        ],
      );

      const createdId = Number(rows[0].id);
      const item = await getMergedTaxCodeByCode(client, companyId, payload.code);

      await logAudit(client, {
        companyId,
        userId: req.user?.sub || null,
        action: "CREATE",
        entityType: "fiscal_tax_code",
        entityId: createdId,
        oldData: null,
        newData: item,
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.status(201).json({
        message: "Code fiscal créé.",
        item,
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },

  update: async (req, res) => {
    const companyId = getCompanyId(req, req.body);
    ensureCompanyAccess(req, companyId);

    const taxCodeId = Number(req.params.id || 0);

    if (!taxCodeId) {
      throw new ApiError(400, "Identifiant du code fiscal invalide.");
    }

    const client = await pool.connect();

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

      const existingRow = await getStoredTaxCodeById(client, taxCodeId);

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

      const matchingOfficialRow =
        existingRow.company_id === null
          ? existingRow
          : await getOfficialTaxCodeByCode(client, existingRow.code);
      const isOfficialContext = Boolean(matchingOfficialRow);

      if (existingRow.company_id !== null && Number(existingRow.company_id) !== companyId) {
        throw new ApiError(404, "Code fiscal introuvable.");
      }

      const previousItem = await getMergedTaxCodeByCode(client, companyId, existingRow.code);

      if (isOfficialContext) {
        const nextActive = normalizeBoolean(req.body.is_active ?? req.body.isActive, true);

        if (nextActive) {
          await client.query(
            `
              DELETE FROM fiscal_tax_codes
              WHERE company_id = $1
                AND code = $2
            `,
            [companyId, existingRow.code],
          );
        } else {
          await upsertCompanyOverrideFromOfficial({
            db: client,
            companyId,
            officialCode: matchingOfficialRow,
            isActive: false,
          });
        }

        const item = await getMergedTaxCodeByCode(
          client,
          companyId,
          matchingOfficialRow.code,
        );

        await logAudit(client, {
          companyId,
          userId: req.user?.sub || null,
          action: "UPDATE",
          entityType: "fiscal_tax_code",
          entityId: matchingOfficialRow.id,
          oldData: previousItem,
          newData: item,
          ipAddress: req.ip,
        });

        await client.query("COMMIT");

        res.json({
          message: "Statut du code fiscal officiel mis à jour.",
          item,
        });
        return;
      }

      const payload = normalizeTaxCodePayload({
        body: req.body,
        defaultCadre: existingRow.cadre,
        defaultSubCadre: existingRow.sub_cadre || "",
        fallbackActive: existingRow.is_active,
      });

      await ensureCodeAvailable({
        db: client,
        companyId,
        code: payload.code,
        excludeId: taxCodeId,
      });

      await client.query(
        `
          UPDATE fiscal_tax_codes
          SET
            code = $2,
            label = $3,
            cadre = $4,
            sub_cadre = $5,
            tax_category = $6,
            rate = $7,
            rate_type = $8,
            base_formula = $9,
            legal_reference = $10,
            is_deduction = $11,
            is_active = $12,
            sort_order = $13,
            updated_at = NOW()
          WHERE id = $1
        `,
        [
          taxCodeId,
          payload.code,
          payload.label,
          payload.cadre,
          payload.sub_cadre,
          payload.tax_category,
          payload.rate,
          payload.rate_type,
          payload.base_formula,
          payload.legal_reference,
          payload.is_deduction,
          payload.is_active,
          payload.sort_order,
        ],
      );

      const item = await getMergedTaxCodeByCode(client, companyId, payload.code);

      await logAudit(client, {
        companyId,
        userId: req.user?.sub || null,
        action: "UPDATE",
        entityType: "fiscal_tax_code",
        entityId: taxCodeId,
        oldData: previousItem,
        newData: item,
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.json({
        message: "Code fiscal mis à jour.",
        item,
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },

  remove: async (req, res) => {
    const companyId = getCompanyId(req);
    ensureCompanyAccess(req, companyId);

    const taxCodeId = Number(req.params.id || 0);

    if (!taxCodeId) {
      throw new ApiError(400, "Identifiant du code fiscal invalide.");
    }

    const client = await pool.connect();

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

      const existingRow = await getStoredTaxCodeById(client, taxCodeId);

      if (!existingRow || Number(existingRow.company_id || 0) !== companyId) {
        throw new ApiError(404, "Code fiscal introuvable.");
      }

      const matchingOfficial = await getOfficialTaxCodeByCode(client, existingRow.code);

      if (matchingOfficial) {
        throw new ApiError(
          400,
          "Les codes fiscaux officiels ne peuvent pas être supprimés. Réactivez le code officiel ou utilisez la réinitialisation.",
        );
      }

      await client.query(
        `
          DELETE FROM fiscal_tax_codes
          WHERE id = $1
            AND company_id = $2
        `,
        [taxCodeId, companyId],
      );

      await logAudit(client, {
        companyId,
        userId: req.user?.sub || null,
        action: "DELETE",
        entityType: "fiscal_tax_code",
        entityId: taxCodeId,
        oldData: mapTaxCodeRow({
          ...existingRow,
          company_row_id: existingRow.id,
          official_row_id: null,
          is_official: false,
          is_custom: true,
          has_company_override: false,
        }),
        newData: null,
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.json({
        message: "Code fiscal supprimé.",
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },

  resetOfficial: async (req, res) => {
    const companyId = getCompanyId(req, req.body);
    ensureCompanyAccess(req, companyId);

    const mode = cleanString(req.body.mode || "reset").toLowerCase() || "reset";

    if (!RESET_MODES.has(mode)) {
      throw new ApiError(400, "Le mode de réinitialisation est invalide.");
    }

    const client = await pool.connect();

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

      const beforeQuery = buildMergedTaxCodesQuery();
      const beforeResult = await client.query(beforeQuery, [companyId]);
      const beforeItems = beforeResult.rows.map(mapTaxCodeRow);

      const deleteResult =
        mode === "import"
          ? await client.query(
              `
                DELETE FROM fiscal_tax_codes company_codes
                USING fiscal_tax_codes official_codes
                WHERE company_codes.company_id = $1
                  AND official_codes.company_id IS NULL
                  AND official_codes.code = company_codes.code
              `,
              [companyId],
            )
          : await client.query(
              `
                DELETE FROM fiscal_tax_codes
                WHERE company_id = $1
              `,
              [companyId],
            );

      const afterResult = await client.query(beforeQuery, [companyId]);
      const afterItems = afterResult.rows.map(mapTaxCodeRow);

      await logAudit(client, {
        companyId,
        userId: req.user?.sub || null,
        action: mode === "import" ? "IMPORT" : "RESET",
        entityType: "fiscal_tax_code_catalog",
        entityId: null,
        oldData: {
          mode,
          items_count: beforeItems.length,
        },
        newData: {
          mode,
          removed_rows: Number(deleteResult.rowCount || 0),
          items_count: afterItems.length,
        },
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.json({
        message:
          mode === "import"
            ? "Les codes fiscaux officiels DGI ont été resynchronisés."
            : "Le catalogue fiscal a été réinitialisé aux codes officiels.",
        removed_rows: Number(deleteResult.rowCount || 0),
        items: afterItems,
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },
};
