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

const cleanString = (value) => String(value ?? "").trim();
const nullableString = (value) => {
  const cleaned = cleanString(value);
  return cleaned || null;
};
const nullableNumber = (value) => {
  if (value === "" || value === null || value === undefined) return null;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
};
const toNumber = (value, fallback = 0) => {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : fallback;
};

const getCompanyId = (req) => {
  const companyId = Number(req.query.company_id || req.body.company_id || req.user?.companyId);
  if (!companyId) throw new ApiError(400, "Société requise.");
  return companyId;
};

const mapRef = (prefix, row) => row[`${prefix}_id`] ? ({
  id: Number(row[`${prefix}_id`]),
  code: row[`${prefix}_code`],
  label: row[`${prefix}_label`],
  color: row[`${prefix}_color`],
  icon: row[`${prefix}_icon`],
  metadata: row[`${prefix}_metadata`] || {},
}) : null;

const depotShape = (row) => row.depot_id ? ({
  id: Number(row.depot_id),
  code: row.depot_code,
  label: row.depot_label,
}) : null;

const logMgxAudit = async (db, req, { companyId, action, entityType, entityId, oldData, newData }) => {
  await db.query(
    `
      INSERT INTO mgx_audit_log (
        company_id, user_id, action, entity_type, entity_id,
        old_data, new_data, ip_address
      )
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
    `,
    [
      companyId,
      req.user?.sub || null,
      action,
      entityType,
      entityId,
      oldData ? JSON.stringify(oldData) : null,
      newData ? JSON.stringify(newData) : null,
      req.ip,
    ],
  );
};

const assetSelect = `
  SELECT
    a.*,
    d.code AS depot_code,
    d.label AS depot_label,
    type_ref.id AS type_id,
    type_ref.code AS type_code,
    type_ref.label AS type_label,
    type_ref.color AS type_color,
    type_ref.icon AS type_icon,
    type_ref.metadata AS type_metadata,
    status.id AS status_id,
    status.code AS status_code,
    status.label AS status_label,
    status.color AS status_color,
    status.icon AS status_icon,
    status.metadata AS status_metadata
  FROM mgx_it_assets a
  LEFT JOIN mgx_depots d ON d.id = a.depot_id
  LEFT JOIN mgx_referentiels type_ref ON type_ref.id = a.type_ref_id
  LEFT JOIN mgx_referentiels status ON status.id = a.status_ref_id
`;

const mapAsset = (row) => ({
  id: Number(row.id),
  company_id: Number(row.company_id),
  depot_id: row.depot_id ? Number(row.depot_id) : null,
  depot: depotShape(row),
  reference: row.reference,
  asset_tag: row.asset_tag,
  name: row.name,
  type_ref_id: row.type_ref_id ? Number(row.type_ref_id) : null,
  type: mapRef("type", row),
  status_ref_id: row.status_ref_id ? Number(row.status_ref_id) : null,
  status: mapRef("status", row),
  brand: row.brand,
  model: row.model,
  serial_number: row.serial_number,
  assigned_to: row.assigned_to,
  location: row.location,
  ip_address: row.ip_address,
  mac_address: row.mac_address,
  os_name: row.os_name,
  acquisition_date: row.acquisition_date,
  warranty_end_date: row.warranty_end_date,
  acquisition_value: row.acquisition_value === null ? null : toNumber(row.acquisition_value),
  notes: row.notes,
  is_active: Boolean(row.is_active),
  created_at: row.created_at,
  updated_at: row.updated_at,
});

const nextAssetReference = async (db, companyId) => {
  const { rows } = await db.query(
    "SELECT COUNT(*)::int AS count FROM mgx_it_assets WHERE company_id = $1",
    [companyId],
  );
  return `IT-${String((rows[0]?.count || 0) + 1).padStart(4, "0")}`;
};

const buildPayload = (body, companyId) => ({
  company_id: companyId,
  depot_id: nullableNumber(body.depot_id),
  reference: nullableString(body.reference),
  asset_tag: nullableString(body.asset_tag),
  name: nullableString(body.name),
  type_ref_id: nullableNumber(body.type_ref_id),
  status_ref_id: nullableNumber(body.status_ref_id),
  brand: nullableString(body.brand),
  model: nullableString(body.model),
  serial_number: nullableString(body.serial_number),
  assigned_to: nullableString(body.assigned_to),
  location: nullableString(body.location),
  ip_address: nullableString(body.ip_address),
  mac_address: nullableString(body.mac_address),
  os_name: nullableString(body.os_name),
  acquisition_date: nullableString(body.acquisition_date),
  warranty_end_date: nullableString(body.warranty_end_date),
  acquisition_value: nullableNumber(body.acquisition_value),
  notes: nullableString(body.notes),
  is_active: body.is_active !== false,
});

const fetchAsset = async (db, id, companyId) => {
  const { rows } = await db.query(
    `
      ${assetSelect}
      WHERE a.id = $1
        AND a.company_id = $2
      LIMIT 1
    `,
    [id, companyId],
  );
  return rows[0] ? mapAsset(rows[0]) : null;
};

export const mgxItController = {
  async listAssets(req, res) {
    const companyId = getCompanyId(req);
    const conditions = ["a.company_id = $1", "a.is_active = true"];
    const values = [companyId];

    const search = cleanString(req.query.search).toLowerCase();
    if (search) {
      values.push(`%${search}%`);
      conditions.push(`(
        lower(a.reference) LIKE $${values.length}
        OR lower(COALESCE(a.asset_tag, '')) LIKE $${values.length}
        OR lower(a.name) LIKE $${values.length}
        OR lower(COALESCE(a.brand, '')) LIKE $${values.length}
        OR lower(COALESCE(a.model, '')) LIKE $${values.length}
        OR lower(COALESCE(a.serial_number, '')) LIKE $${values.length}
        OR lower(COALESCE(a.assigned_to, '')) LIKE $${values.length}
      )`);
    }

    for (const [queryKey, column] of [
      ["depot_id", "a.depot_id"],
      ["type", "a.type_ref_id"],
      ["status", "a.status_ref_id"],
    ]) {
      const value = nullableNumber(req.query[queryKey]);
      if (value) {
        values.push(value);
        conditions.push(`${column} = $${values.length}`);
      }
    }

    const { rows } = await pool.query(
      `
        ${assetSelect}
        WHERE ${conditions.join(" AND ")}
        ORDER BY a.reference ASC, a.id DESC
        LIMIT 500
      `,
      values,
    );

    res.json({ items: rows.map(mapAsset) });
  },

  async getAsset(req, res) {
    const companyId = getCompanyId(req);
    const item = await fetchAsset(pool, req.params.id, companyId);
    if (!item) throw new ApiError(404, "Matériel informatique introuvable.");
    res.json({ item });
  },

  async saveAsset(req, res) {
    const companyId = getCompanyId(req);
    const payload = buildPayload(req.body, companyId);
    if (!payload.name) throw new ApiError(400, "Le nom du matériel est requis.");

    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const isUpdate = Boolean(req.params.id);
      const oldData = isUpdate ? await fetchAsset(db, req.params.id, companyId) : null;
      if (isUpdate && !oldData) throw new ApiError(404, "Matériel informatique introuvable.");

      const reference = payload.reference || oldData?.reference || await nextAssetReference(db, companyId);
      let itemId;

      if (isUpdate) {
        const { rows } = await db.query(
          `
            UPDATE mgx_it_assets
            SET depot_id = $1,
                reference = $2,
                asset_tag = $3,
                name = $4,
                type_ref_id = $5,
                status_ref_id = $6,
                brand = $7,
                model = $8,
                serial_number = $9,
                assigned_to = $10,
                location = $11,
                ip_address = $12,
                mac_address = $13,
                os_name = $14,
                acquisition_date = $15,
                warranty_end_date = $16,
                acquisition_value = $17,
                notes = $18,
                is_active = $19,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = $20
              AND company_id = $21
            RETURNING id
          `,
          [
            payload.depot_id,
            reference,
            payload.asset_tag,
            payload.name,
            payload.type_ref_id,
            payload.status_ref_id,
            payload.brand,
            payload.model,
            payload.serial_number,
            payload.assigned_to,
            payload.location,
            payload.ip_address,
            payload.mac_address,
            payload.os_name,
            payload.acquisition_date,
            payload.warranty_end_date,
            payload.acquisition_value,
            payload.notes,
            payload.is_active,
            req.params.id,
            companyId,
          ],
        );
        itemId = Number(rows[0].id);
      } else {
        const { rows } = await db.query(
          `
            INSERT INTO mgx_it_assets (
              company_id, depot_id, reference, asset_tag, name, type_ref_id, status_ref_id,
              brand, model, serial_number, assigned_to, location, ip_address, mac_address,
              os_name, acquisition_date, warranty_end_date, acquisition_value, notes, is_active, created_by
            )
            VALUES (
              $1, $2, $3, $4, $5, $6, $7,
              $8, $9, $10, $11, $12, $13, $14,
              $15, $16, $17, $18, $19, $20, $21
            )
            RETURNING id
          `,
          [
            companyId,
            payload.depot_id,
            reference,
            payload.asset_tag,
            payload.name,
            payload.type_ref_id,
            payload.status_ref_id,
            payload.brand,
            payload.model,
            payload.serial_number,
            payload.assigned_to,
            payload.location,
            payload.ip_address,
            payload.mac_address,
            payload.os_name,
            payload.acquisition_date,
            payload.warranty_end_date,
            payload.acquisition_value,
            payload.notes,
            payload.is_active,
            req.user?.sub || null,
          ],
        );
        itemId = Number(rows[0].id);
      }

      const item = await fetchAsset(db, itemId, companyId);
      await logMgxAudit(db, req, {
        companyId,
        action: isUpdate ? "UPDATE" : "CREATE",
        entityType: "mgx_it_asset",
        entityId: itemId,
        oldData,
        newData: item,
      });
      await db.query("COMMIT");
      res.status(isUpdate ? 200 : 201).json({ item });
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },

  async deleteAsset(req, res) {
    const companyId = getCompanyId(req);
    const db = await pool.connect();
    try {
      await db.query("BEGIN");
      const oldData = await fetchAsset(db, req.params.id, companyId);
      if (!oldData) throw new ApiError(404, "Matériel informatique introuvable.");
      const { rows } = await db.query(
        `
          UPDATE mgx_it_assets
          SET is_active = false,
              updated_at = CURRENT_TIMESTAMP
          WHERE id = $1
            AND company_id = $2
          RETURNING id
        `,
        [req.params.id, companyId],
      );
      await logMgxAudit(db, req, {
        companyId,
        action: "DELETE",
        entityType: "mgx_it_asset",
        entityId: rows[0].id,
        oldData,
      });
      await db.query("COMMIT");
      res.json({ message: "Matériel informatique désactivé." });
    } catch (error) {
      await db.query("ROLLBACK");
      throw error;
    } finally {
      db.release();
    }
  },
};
