const express = require("express");
const cors = require("cors");
const fs = require("fs");
const path = require("path");

const app = express();
const packagedOutputDir = path.resolve(__dirname, "data", "output");
const packagedInputDir = path.resolve(__dirname, "data", "input");
const AI_OUTPUT_DIR = process.env.AI_OUTPUT_DIR
  ? path.resolve(process.env.AI_OUTPUT_DIR)
  : fs.existsSync(packagedOutputDir)
    ? packagedOutputDir
    : path.resolve(__dirname, "..", "admin-app", "backend", "output");
const AI_INPUT_DIR = process.env.AI_INPUT_DIR
  ? path.resolve(process.env.AI_INPUT_DIR)
  : fs.existsSync(packagedInputDir)
    ? packagedInputDir
    : path.resolve(__dirname, "..", "admin-app", "backend", "ai", "input");
const CAMERA_CATALOG = [
  {
    id: "04",
    camera_key: "CCTV-04",
    label: "CCTV-04",
    area: "Journey Entrance",
    videoFile: "journey_entry.mp4",
    mode: "verified-timestamp-sync",
  },
  {
    id: "05",
    camera_key: "CCTV-05",
    label: "CCTV-05",
    area: "Journey Dining",
    videoFile: "journey_dining.mp4",
    mode: "verified-timestamp-sync",
  },
];
const FLAG_RULES = [
  {
    id: "greeting_staff_missing",
    name: "Greeting Staff Missing",
    trigger: "Customer enters but no greeting staff is present at the entrance",
    severity: "high",
    thresholdSeconds: 15,
    thresholdLabel: "15 seconds",
  },
  {
    id: "order_taking_delay",
    name: "Order Taking Delay",
    trigger: "Customer is seated but a waiter does not take the order within the timeout",
    severity: "medium",
    thresholdSeconds: 300,
    thresholdLabel: "5 minutes",
  },
  {
    id: "food_service_delay",
    name: "Food Service Delay",
    trigger: "Food is not served within the expected time after the order",
    severity: "high",
    thresholdSeconds: 600,
    thresholdLabel: "10 minutes",
  },
  {
    id: "customer_assistance_wave",
    name: "Customer Assistance Wave",
    trigger: "A seated customer repeatedly waves for assistance while no waiter responds",
    severity: "high",
    thresholdSeconds: 3,
    thresholdLabel: "3 seconds",
  },
  {
    id: "bill_service_delay",
    name: "Bill Service Delay",
    trigger: "Bill is not provided within the expected time after it is requested",
    severity: "medium",
    thresholdSeconds: 600,
    thresholdLabel: "10 minutes",
  },
  {
    id: "dirty_table_ignored",
    name: "Dirty Table Ignored",
    trigger: "A dirty table remains unattended while restaurant staff is nearby",
    severity: "high",
    thresholdSeconds: 180,
    thresholdLabel: "3 minutes",
  },
  {
    id: "staff_gathering",
    name: "Staff Gathering",
    trigger: "Three or more waiters remain idle together",
    severity: "medium",
    thresholdSeconds: 30,
    thresholdLabel: "30 seconds",
  },
  {
    id: "table_reset_delay",
    name: "Table Reset Delay",
    trigger: "A vacant table is not cleaned and reset within the timeout",
    severity: "medium",
    thresholdSeconds: 300,
    thresholdLabel: "5 minutes",
  },
];

const FRONTEND_ORIGINS = String(
  process.env.FRONTEND_ORIGINS ||
    "https://tltfrontend.loveconnectionltd.com,http://localhost:5173"
)
  .split(",")
  .map((origin) => origin.trim().replace(/\/$/, ""))
  .filter(Boolean);

app.use(
  cors({
    origin(origin, callback) {
      if (!origin || FRONTEND_ORIGINS.includes(origin.replace(/\/$/, ""))) {
        return callback(null, true);
      }
      return callback(new Error("Origin is not allowed by CORS"));
    },
    methods: ["GET", "POST", "PATCH", "OPTIONS"],
    allowedHeaders: ["Content-Type", "Authorization"],
  })
);
app.use(express.json());

function normalizeCameraId(value) {
  const digits = String(value || "05").replace(/\D/g, "");
  const normalized = digits.padStart(2, "0").slice(-2);
  return CAMERA_CATALOG.some((camera) => camera.id === normalized)
    ? normalized
    : null;
}

function cameraFilePath(cameraId, fileName) {
  return path.join(AI_OUTPUT_DIR, `cctv_${cameraId}`, fileName);
}

function legacyCameraOnePath(fileName) {
  return path.join(AI_OUTPUT_DIR, fileName);
}

function resolveCameraFile(cameraId, fileName) {
  const cameraPath = cameraFilePath(cameraId, fileName);
  if (fs.existsSync(cameraPath)) return cameraPath;
  if (cameraId === "01") {
    const legacyPath = legacyCameraOnePath(fileName);
    if (fs.existsSync(legacyPath)) return legacyPath;
  }
  return cameraPath;
}

function resolveCameraVideo(cameraId) {
  const camera = CAMERA_CATALOG.find((item) => item.id === cameraId);
  if (camera?.videoFile) {
    return path.join(AI_INPUT_DIR, camera.videoFile);
  }
  return resolveCameraFile(cameraId, "output.mp4");
}

function readAiJson(fileName, cameraId = "05") {
  const filePath = resolveCameraFile(cameraId, fileName);
  if (!fs.existsSync(filePath)) return null;

  try {
    return JSON.parse(fs.readFileSync(filePath, "utf8"));
  } catch (error) {
    console.error(`Could not read ${fileName}:`, error.message);
    return null;
  }
}

/* =====================================================
   HOME
===================================================== */

app.get("/", (req, res) => {
  res.send("Restaurant AI Server Running");
});

/* =====================================================
   LOGIN
===================================================== */

app.post("/api/auth/login", (req, res) => {
  const { username, password } = req.body;

  if (username === "admin@gmail.com" && password === "123123123") {
    return res.json({
      token: "restaurant-token",
      user: {
        name: "Restaurant Admin",
        email: "admin@gmail.com",
      },
    });
  }

  return res.status(401).json({
    error: "Invalid username or password",
  });
});

/* =====================================================
   DASHBOARD STATS
===================================================== */

app.get("/api/dashboard/stats", (req, res) => {
  const analyticsByCamera = CAMERA_CATALOG
    .map((camera) => readAiJson("analytics.json", camera.id))
    .filter(Boolean);
  if (!analyticsByCamera.length) {
    return res.status(503).json({ error: "AI analytics are not ready" });
  }

  res.json({
    totalCameras: CAMERA_CATALOG.length,
    activeCameras: analyticsByCamera.length,
    peopleDetected: analyticsByCamera.reduce(
      (sum, analytics) => sum + Number(analytics.customers || 0),
      0
    ),
    alertsToday: analyticsByCamera.reduce(
      (sum, analytics) => sum + Number(analytics.customers || 0),
      0
    ),
    systemStatus: "Running",
  });
});

/* =====================================================
   ANALYTICS
===================================================== */

app.get("/api/analytics", (req, res) => {
  const cameraId = normalizeCameraId(req.query.camera);
  if (!cameraId) {
    return res.status(400).json({ error: "Unknown camera" });
  }
  const analytics = readAiJson("analytics.json", cameraId);
  if (!analytics) {
    return res.status(503).json({ error: "AI analytics are not ready" });
  }
  res.set("Cache-Control", "no-store");
  res.json(analytics);
});

/* =====================================================
   DETECTIONS
===================================================== */

app.get("/api/detections", (req, res) => {
  const requestedCamera = req.query.camera
    ? normalizeCameraId(req.query.camera)
    : null;
  if (req.query.camera && !requestedCamera) {
    return res.status(400).json({ error: "Unknown camera" });
  }

  const detections = requestedCamera
    ? readAiJson("detections.json", requestedCamera)
    : CAMERA_CATALOG.flatMap(
      (camera) => readAiJson("detections.json", camera.id) || []
    );
  if (!detections || !detections.length) {
    return res.status(503).json({ error: "AI detections are not ready" });
  }
  res.set("Cache-Control", "no-store");
  res.json(detections);
});

app.get("/api/timeline", (req, res) => {
  const cameraId = normalizeCameraId(req.query.camera);
  if (!cameraId) {
    return res.status(400).json({ error: "Unknown camera" });
  }
  let timeline = readAiJson("timeline.json", cameraId);

  // Backward-compatible fallback for the already processed video. The next
  // AI run writes exact per-frame values instead of these real 5-second
  // customer averages.
  if (!timeline) {
    const analytics = readAiJson("analytics.json", cameraId);
    if (analytics?.hourlyData?.length) {
      timeline = analytics.hourlyData.map((item, index) => {
        const [minutes, seconds] = String(item.hour).split(":").map(Number);
        const customers = Number(item.customers) || 0;
        const waiters = Number(analytics.waiters) || 0;
        const tables = Number(analytics.tables) || 0;
        const chairs = Number(analytics.chairs) || 0;
        return {
          time: Number.isFinite(seconds)
            ? minutes * 60 + seconds
            : index * 5,
          customers,
          waiters,
          tables,
          occupiedTables: Number(analytics.occupiedTables) || 0,
          vacantTables: Number(analytics.vacantTables) || 0,
          chairs,
          total: customers + waiters + tables + chairs,
        };
      });
    }
  }

  if (!timeline) {
    return res.status(503).json({
      error: "Video timeline is not ready; run the AI pipeline again",
    });
  }
  res.set("Cache-Control", "no-store");
  res.json(timeline);
});

app.get("/api/activity", (req, res) => {
  const cameraId = normalizeCameraId(req.query.camera);
  if (!cameraId) {
    return res.status(400).json({ error: "Unknown camera" });
  }
  let activity = readAiJson("activity.json", cameraId) || [];

  if (!activity.length) {
    const timeline = readAiJson("timeline.json", cameraId) || [];
    activity = timeline
      .filter((_, index) => index % 75 === 0)
      .map((item) => ({
        time: item.time,
        personId: "AI",
        role: "system",
        tableId: null,
        action: "restaurant status updated",
        message: `${item.customers} customers, ${item.waiters} waiters, ${item.occupiedTables} occupied tables`,
      }));
  }

  res.set("Cache-Control", "no-store");
  res.json(activity);
});

/* =====================================================
   SERVICE FLAGS
===================================================== */

app.get("/api/flags", (req, res) => {
  const cameraId = normalizeCameraId(req.query.camera);
  if (!cameraId) {
    return res.status(400).json({ error: "Unknown camera" });
  }

  const storedFlags = readAiJson("flags.json", cameraId);
  const flags = Array.isArray(storedFlags)
    ? storedFlags
    : Array.isArray(storedFlags?.flags)
      ? storedFlags.flags
      : [];
  const requestedTime = Number(req.query.time);
  const hasPlaybackTime = Number.isFinite(requestedTime);
  const activeFlags = flags
    .filter((flag) => {
      if (hasPlaybackTime && Number.isFinite(Number(flag.raisedAtVideoTime))) {
        const raised = Number(flag.raisedAtVideoTime);
        const resolved = Number(flag.resolvedAtVideoTime);
        return requestedTime >= raised
          && (!Number.isFinite(resolved) || requestedTime < resolved);
      }
      return !["resolved", "dismissed"].includes(
        String(flag.status || "active").toLowerCase()
      );
    })
    .map((flag) => ({
      ...flag,
      status: "active",
      ageSeconds: hasPlaybackTime
        ? Math.max(0, requestedTime - Number(flag.raisedAtVideoTime || 0))
        : flag.ageSeconds,
    }));
  const resolvedToday = flags.filter((flag) => {
    if (hasPlaybackTime && Number.isFinite(Number(flag.resolvedAtVideoTime))) {
      return requestedTime >= Number(flag.resolvedAtVideoTime);
    }
    return String(flag.status || "").toLowerCase() === "resolved";
  }).length;

  res.set("Cache-Control", "no-store");
  res.json({
    cameraId,
    source: storedFlags?.source || (
      storedFlags ? "ai-event-engine" : "rules-ready-awaiting-event-engine"
    ),
    playbackTime: hasPlaybackTime ? requestedTime : null,
    rules: FLAG_RULES,
    flags: activeFlags,
    history: flags,
    summary: {
      active: activeFlags.length,
      high: activeFlags.filter((flag) => flag.severity === "high").length,
      medium: activeFlags.filter((flag) => flag.severity === "medium").length,
      resolved: resolvedToday,
    },
  });
});

app.get("/api/video", (req, res) => {
  const cameraId = normalizeCameraId(req.query.camera);
  if (!cameraId) {
    return res.status(400).json({ error: "Unknown camera" });
  }
  const videoPath = resolveCameraVideo(cameraId);
  if (!fs.existsSync(videoPath)) {
    return res.status(503).json({ error: "Processed video is not ready" });
  }
  res.set("Cache-Control", "no-store");
  res.sendFile(videoPath);
});

app.get("/api/status", (req, res) => {
  const cameraId = normalizeCameraId(req.query.camera);
  if (!cameraId) {
    return res.status(400).json({ error: "Unknown camera" });
  }
  const analytics = readAiJson("analytics.json", cameraId);
  res.set("Cache-Control", "no-store");
  res.json({
    success: true,
    cameraId,
    source: analytics?.source || "legacy-output",
    analytics: Boolean(analytics),
    detections: fs.existsSync(resolveCameraFile(cameraId, "detections.json")),
    timeline: fs.existsSync(resolveCameraFile(cameraId, "timeline.json")),
    activity: fs.existsSync(resolveCameraFile(cameraId, "activity.json")),
    video: fs.existsSync(resolveCameraVideo(cameraId)),
    lastUpdated: analytics?.lastUpdated || null,
  });
});

app.get("/api/journeys", (req, res) => {
  const cameraId = req.query.camera
    ? normalizeCameraId(req.query.camera)
    : null;
  if (req.query.camera && !cameraId) {
    return res.status(400).json({ error: "Unknown camera" });
  }
  const journeys = cameraId
    ? readAiJson("journeys.json", cameraId) || []
    : readAiJson("journeys.json", "05")
      || readAiJson("journeys.json")
      || [];
  res.set("Cache-Control", "no-store");
  res.json(journeys);
});

/* =====================================================
   CAMERAS
===================================================== */

app.get("/api/cameras", (req, res) => {
  res.json([
    {
      branch_key: "main",
      label: "Main Branch",
      cameras: CAMERA_CATALOG.map((camera) => ({
        ...camera,
        connected: fs.existsSync(resolveCameraVideo(camera.id)),
      })),
    },
  ]);
});

/* =====================================================
   SERVER
===================================================== */

const PORT = Number(process.env.PORT) || 4001;

const server = app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});

server.on("error", (error) => {
  console.error("Server failed to start:", error);
  process.exitCode = 1;
});

function stopServer(signal) {
  console.log(`\n${signal} received. Stopping server...`);
  server.close(() => process.exit(0));
}

process.on("SIGINT", () => stopServer("SIGINT"));
process.on("SIGTERM", () => stopServer("SIGTERM"));
