import { Router } from "express";
import crypto from "node:crypto";
import { z } from "zod";
import { prisma } from "../../lib/prisma";
import { requireAuth, requireCapability } from "../../middleware/auth";
import { getDataOwnerId } from "../../lib/data-owner";
import { evaluateGeofence, formatAlert } from "../../lib/geofence";
import { evaluateBatteryAlert, formatBattery } from "../../lib/battery-alert";
import { sendText } from "../../lib/evolution";

export const trackingRouter = Router();

// ─── Ingestion OwnTracks (PAS d'auth JWT : l'appareil s'authentifie par son jeton dans l'URL) ──
// URL à configurer dans OwnTracks (mode HTTP privé) :
//   https://leperelion.fr/backend/track/owntracks/<pairingToken>
trackingRouter.post("/owntracks/:token", async (req, res) => {
  const device = await prisma.device.findUnique({ where: { pairingToken: String(req.params.token) } });
  if (!device) return res.status(404).json([]);

  const body = req.body ?? {};
  // On accepte les positions (location) et les transitions de zone (transition).
  if ((body._type === "location" || body._type === "transition") && typeof body.lat === "number" && typeof body.lon === "number") {
    await prisma.locationPoint.create({
      data: {
        deviceId: device.id,
        lat: body.lat,
        lng: body.lon,
        accuracy: typeof body.acc === "number" ? body.acc : null,
        battery: typeof body.batt === "number" ? Math.round(body.batt) : null,
        recordedAt: body.tst ? new Date(body.tst * 1000) : new Date(),
      },
    });
    await prisma.device.update({ where: { id: device.id }, data: { lastSeenAt: new Date() } });
    // Alertes (ne bloquent jamais l'ingestion).
    await evaluateGeofence(device.id, body.lat, body.lon);
    if (typeof body.batt === "number") await evaluateBatteryAlert(device.id, Math.round(body.batt));
  }

  // Commandes en attente, renvoyées au téléphone à ce contact (mode HTTP).
  const cmds: unknown[] = [];
  if (device.cmdReportLocation) cmds.push({ _type: "cmd", action: "reportLocation" });
  if (device.cmdSyncWaypoints) {
    const geo = await prisma.geofence.findUnique({ where: { deviceId: device.id } });
    const waypoints =
      geo && geo.enabled
        ? [{ _type: "waypoint", desc: `leperelion-${device.name}`, lat: geo.refLat, lon: geo.refLng, rad: geo.radiusM, tst: Math.floor(Date.now() / 1000) }]
        : [];
    cmds.push({ _type: "cmd", action: "setWaypoints", waypoints: { _type: "waypoints", waypoints } });
  }
  if (device.cmdReportLocation || device.cmdSyncWaypoints) {
    await prisma.device.update({ where: { id: device.id }, data: { cmdReportLocation: false, cmdSyncWaypoints: false } });
  }
  return res.json(cmds);
});

// ─── Modèle « compte partagé » : les appareils suivis appartiennent au propriétaire (Super Admin).
// Lecture (carte/historique) : ouverte à tout utilisateur authentifié.
// Gestion (créer/jeton/supprimer) : réservée au Super Admin (capacité config:manage).
async function ownerDevice(deviceId: string) {
  const ownerId = await getDataOwnerId();
  if (!ownerId) return null;
  const device = await prisma.device.findUnique({ where: { id: deviceId } });
  if (!device || device.userId !== ownerId) return null;
  return device;
}

// Liste des appareils du propriétaire (avec la dernière position connue).
trackingRouter.get("/devices", requireAuth, async (_req, res) => {
  const ownerId = await getDataOwnerId();
  if (!ownerId) return res.json({ devices: [] });
  const devices = await prisma.device.findMany({
    where: { userId: ownerId },
    orderBy: { createdAt: "asc" },
    include: {
      locations: { orderBy: { recordedAt: "desc" }, take: 1 },
      geofence: { select: { refLat: true, refLng: true, radiusM: true, enabled: true } },
    },
  });
  res.json({
    devices: devices.map((d) => ({
      id: d.id,
      name: d.name,
      lastSeenAt: d.lastSeenAt,
      createdAt: d.createdAt,
      lastPoint: d.locations[0]
        ? {
            lat: d.locations[0].lat,
            lng: d.locations[0].lng,
            battery: d.locations[0].battery,
            recordedAt: d.locations[0].recordedAt,
          }
        : null,
      // Zone d'alerte (lecture seule) — visible par tous pour l'affichage carte.
      geofence: d.geofence
        ? { refLat: d.geofence.refLat, refLng: d.geofence.refLng, radiusM: d.geofence.radiusM, enabled: d.geofence.enabled }
        : null,
    })),
  });
});

// Historique des positions d'un appareil sur une plage (lecture, tout utilisateur).
trackingRouter.get("/devices/:id/points", requireAuth, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });

  const from = req.query.from ? new Date(String(req.query.from)) : new Date(Date.now() - 24 * 3600 * 1000);
  const to = req.query.to ? new Date(String(req.query.to)) : new Date();
  const limit = Math.min(Number(req.query.limit) || 2000, 5000);

  const points = await prisma.locationPoint.findMany({
    where: { deviceId: device.id, recordedAt: { gte: from, lte: to } },
    orderBy: { recordedAt: "asc" },
    take: limit,
    select: { lat: true, lng: true, accuracy: true, battery: true, recordedAt: true },
  });
  res.json({ points });
});

// ── Gestion des appareils : Super Admin uniquement ──
const manage = [requireAuth, requireCapability("config:manage")] as const;

const createSchema = z.object({ name: z.string().min(1).max(80) });
trackingRouter.post("/devices", ...manage, async (req, res) => {
  const parsed = createSchema.safeParse(req.body);
  if (!parsed.success) return res.status(400).json({ error: "Nom d'appareil requis" });
  const ownerId = await getDataOwnerId();
  if (!ownerId) return res.status(409).json({ error: "Aucun propriétaire" });
  const token = crypto.randomBytes(24).toString("hex");
  const device = await prisma.device.create({
    data: { userId: ownerId, name: parsed.data.name, pairingToken: token },
  });
  res.status(201).json({ device: { id: device.id, name: device.name, pairingToken: device.pairingToken } });
});

trackingRouter.get("/devices/:id/token", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  res.json({ pairingToken: device.pairingToken });
});

trackingRouter.post("/devices/:id/regenerate", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  const token = crypto.randomBytes(24).toString("hex");
  await prisma.device.update({ where: { id: device.id }, data: { pairingToken: token } });
  res.json({ pairingToken: token });
});

trackingRouter.delete("/devices/:id", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  await prisma.device.delete({ where: { id: device.id } });
  res.json({ ok: true });
});

// Demander une position immédiate (commande envoyée au prochain contact OwnTracks).
trackingRouter.post("/devices/:id/locate", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  await prisma.device.update({ where: { id: device.id }, data: { cmdReportLocation: true } });
  res.json({ ok: true });
});

// (Re)pousser la zone (waypoint) vers le téléphone au prochain contact.
trackingRouter.post("/devices/:id/sync-waypoints", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  await prisma.device.update({ where: { id: device.id }, data: { cmdSyncWaypoints: true } });
  res.json({ ok: true });
});

// ── Alerte de zone (geofence) — Super Admin ──
trackingRouter.get("/devices/:id/geofence", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  const geofence = await prisma.geofence.findUnique({ where: { deviceId: device.id } });
  res.json({ geofence });
});

const geofenceSchema = z.object({
  refLat: z.number().min(-90).max(90),
  refLng: z.number().min(-180).max(180),
  radiusM: z.number().int().min(10).max(100000).default(50),
  // Cible : un identifiant de conversation WhatsApp (jid, ex. ...@lid / ...@s.whatsapp.net /
  // ...@g.us) ou un numéro international. Stocké tel quel (sendText accepte les deux).
  notifyNumber: z.string().min(3).max(64),
  message: z.string().max(1000).optional(),
  enabled: z.boolean().default(true),
});

trackingRouter.put("/devices/:id/geofence", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  const parsed = geofenceSchema.safeParse(req.body);
  if (!parsed.success) return res.status(400).json({ error: "Champs invalides" });
  const { refLat, refLng, radiusM, notifyNumber, enabled } = parsed.data;
  const target = notifyNumber.trim();
  const message = parsed.data.message?.trim() || null;

  // Toute modification réinitialise l'état (re-calibrage du « dedans/dehors »).
  const geofence = await prisma.geofence.upsert({
    where: { deviceId: device.id },
    create: { deviceId: device.id, refLat, refLng, radiusM, notifyNumber: target, message, enabled, lastInside: null },
    update: { refLat, refLng, radiusM, notifyNumber: target, message, enabled, lastInside: null, lastNotifiedAt: null },
  });
  // Pousser la zone vers le téléphone au prochain contact (geofence côté appareil).
  await prisma.device.update({ where: { id: device.id }, data: { cmdSyncWaypoints: true } });
  res.json({ geofence });
});

// Envoi d'un message de test (avec valeurs d'exemple) vers la cible choisie.
const testSchema = z.object({
  notifyNumber: z.string().min(3).max(64),
  message: z.string().max(1000).optional(),
  radiusM: z.number().int().min(10).max(100000).optional(),
});
trackingRouter.post("/devices/:id/geofence/test", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  const parsed = testSchema.safeParse(req.body);
  if (!parsed.success) return res.status(400).json({ error: "Choisissez une conversation cible." });

  const lp = await prisma.locationPoint.findFirst({
    where: { deviceId: device.id },
    orderBy: { recordedAt: "desc" },
  });
  const lat = lp?.lat ?? 48.8566;
  const lng = lp?.lng ?? 2.3522;
  const text =
    "🧪 (Test) " +
    formatAlert(parsed.data.message ?? null, {
      device: device.name,
      distance: (parsed.data.radiusM ?? 50) + 102,
      radius: parsed.data.radiusM ?? 50,
      lat,
      lng,
    });

  try {
    await sendText(parsed.data.notifyNumber.trim(), text);
    res.json({ ok: true });
  } catch (e) {
    res.status(502).json({ error: "Échec de l'envoi du test (WhatsApp connecté ?)." });
  }
});

trackingRouter.delete("/devices/:id/geofence", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  await prisma.geofence.deleteMany({ where: { deviceId: device.id } });
  // Pousser la suppression de la zone (waypoints vides) au prochain contact.
  await prisma.device.update({ where: { id: device.id }, data: { cmdSyncWaypoints: true } });
  res.json({ ok: true });
});

// ── Alerte batterie faible (Super Admin) ──
trackingRouter.get("/devices/:id/battery-alert", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  const batteryAlert = await prisma.batteryAlert.findUnique({ where: { deviceId: device.id } });
  res.json({ batteryAlert });
});

const batterySchema = z.object({
  threshold: z.number().int().min(1).max(100).default(20),
  notifyNumber: z.string().min(3).max(64),
  message: z.string().max(1000).optional(),
  enabled: z.boolean().default(true),
});

trackingRouter.put("/devices/:id/battery-alert", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  const parsed = batterySchema.safeParse(req.body);
  if (!parsed.success) return res.status(400).json({ error: "Champs invalides" });
  const { threshold, notifyNumber, enabled } = parsed.data;
  const message = parsed.data.message?.trim() || null;

  const batteryAlert = await prisma.batteryAlert.upsert({
    where: { deviceId: device.id },
    create: { deviceId: device.id, threshold, notifyNumber: notifyNumber.trim(), message, enabled, lastBelow: null },
    update: { threshold, notifyNumber: notifyNumber.trim(), message, enabled, lastBelow: null, lastNotifiedAt: null },
  });
  res.json({ batteryAlert });
});

trackingRouter.delete("/devices/:id/battery-alert", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  await prisma.batteryAlert.deleteMany({ where: { deviceId: device.id } });
  res.json({ ok: true });
});

const batteryTestSchema = z.object({
  notifyNumber: z.string().min(3).max(64),
  message: z.string().max(1000).optional(),
  threshold: z.number().int().min(1).max(100).optional(),
});
trackingRouter.post("/devices/:id/battery-alert/test", ...manage, async (req, res) => {
  const device = await ownerDevice(String(req.params.id));
  if (!device) return res.status(404).json({ error: "Appareil introuvable" });
  const parsed = batteryTestSchema.safeParse(req.body);
  if (!parsed.success) return res.status(400).json({ error: "Choisissez une conversation cible." });
  const threshold = parsed.data.threshold ?? 20;
  const text =
    "🧪 (Test) " +
    formatBattery(parsed.data.message ?? null, {
      device: device.name,
      battery: Math.max(1, threshold - 5),
      threshold,
    });
  try {
    await sendText(parsed.data.notifyNumber.trim(), text);
    res.json({ ok: true });
  } catch {
    res.status(502).json({ error: "Échec de l'envoi du test (WhatsApp connecté ?)." });
  }
});
