import express, { type Express, type Request, type Response } from "express";

import { sendPush, validatePushRequest, type MessengerLike } from "./push";
import {
  checkRateLimit,
  DEFAULT_RATE_LIMIT,
  type RateLimitOptions,
  type RateLimitStore,
} from "./rateLimit";

export interface AppDependencies {
  messenger: MessengerLike;
  rateLimitStore: RateLimitStore;
  rateLimit?: RateLimitOptions;
}

// Identify the caller for rate limiting. Behind Cloud Functions the real client IP is the left-most
// entry of X-Forwarded-For (Google's proxy appends its own); `trust proxy` makes `req.ip` resolve to
// it. One home/deployment is roughly one public IP.
function clientKey(req: Request): string {
  return req.ip ?? "unknown";
}

// Build the relay HTTP app. Dependencies are injected so the routes can be exercised with a fake
// messenger and an in-memory rate-limit store in tests, and with the real Firebase ones in
// production (see index.ts).
export function createApp(deps: AppDependencies): Express {
  const app = express();
  // Trust Google's front-end proxy so req.ip is the caller, not the proxy.
  app.set("trust proxy", true);
  app.use(express.json({ limit: "256kb" }));

  // Liveness probe — no side effects, never rate limited. Note: the path
  // `/healthz` is reserved by Google Front End (it returns its own 404 before
  // the request reaches Cloud Run), so this uses `/health`.
  app.get("/health", (_req: Request, res: Response) => {
    res.status(200).send("ok");
  });

  app.post("/v1/push", async (req: Request, res: Response) => {
    try {
      const decision = await checkRateLimit(
        deps.rateLimitStore,
        clientKey(req),
        deps.rateLimit ?? DEFAULT_RATE_LIMIT,
      );
      if (!decision.allowed) {
        res.status(429).json({ error: "rate limit exceeded" });
        return;
      }

      const validation = validatePushRequest(req.body);
      if (!validation.ok) {
        res.status(400).json({ error: validation.error });
        return;
      }

      const results = await sendPush(deps.messenger, validation.value);
      res.status(200).json({ results });
    } catch (err) {
      // A messenger/transport failure is upstream's fault, not the caller's.
      const message = err instanceof Error ? err.message : "unknown error";
      console.error("Failed to relay push:", message);
      res.status(502).json({ error: "failed to deliver to FCM" });
    }
  });

  return app;
}
