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

const HOUR_MS = 60 * 60 * 1000;

let alertEngineTimer = null;

const toNumber = (value, fallback = 0) => {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : fallback;
};

const startOfToday = () => {
  const now = new Date();
  return new Date(now.getFullYear(), now.getMonth(), now.getDate());
};

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

  const date = value instanceof Date ? value : new Date(value);

  if (Number.isNaN(date.getTime())) {
    return null;
  }

  return new Date(date.getFullYear(), date.getMonth(), date.getDate());
};

const daysBetween = (value, compareDate = startOfToday()) => {
  const date = parseDate(value);

  if (!date) {
    return null;
  }

  return Math.round((date.getTime() - compareDate.getTime()) / 86400000);
};

const addDays = (date, days) => {
  const next = new Date(date);
  next.setDate(next.getDate() + days);
  return next;
};

const getResolutionMessage = (entityLabel) =>
  `Alerte résolue automatiquement après mise à jour de ${entityLabel}.`;

const buildDocumentPayload = (config, row, today) => {
  const dueDate = parseDate(row.expiry_date);
  const daysRemaining = daysBetween(dueDate, today);
  const plate = row.plate_number || row.vehicle_reference || "Véhicule";
  const documentType = row.document_type_label || "Document";
  const ref = row.reference_number ? ` (${row.reference_number})` : "";
  const title = `${config.label} · ${plate}`;
  const message =
    daysRemaining !== null && daysRemaining < 0
      ? `${documentType}${ref} du véhicule ${plate} expiré depuis ${Math.abs(daysRemaining)} jour(s).`
      : `${documentType}${ref} du véhicule ${plate} arrive à échéance dans ${daysRemaining ?? 0} jour(s).`;

  return {
    entityType: "document",
    entityId: Number(row.id),
    companyId: Number(row.company_id),
    vehicleId: Number(row.vehicle_id),
    documentId: Number(row.id),
    consumableItemId: null,
    rentalId: null,
    dueDate,
    daysRemaining,
    title,
    message,
    meta: {
      plate_number: row.plate_number,
      depot_id: row.depot_id ? Number(row.depot_id) : null,
    },
  };
};

const buildMaintenancePayload = (config, row, today) => {
  const plate = row.plate_number || "Véhicule";
  const maintenanceType = row.maintenance_type_label || row.label || "Entretien";
  const dueDate = parseDate(row.next_due_date);
  const kmCurrent = toNumber(row.km_current, 0);
  const nextDueKm = row.next_due_km === null ? null : toNumber(row.next_due_km, 0);
  const isKmRule = config.trigger_field === "next_due_km";
  const remainingValue = isKmRule && nextDueKm !== null ? Math.round(nextDueKm - kmCurrent) : daysBetween(dueDate, today);
  const title = `${config.label} · ${plate}`;
  const message = isKmRule
    ? remainingValue < 0
      ? `${maintenanceType} du véhicule ${plate} en retard de ${Math.abs(remainingValue)} km.`
      : `${maintenanceType} du véhicule ${plate} à prévoir dans ${remainingValue} km.`
    : remainingValue !== null && remainingValue < 0
      ? `${maintenanceType} du véhicule ${plate} en retard de ${Math.abs(remainingValue)} jour(s).`
      : `${maintenanceType} du véhicule ${plate} à prévoir dans ${remainingValue ?? 0} jour(s).`;

  return {
    entityType: "maintenance",
    entityId: Number(row.vehicle_id),
    companyId: Number(row.company_id),
    vehicleId: Number(row.vehicle_id),
    documentId: null,
    consumableItemId: null,
    rentalId: null,
    dueDate,
    daysRemaining: remainingValue,
    title,
    message,
    meta: {
      plate_number: row.plate_number,
      depot_id: row.depot_id ? Number(row.depot_id) : null,
      trigger_field: config.trigger_field,
    },
  };
};

const buildRentalPayload = (config, row, today) => {
  const dueDate = parseDate(row.end_date);
  const daysRemaining = daysBetween(dueDate, today);
  const plate = row.plate_number || "Location";
  const supplier = row.supplier_name ? ` · ${row.supplier_name}` : "";
  const title = `${config.label} · ${plate}`;
  const message =
    daysRemaining !== null && daysRemaining < 0
      ? `La location ${plate}${supplier} est dépassée depuis ${Math.abs(daysRemaining)} jour(s).`
      : `La location ${plate}${supplier} prend fin dans ${daysRemaining ?? 0} jour(s).`;

  return {
    entityType: "rental",
    entityId: Number(row.id),
    companyId: Number(row.company_id),
    vehicleId: row.vehicle_id ? Number(row.vehicle_id) : null,
    documentId: null,
    consumableItemId: null,
    rentalId: Number(row.id),
    dueDate,
    daysRemaining,
    title,
    message,
    meta: {
      plate_number: row.plate_number,
      depot_id: row.depot_id ? Number(row.depot_id) : null,
    },
  };
};

const buildConsumablePayload = (config, row) => {
  const currentQty = toNumber(row.current_stock_qty, 0);
  const minQty = toNumber(row.min_stock_qty, 0);
  const delta = Math.round((currentQty - minQty) * 1000) / 1000;
  const itemLabel = row.label || "Article";
  const title = `${config.label} · ${itemLabel}`;
  const message =
    currentQty <= 0
      ? `Le stock de ${itemLabel} est en rupture (${currentQty}).`
      : `Le stock de ${itemLabel} est sous le seuil minimum (${currentQty} / ${minQty}).`;

  return {
    entityType: "consumable",
    entityId: Number(row.id),
    companyId: Number(row.company_id),
    vehicleId: null,
    documentId: null,
    consumableItemId: Number(row.id),
    rentalId: null,
    dueDate: null,
    daysRemaining: delta,
    title,
    message,
    meta: {
      depot_id: null,
      trigger_field: config.trigger_field,
    },
  };
};

const listCompaniesWithAlertConfigs = async (db) => {
  const { rows } = await db.query(
    `
      SELECT DISTINCT company_id
      FROM mgx_alert_configs
      WHERE is_active = TRUE
      ORDER BY company_id ASC
    `,
  );

  return rows.map((row) => Number(row.company_id)).filter(Boolean);
};

const fetchAlertConfigs = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT
        ac.*,
        rt.label AS alert_type_label,
        rt.color AS alert_type_color,
        rt.icon AS alert_type_icon
      FROM mgx_alert_configs ac
      LEFT JOIN mgx_referentiels rt ON rt.id = ac.alert_type_ref_id
      WHERE ac.company_id = $1
        AND ac.is_active = TRUE
      ORDER BY ac.id ASC
    `,
    [companyId],
  );

  return rows.map((row) => ({
    ...row,
    id: Number(row.id),
    company_id: Number(row.company_id),
    threshold_value: row.threshold_value === null ? null : Number(row.threshold_value),
  }));
};

const fetchCandidatesForConfig = async (db, config, today) => {
  const thresholdValue = toNumber(config.threshold_value, 0);

  if (config.trigger_entity === "document") {
    const thresholdDays = thresholdValue || 30;
    const limitDate = addDays(today, thresholdDays).toISOString().slice(0, 10);
    const { rows } = await db.query(
      `
        SELECT
          doc.*,
          v.reference AS vehicle_reference,
          v.plate_number,
          v.depot_id,
          type.label AS document_type_label
        FROM mgx_vehicle_documents doc
        JOIN mgx_vehicles v ON v.id = doc.vehicle_id
        LEFT JOIN mgx_referentiels type ON type.id = doc.document_type_ref_id
        WHERE doc.company_id = $1
          AND doc.status = 'active'
          AND doc.expiry_date IS NOT NULL
          AND doc.expiry_date <= $2
      `,
      [config.company_id, limitDate],
    );

    return rows.map((row) => buildDocumentPayload(config, row, today));
  }

  if (config.trigger_entity === "maintenance") {
    const { rows } = await db.query(
      `
        SELECT
          plan.*,
          vehicle.plate_number,
          vehicle.km_current,
          vehicle.depot_id,
          type.label AS maintenance_type_label
        FROM mgx_maintenance_plans plan
        JOIN mgx_vehicles vehicle ON vehicle.id = plan.vehicle_id
        LEFT JOIN mgx_referentiels type ON type.id = plan.maintenance_type_ref_id
        WHERE plan.company_id = $1
          AND plan.is_active = TRUE
      `,
      [config.company_id],
    );

    return rows
      .filter((row) => {
        if (config.trigger_field === "next_due_km") {
          if (row.next_due_km === null) {
            return false;
          }

          return toNumber(row.next_due_km, 0) <= toNumber(row.km_current, 0) + thresholdValue;
        }

        if (!row.next_due_date) {
          return false;
        }

        return parseDate(row.next_due_date)?.getTime() <= addDays(today, thresholdValue).getTime();
      })
      .map((row) => buildMaintenancePayload(config, row, today));
  }

  if (config.trigger_entity === "rental") {
    const thresholdDays = thresholdValue || 15;
    const limitDate = addDays(today, thresholdDays).toISOString().slice(0, 10);
    const { rows } = await db.query(
      `
        SELECT
          rental.*,
          vehicle.plate_number,
          vehicle.depot_id,
          supplier.name AS supplier_name,
          status.code AS status_code,
          status.label AS status_label
        FROM mgx_rentals rental
        LEFT JOIN mgx_vehicles vehicle ON vehicle.id = rental.vehicle_id
        LEFT JOIN mgx_suppliers supplier ON supplier.id = rental.supplier_id
        LEFT JOIN mgx_referentiels status ON status.id = rental.status_ref_id
        WHERE rental.company_id = $1
          AND rental.end_date IS NOT NULL
          AND rental.end_date <= $2
      `,
      [config.company_id, limitDate],
    );

    return rows
      .filter((row) => {
        const code = String(row.status_code || "").toLowerCase();
        const label = String(row.status_label || "").toLowerCase();

        return !["done", "cancel", "terminated", "closed"].includes(code) &&
          !label.includes("termin") &&
          !label.includes("résili") &&
          !label.includes("resili");
      })
      .map((row) => buildRentalPayload(config, row, today));
  }

  if (config.trigger_entity === "consumable") {
    const { rows } = await db.query(
      `
        SELECT *
        FROM mgx_consumable_items
        WHERE company_id = $1
          AND is_active = TRUE
          AND current_stock_qty <= min_stock_qty
      `,
      [config.company_id],
    );

    return rows.map((row) => buildConsumablePayload(config, row));
  }

  return [];
};

const findExistingActiveAlert = async (db, config, payload) => {
  const clauses = ["company_id = $1", "alert_config_id = $2", "status = 'active'"];
  const values = [config.company_id, config.id];

  if (payload.documentId) {
    values.push(payload.documentId);
    clauses.push(`document_id = $${values.length}`);
  } else if (payload.rentalId) {
    values.push(payload.rentalId);
    clauses.push(`rental_id = $${values.length}`);
  } else if (payload.consumableItemId) {
    values.push(payload.consumableItemId);
    clauses.push(`consumable_item_id = $${values.length}`);
  } else if (payload.vehicleId) {
    values.push(payload.vehicleId);
    clauses.push(`vehicle_id = $${values.length}`);
    clauses.push("document_id IS NULL");
    clauses.push("rental_id IS NULL");
    clauses.push("consumable_item_id IS NULL");
  } else {
    return null;
  }

  const { rows } = await db.query(
    `
      SELECT *
      FROM mgx_alerts
      WHERE ${clauses.join(" AND ")}
      ORDER BY id DESC
      LIMIT 1
    `,
    values,
  );

  return rows[0] || null;
};

const upsertAlert = async (db, config, payload) => {
  const existing = await findExistingActiveAlert(db, config, payload);

  if (existing) {
    const { rows } = await db.query(
      `
        UPDATE mgx_alerts
        SET
          title = $3,
          message = $4,
          due_date = $5,
          days_remaining = $6,
          criticality = $7
        WHERE id = $1 AND company_id = $2
        RETURNING *
      `,
      [
        existing.id,
        config.company_id,
        payload.title,
        payload.message,
        payload.dueDate ? payload.dueDate.toISOString().slice(0, 10) : null,
        payload.daysRemaining,
        config.criticality,
      ],
    );

    return { action: "updated", item: rows[0] };
  }

  const { rows } = await db.query(
    `
      INSERT INTO mgx_alerts (
        company_id,
        alert_config_id,
        vehicle_id,
        document_id,
        consumable_item_id,
        rental_id,
        title,
        message,
        due_date,
        days_remaining,
        criticality,
        status
      )
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 'active')
      RETURNING *
    `,
    [
      config.company_id,
      config.id,
      payload.vehicleId,
      payload.documentId,
      payload.consumableItemId,
      payload.rentalId,
      payload.title,
      payload.message,
      payload.dueDate ? payload.dueDate.toISOString().slice(0, 10) : null,
      payload.daysRemaining,
      config.criticality,
    ],
  );

  return { action: "created", item: rows[0] };
};

const resolveObsoleteAlerts = async (db, config, activeEntityKeys) => {
  const { rows } = await db.query(
    `
      SELECT *
      FROM mgx_alerts
      WHERE company_id = $1
        AND alert_config_id = $2
        AND status = 'active'
    `,
    [config.company_id, config.id],
  );

  let resolvedCount = 0;

  for (const row of rows) {
    const entityKey = row.document_id
      ? `document:${row.document_id}`
      : row.rental_id
        ? `rental:${row.rental_id}`
        : row.consumable_item_id
          ? `consumable:${row.consumable_item_id}`
          : row.vehicle_id
            ? config.trigger_entity === "maintenance"
              ? `maintenance:${row.vehicle_id}`
              : `vehicle:${row.vehicle_id}`
            : `alert:${row.id}`;

    if (activeEntityKeys.has(entityKey)) {
      continue;
    }

    await db.query(
      `
        UPDATE mgx_alerts
        SET
          status = 'cancelled',
          treated_at = CURRENT_TIMESTAMP,
          treatment_notes = COALESCE(treatment_notes, $3)
        WHERE id = $1 AND company_id = $2
      `,
      [row.id, config.company_id, getResolutionMessage(config.trigger_entity)],
    );
    resolvedCount += 1;
  }

  return resolvedCount;
};

export const getMgxAlertSummary = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT
        COUNT(*) FILTER (WHERE criticality = 'critical')::int AS critical,
        COUNT(*) FILTER (WHERE criticality = 'warning')::int AS warning,
        COUNT(*) FILTER (WHERE criticality = 'info')::int AS info
      FROM mgx_alerts
      WHERE company_id = $1
        AND status = 'active'
    `,
    [companyId],
  );

  return {
    critical: Number(rows[0]?.critical || 0),
    warning: Number(rows[0]?.warning || 0),
    info: Number(rows[0]?.info || 0),
  };
};

export const runMgxAlertEngine = async ({ companyId, io } = {}) => {
  const db = await pool.connect();

  try {
    const today = startOfToday();
    const companies = companyId ? [Number(companyId)] : await listCompaniesWithAlertConfigs(db);
    const results = [];

    for (const currentCompanyId of companies) {
      const configs = await fetchAlertConfigs(db, currentCompanyId);
      let created = 0;
      let updated = 0;
      let resolved = 0;
      let newCritical = 0;

      for (const config of configs) {
        const candidates = await fetchCandidatesForConfig(db, config, today);
        const activeEntityKeys = new Set();

        for (const candidate of candidates) {
          const entityKey = `${candidate.entityType}:${candidate.entityId}`;
          activeEntityKeys.add(entityKey);
          const outcome = await upsertAlert(db, config, candidate);

          if (outcome.action === "created") {
            created += 1;

            if (config.criticality === "critical") {
              newCritical += 1;
            }
          } else {
            updated += 1;
          }
        }

        resolved += await resolveObsoleteAlerts(db, config, activeEntityKeys);
      }

      const summary = await getMgxAlertSummary(db, currentCompanyId);
      const payload = {
        company_id: currentCompanyId,
        ...summary,
        created,
        updated,
        resolved,
        new_critical: newCritical,
      };

      io?.to(`company:${currentCompanyId}`).emit("alerts:updated", payload);
      results.push(payload);
    }

    return companyId ? results[0] || { company_id: Number(companyId), critical: 0, warning: 0, info: 0, created: 0, updated: 0, resolved: 0, new_critical: 0 } : results;
  } finally {
    db.release();
  }
};

export const initializeMgxAlertEngine = (io) => {
  if (alertEngineTimer) {
    return alertEngineTimer;
  }

  setTimeout(() => {
    runMgxAlertEngine({ io }).catch((error) => {
      console.error("MGX alert engine startup run failed", error);
    });
  }, 1500);

  alertEngineTimer = setInterval(() => {
    runMgxAlertEngine({ io }).catch((error) => {
      console.error("MGX alert engine scheduled run failed", error);
    });
  }, HOUR_MS);

  return alertEngineTimer;
};
