import express from "express";
import cors from "cors";
import rateLimit from "express-rate-limit";
import dotenv from "dotenv";
import path from "path";
import { fileURLToPath } from "url";
import { getBookRecommendations, searchBooks } from "./src/pineconeClient.js";

dotenv.config();

const app = express();

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

app.set("trust proxy", 1);

function normalizeOrigin(value) {
  if (!value || typeof value !== "string") {
    return null;
  }
  try {
    return new URL(value).origin;
  } catch {
    return null;
  }
}

const fallbackOrigins = ["http://localhost:3000", "http://127.0.0.1:3000"];
const configuredOrigins = (process.env.CORS_ALLOWED_ORIGINS || "")
  .split(",")
  .map((value) => normalizeOrigin(value.trim()))
  .filter(Boolean);

const allowedOrigins = new Set(configuredOrigins.length ? configuredOrigins : fallbackOrigins);

function isAllowedOrigin(origin) {
  const normalizedOrigin = normalizeOrigin(origin);
  return normalizedOrigin ? allowedOrigins.has(normalizedOrigin) : false;
}

const corsOptions = {
  origin(origin, callback) {
    if (!origin) {
      return callback(null, true);
    }
    if (isAllowedOrigin(origin)) {
      return callback(null, true);
    }
    return callback(null, false);
  },
  methods: ["GET", "POST", "OPTIONS"],
  allowedHeaders: ["Content-Type", "X-NextPick-Client"],
  optionsSuccessStatus: 204
};

const apiGlobalLimiter = rateLimit({
  windowMs: Number(process.env.API_RATE_LIMIT_WINDOW_MS || 60_000),
  max: Number(process.env.API_RATE_LIMIT_MAX || 120),
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: "Too many requests. Please try again in a moment." }
});

const recommendationsLimiter = rateLimit({
  windowMs: Number(process.env.RECOMMENDATIONS_RATE_LIMIT_WINDOW_MS || 900_000),
  max: Number(process.env.RECOMMENDATIONS_RATE_LIMIT_MAX || 20),
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: "Recommendation rate limit exceeded. Please try again later." }
});

function apiAbuseGate(req, res, next) {
  if (req.method === "OPTIONS") {
    return next();
  }

  const clientHeader = req.get("x-nextpick-client");
  if (clientHeader !== "web") {
    return res.status(403).json({ error: "Request blocked by abuse protection." });
  }

  const userAgent = req.get("user-agent") || "";
  if (userAgent.length < 12) {
    return res.status(403).json({ error: "Request blocked by abuse protection." });
  }

  if (req.method === "POST" && req.path === "/recommendations" && !req.is("application/json")) {
    return res.status(415).json({ error: "Content-Type must be application/json." });
  }

  return next();
}

app.use(cors(corsOptions));
app.use(express.json({ limit: "16kb" }));
app.use("/api", apiGlobalLimiter, apiAbuseGate);

app.use(express.static(path.join(__dirname, "public")));

app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "public", "index.html"));
});

app.get("/about", (req, res) => {
  res.sendFile(path.join(__dirname, "public", "about.html"));
});

app.get("/how-it-was-made", (req, res) => {
  res.sendFile(path.join(__dirname, "public", "how-it-was-made.html"));
});

// Canonical redirects from legacy .html URLs
app.get("/index.html", (req, res) => {
  res.redirect(301, "/");
});

app.get("/about.html", (req, res) => {
  res.redirect(301, "/about");
});

app.get("/how-it-was-made.html", (req, res) => {
  res.redirect(301, "/how-it-was-made");
});

app.get("/api/search", async (req, res) => {
  try {
    const q = req.query.q;
    if (!q || typeof q !== "string" || q.trim().length < 3) {
      return res.status(400).json({ error: "Query must be at least 3 characters.", results: [], found: false });
    }
    const data = await searchBooks(q.trim());
    return res.json(data);
  } catch (error) {
    console.error("Error in /api/search:", error);
    return res.status(500).json({ error: "Search failed.", results: [], found: false });
  }
});

app.post("/api/recommendations", recommendationsLimiter, async (req, res) => {
  try {
    const { lastBook, excludedBooks = [], excludeSeries = false } = req.body || {};

    if (!lastBook || typeof lastBook !== "string" || !lastBook.trim()) {
      return res.status(400).json({ error: "Please provide lastBook as a non-empty string." });
    }

    if (!Array.isArray(excludedBooks) || excludedBooks.some((value) => typeof value !== "string")) {
      return res.status(400).json({ error: "excludedBooks must be an array of strings." });
    }

    if (typeof excludeSeries !== "boolean") {
      return res.status(400).json({ error: "excludeSeries must be a boolean." });
    }

    const normalizedExclusions = excludedBooks.map((value) => value.trim()).filter(Boolean);
    const recommendations = await getBookRecommendations(lastBook.trim(), normalizedExclusions, { excludeSeries });
    return res.json({ recommendations });
  } catch (error) {
    console.error("Error in /api/recommendations:", error);
    return res.status(500).json({ error: "Something went wrong fetching recommendations." });
  }
});

app.use((req, res) => {
  res.status(404).sendFile(path.join(__dirname, "public", "404.html"));
});

const PORT = process.env.PORT || 3000;

if (process.env.NODE_ENV !== "production") {
  app.listen(PORT, () => {
    console.log(`Server is running locally at http://localhost:${PORT}`);
  });
}

export default app;
