const express = require("express");
const cors = require("cors");
const multer = require("multer");
const path = require("path");
const fs = require("fs").promises;
const helmet = require("helmet");

const app = express();
const PORT = process.env.PORT || 3001;

// Security middleware
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: [
          "'self'",
          "https://stats.schmelczer.dev",
          "'unsafe-inline'",
        ],
        scriptSrc: [
          "'self'",
          "https://stats.schmelczer.dev",
          "'unsafe-inline'",
        ],
      },
    },
  }),
);
app.use(
  cors({
    origin: process.env.FRONTEND_URL || "*",
    credentials: true,
  }),
);

app.use(express.json({ limit: "100mb" }));
app.use(express.static("public"));

// Serve the official Plausible tracker (npm: @plausible-analytics/tracker) from node_modules.
const plausibleTrackerPath =
  require.resolve("@plausible-analytics/tracker/plausible.js");

app.get("/vendor/plausible.js", (req, res) => {
  res.sendFile(plausibleTrackerPath);
});

// File paths
const DATA_PATH =
  process.env.DATA_PATH || path.join(__dirname, "../frontend/fizika.json");
const PICS_PATH =
  process.env.PICS_PATH || path.join(__dirname, "../frontend/pics");

// Reject any name that could escape the target directory (path traversal).
const isUnsafeFilename = (name) =>
  typeof name !== "string" ||
  name.length === 0 ||
  name.includes("/") ||
  name.includes("\\") ||
  name.includes("\0") ||
  name === "." ||
  name === ".." ||
  path.isAbsolute(name);

// Multer configuration for image uploads
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, PICS_PATH);
  },
  filename: (req, file, cb) => {
    // Strip any directory components the client may have sent so an upload
    // named e.g. "../../server.js" can never escape PICS_PATH.
    const name = path.basename(file.originalname);
    if (isUnsafeFilename(name)) {
      return cb(new Error("Invalid filename"));
    }
    cb(null, name);
  },
});

const upload = multer({
  storage: storage,
  limits: { fileSize: 50 * 1024 * 1024 }, // 50MB
  fileFilter: (req, file, cb) => {
    if (file.mimetype.startsWith("image/")) {
      cb(null, true);
    } else {
      cb(new Error("Only images allowed"));
    }
  },
});

// Utility functions
const readData = async () => {
  const data = await fs.readFile(DATA_PATH, "utf8");
  return JSON.parse(data);
};

const writeData = async (data) => {
  await fs.writeFile(DATA_PATH, JSON.stringify(data, null, 2));
};

// Public routes
app.get("/api/fizika", async (req, res) => {
  try {
    const data = await readData();
    res.json(data);
  } catch (error) {
    res.status(500).json({ error: "Failed to read data" });
  }
});

app.get("/api/images", async (req, res) => {
  try {
    const files = await fs.readdir(PICS_PATH);
    const images = files.filter((f) => /\.(jpg|jpeg|png|gif|bmp)$/i.test(f));
    res.json(images);
  } catch (error) {
    res.status(500).json({ error: "Failed to read images" });
  }
});

app.use("/api/pics", express.static(PICS_PATH));

// Admin routes (no auth required)
app.get("/api/admin/questions", async (req, res) => {
  try {
    const data = await readData();
    res.json(data);
  } catch (error) {
    res.status(500).json({ error: "Failed to read questions" });
  }
});

app.post("/api/admin/questions", async (req, res) => {
  try {
    const data = await readData();
    const maxId = Math.max(...data.map((q) => q.id), 0);

    const newQuestion = { id: maxId + 1, ...req.body };
    data.push(newQuestion);
    await writeData(data);

    res.status(201).json(newQuestion);
  } catch (error) {
    res.status(500).json({ error: "Failed to create question" });
  }
});

app.put("/api/admin/questions/:id", async (req, res) => {
  try {
    const data = await readData();
    const index = data.findIndex((q) => q.id === parseInt(req.params.id));

    if (index === -1) {
      return res.status(404).json({ error: "Question not found" });
    }

    data[index] = { ...data[index], ...req.body };
    await writeData(data);

    res.json(data[index]);
  } catch (error) {
    res.status(500).json({ error: "Failed to update question" });
  }
});

app.delete("/api/admin/questions/:id", async (req, res) => {
  try {
    const data = await readData();
    const index = data.findIndex((q) => q.id === parseInt(req.params.id));

    if (index === -1) {
      return res.status(404).json({ error: "Question not found" });
    }

    data.splice(index, 1);
    await writeData(data);

    res.json({ message: "Question deleted" });
  } catch (error) {
    res.status(500).json({ error: "Failed to delete question" });
  }
});

app.post("/api/admin/images/upload", upload.single("image"), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: "No image provided" });
  }

  res.json({
    filename: req.file.filename,
    path: `/api/pics/${req.file.filename}`,
  });
});

app.delete("/api/admin/images/:filename", async (req, res) => {
  if (isUnsafeFilename(req.params.filename)) {
    return res.status(400).json({ error: "Invalid filename" });
  }

  try {
    await fs.unlink(path.join(PICS_PATH, req.params.filename));
    res.json({ message: "Image deleted" });
  } catch (error) {
    if (error.code === "ENOENT") {
      return res.status(404).json({ error: "Image not found" });
    }
    res.status(500).json({ error: "Failed to delete image" });
  }
});

// Error handling
app.use((error, req, res, next) => {
  console.error("Error:", error.message);
  res.status(500).json({ error: "Server error" });
});

app.use((req, res) => {
  res.status(404).json({ error: "Not found" });
});

// Only start listening when run directly, so tests can import the app.
if (require.main === module) {
  app.listen(PORT, () => {
    console.log(`Fizika Admin Backend running on port ${PORT}`);
  });
}

module.exports = { app, isUnsafeFilename };
