import "dotenv/config";
import express from "express";
import { createServer } from "http";
import net from "net";
import path from "node:path";
import { createExpressMiddleware } from "@trpc/server/adapters/express";
import { appRouter, computeReservationAnalytics, runStatsReportScheduler } from "../routers";
import { createContext } from "./context";
import { serveStatic, setupVite } from "./vite";
import { registerEmailActionRoutes } from "../emailActions";
import { registerSocialAuthRoutes } from "../socialAuth";
import { ENV } from "./env";
import { runMaterialReturnScheduler } from "../materialReturnWorkflow";
import { authenticateRequest, runUserPurgeScheduler } from "./auth";
import { withEffectiveInternalAccess } from "../internalAccess";
import { buildRuntimeHealth, ensureMaintenanceFlagDirectory, isMaintenanceModeEnabled, markRuntimeReady, markRuntimeShuttingDown } from "./runtime";

function isPortAvailable(port: number): Promise<boolean> {
  return new Promise(resolve => {
    const server = net.createServer();
    server.listen(port, () => {
      server.close(() => resolve(true));
    });
    server.on("error", () => resolve(false));
  });
}

async function findAvailablePort(startPort: number = 3000): Promise<number> {
  for (let port = startPort; port < startPort + 20; port++) {
    if (await isPortAvailable(port)) {
      return port;
    }
  }
  throw new Error(`No available port found starting from ${startPort}`);
}

async function startServer() {
  const app = express();
  const server = createServer(app);
  const schedulerTimers: NodeJS.Timeout[] = [];
  const openSockets = new Set<net.Socket>();
  let shutdownInFlight = false;

  await ensureMaintenanceFlagDirectory();

  server.on("connection", (socket) => {
    openSockets.add(socket);
    socket.on("close", () => {
      openSockets.delete(socket);
    });
  });

  async function sendHealth(res: express.Response, mode: "live" | "ready") {
    const health = await buildRuntimeHealth();
    const status = mode === "live"
      ? (health.checks.shuttingDown ? 503 : 200)
      : (health.ok ? 200 : 503);
    res.status(status).json(health);
  }

  function renderMaintenancePage() {
    return `<!doctype html>
<html lang="fr">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Maintenance en cours</title>
    <style>
      body{margin:0;font-family:Arial,sans-serif;background:#f6f9fc;color:#162133;display:grid;place-items:center;min-height:100vh;padding:24px}
      .panel{max-width:560px;background:#fff;border:1px solid #d5e2f3;border-radius:20px;padding:32px;box-shadow:0 12px 30px rgba(15,23,42,.08)}
      h1{margin:0 0 12px;font-size:32px;line-height:1.1}
      p{margin:0 0 10px;font-size:16px;line-height:1.6;color:#52627a}
      .badge{display:inline-flex;padding:6px 12px;border-radius:999px;background:#e8f2ff;color:#2c63c9;font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;margin-bottom:18px}
    </style>
  </head>
  <body>
    <main class="panel">
      <div class="badge">Maintenance</div>
      <h1>Le portail revient dans un instant</h1>
      <p>Nous appliquons une mise à jour de service pour garder une expérience stable, propre et fiable.</p>
      <p>Tu peux recharger la page dans quelques instants.</p>
    </main>
  </body>
</html>`;
  }

  async function gracefulShutdown(signal: string) {
    if (shutdownInFlight) return;
    shutdownInFlight = true;
    console.log(`[Runtime] graceful shutdown triggered by ${signal}`);
    markRuntimeShuttingDown();
    schedulerTimers.forEach(clearTimeout);
    schedulerTimers.splice(0, schedulerTimers.length);

    const forceCloseTimer = setTimeout(() => {
      for (const socket of Array.from(openSockets)) {
        socket.destroy();
      }
    }, ENV.gracefulShutdownTimeoutMs);

    await new Promise<void>((resolve) => {
      server.close(() => resolve());
    });

    clearTimeout(forceCloseTimer);
    process.exit(0);
  }

  process.on("SIGTERM", () => {
    void gracefulShutdown("SIGTERM");
  });
  process.on("SIGINT", () => {
    void gracefulShutdown("SIGINT");
  });

  // Configure body parser with larger size limit for file uploads
  app.use(express.json({ limit: "50mb" }));
  app.use(express.urlencoded({ limit: "50mb", extended: true }));
  app.use("/uploads", express.static(path.resolve(process.cwd(), "uploads")));
  app.get("/health/live", async (_req, res) => {
    await sendHealth(res, "live");
  });
  app.get("/health/ready", async (_req, res) => {
    await sendHealth(res, "ready");
  });
  app.get("/health", async (_req, res) => {
    await sendHealth(res, "ready");
  });
  // Email action routes for validate/refuse from email
  registerEmailActionRoutes(app);
  registerSocialAuthRoutes(app);
  app.get("/api/internal/stats/reservations", async (req, res) => {
    try {
      let authenticatedUser;
      try {
        authenticatedUser = await withEffectiveInternalAccess(await authenticateRequest(req));
      } catch {
        return res.status(401).json({ error: "Authentification requise" });
      }
      if (!authenticatedUser) {
        return res.status(401).json({ error: "Authentification requise" });
      }
      const allowedRoles = new Set(["accueil", "admin", "super_admin"]);
      if (!allowedRoles.has(authenticatedUser.role)) {
        return res.status(403).json({ error: "Accès réservé à l’accueil et aux administrateurs" });
      }

      const rawPeriod = typeof req.query.period === "string" ? req.query.period : "month";
      const period = ["7d", "month", "quarter", "semester", "year"].includes(rawPeriod) ? rawPeriod as "7d" | "month" | "quarter" | "semester" | "year" : "month";
      const payload = await computeReservationAnalytics(period);
      return res.json(payload);
    } catch {
      return res.status(500).json({ error: "Impossible de récupérer les statistiques" });
    }
  });
  // tRPC API
  app.use(
    "/api/trpc",
    createExpressMiddleware({
      router: appRouter,
      createContext,
    })
  );

  app.use(async (req, res, next) => {
    if (!["GET", "HEAD"].includes(req.method)) {
      next();
      return;
    }
    if (req.path.startsWith("/api") || req.path.startsWith("/health") || req.path.startsWith("/uploads")) {
      next();
      return;
    }
    if (!(await isMaintenanceModeEnabled())) {
      next();
      return;
    }
    res.status(503).type("html").send(renderMaintenancePage());
  });

  // development mode uses Vite, production mode uses static files
  if (process.env.NODE_ENV === "development") {
    await setupVite(app, server);
  } else {
    serveStatic(app);
  }

  const preferredPort = parseInt(process.env.PORT || "3000");
  const port = await findAvailablePort(preferredPort);

  if (port !== preferredPort) {
    console.log(`Port ${preferredPort} is busy, using port ${port} instead`);
  }

  server.listen(port, () => {
    console.log(`Server running on http://localhost:${port}/`);

    const baseUrl = (ENV.appBaseUrl || `http://localhost:${port}`).replace(/\/$/, "");
    const runScheduler = () => {
      runMaterialReturnScheduler(baseUrl).catch((error) => {
        console.error("[MaterialReturnScheduler] execution error:", error);
      });
      runStatsReportScheduler(baseUrl).catch((error) => {
        console.error("[StatsReportScheduler] execution error:", error);
      });
      runUserPurgeScheduler().catch((error) => {
        console.error("[UserPurgeScheduler] execution error:", error);
      });
    };

    markRuntimeReady();
    schedulerTimers.push(setTimeout(runScheduler, 5_000));
    schedulerTimers.push(setInterval(runScheduler, 60 * 60 * 1000));
  });
}

startServer().catch(console.error);
