import express from "express";
import helmet from "helmet";
import { env } from "./config/env";
import { requestContext } from "./shared/middleware/requestContext";
import { requestLogging } from "./shared/middleware/requestLogging";
import { rateLimit } from "./shared/middleware/rateLimit";
import { handleErrors, successResponse } from "./shared/middleware/errorMiddleware";
import { NextFunction, Request, Response } from "express";
import adminRoutes from "./routes/admin";
import integrationRoutes from "./routes/integrations";
import deviceRoutes from "./routes/device";
import merchantRoutes from "./routes/merchant";
import speakerRoutes from "./routes/speaker";
import { startNotificationOrchestrator } from "./shared/orchestrators/notificationOrchestrator";
import { startDynamicQrExpiryScheduler } from "./shared/services/dynamicQrExpiryScheduler";
import { startExportJobWorker } from "./shared/services/exportJobWorker";
import { startMqttSubscriber } from "./shared/services/mqttSubscriber";
import { getDatabaseHealth } from "./shared/services/health";
import path from "node:path";
import fs from "node:fs";

const app = express();
if (env.TRUST_PROXY === "true") {
  app.set("trust proxy", 1);
}

const allowedPreviewOrigins = new Set([
  "http://127.0.0.1:4173",
  "http://localhost:4173"
]);

app.use((req, res, next) => {
  const origin = req.header("origin");
  if (origin && allowedPreviewOrigins.has(origin)) {
    res.header("Access-Control-Allow-Origin", origin);
    res.header("Vary", "Origin");
    res.header("Access-Control-Allow-Headers", "authorization, content-type, x-request-id, idempotency-key");
    res.header("Access-Control-Allow-Methods", "GET,POST,PATCH,PUT,DELETE,OPTIONS");
  }
  if (req.method === "OPTIONS") {
    return res.sendStatus(204);
  }
  return next();
});

startNotificationOrchestrator();
startDynamicQrExpiryScheduler();
startExportJobWorker();
startMqttSubscriber();

app.use(
  helmet({
    hidePoweredBy: true,
    referrerPolicy: {
      policy: "no-referrer"
    },
    hsts:
      process.env.NODE_ENV === "production"
        ? {
            maxAge: 15552000,
            includeSubDomains: true
          }
        : false,
    crossOriginResourcePolicy: {
      policy: "cross-origin"
    },
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'", "https://cdn.tailwindcss.com"],
        scriptSrcAttr: ["'unsafe-inline'"],
        styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
        fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"],
        imgSrc: ["'self'", "data:", "https://lh3.googleusercontent.com", "https://*.googleusercontent.com"],
        connectSrc: ["'self'"],
        objectSrc: ["'none'"],
        baseUri: ["'self'"]
      }
    }
  })
);
app.use(express.json({ limit: env.JSON_BODY_LIMIT }));
app.use(requestContext);
app.use(requestLogging);

const loginLimiter = rateLimit({
  name: "login",
  windowMs: env.RATE_LIMIT_LOGIN_WINDOW_MS,
  max: env.RATE_LIMIT_LOGIN_MAX
});
const deviceLimiter = rateLimit({
  name: "device",
  windowMs: env.RATE_LIMIT_DEVICE_WINDOW_MS,
  max: env.RATE_LIMIT_DEVICE_MAX,
  key: (req) => `${req.ip}:${req.header("x-device-id") || "legacy"}`
});
const adminWriteLimiter = rateLimit({
  name: "admin_write",
  windowMs: env.RATE_LIMIT_ADMIN_WRITE_WINDOW_MS,
  max: env.RATE_LIMIT_ADMIN_WRITE_MAX,
  key: (req) => `${req.ip}:${req.header("authorization") || "anonymous"}`
});

app.get("/", (_req, res) => {
  res.redirect(302, "/ui/soundbox-ops");
});

app.get("/favicon.ico", (_req, res) => {
  res
    .type("image/svg+xml")
    .send(
      `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#004ac6"/><path fill="#fff" d="M16 17h12v12H16V17Zm4 4v4h4v-4h-4Zm16-4h12v12H36V17Zm4 4v4h4v-4h-4ZM16 35h12v12H16V35Zm4 4v4h4v-4h-4Zm18-4h4v4h-4v-4Zm6 0h4v10h-4V35Zm-10 6h8v4h-8v-4Zm0 8h4v4h-4v-4Zm8 0h10v4H42v-4Z"/></svg>`
    );
});

function resolveUiPageFile(slug: string) {
  const workspaceRoot = process.cwd();
  const candidates = [
    path.resolve(workspaceRoot, "ui", slug, "index.html"),
    path.resolve(workspaceRoot, "ui", slug.replace(/_/g, "-"), "index.html"),
    path.resolve(workspaceRoot, "ui", slug.replace(/-/g, "_"), "index.html")
  ];

  return candidates.find((candidate) => fs.existsSync(candidate)) || null;
}

app.get("/ui", (_req, res) => {
  res.redirect(302, "/ui/soundbox-ops");
});

app.get("/ui/hub", (_req, res) => {
  const filePath = path.resolve(process.cwd(), "ui/hub.html");
  res.sendFile(filePath);
});

app.use("/ui/shared", express.static(path.resolve(process.cwd(), "ui", "shared")));

app.get("/ui/:page", (req, res, next) => {
  const filePath = resolveUiPageFile(req.params.page);
  if (!filePath) {
    return next();
  }
  res.sendFile(filePath);
});

app.use("/admin/login", loginLimiter);
app.use("/merchant/login", loginLimiter);
app.use("/admin", (req, res, next) => {
  if (req.path === "/login") {
    return next();
  }
  if (["POST", "PATCH", "PUT", "DELETE"].includes(req.method)) {
    return adminWriteLimiter(req, res, next);
  }
  return next();
});
app.use("/device", deviceLimiter);
app.use("/speaker", deviceLimiter);
app.use("/integrations", rateLimit({ name: "integrations", windowMs: env.RATE_LIMIT_DEVICE_WINDOW_MS, max: env.RATE_LIMIT_DEVICE_MAX }));

app.use("/admin", adminRoutes);
app.use("/merchant", merchantRoutes);
app.use("/integrations", integrationRoutes);
app.use("/device", deviceRoutes);
app.use("/speaker", speakerRoutes);

app.use((err: Error, _req: Request, res: Response, next: NextFunction) => {
  handleErrors(err, _req, res, next);
});

app.get("/health", (req, res) => {
  res.status(200).json(
    successResponse(req, {
      status: "healthy",
      time: new Date().toISOString()
    })
  );
});

app.get("/health/deep", async (req, res) => {
  const database = await getDatabaseHealth();
  const healthy = database.status === "ok";
  res.status(healthy ? 200 : 503).json(
    successResponse(req, {
      status: healthy ? "healthy" : "degraded",
      time: new Date().toISOString(),
      checks: {
        database
      }
    })
  );
});

app.use((req, res) => {
  res.status(404).json({
    code: "NOT_FOUND",
    message: `Route ${req.path} not found`,
    request_id: req.requestId,
    timestamp: new Date().toISOString()
  });
});

export default app;
