import { Router, type Request, type Response, type NextFunction } from 'express';
import { timingSafeEqual } from 'node:crypto';
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { redis } from '../../config/redis';
import { env } from '../../config/env';
import { validate } from '../../middleware/validate';
import { authService } from './auth.service';
import { computeDeviceFingerprint } from '../../utils/device-fingerprint';
import { issueSsoTokenSchema, consumeSsoTokenSchema } from './sso-bridge.schemas';
import { ssoBridgeService } from './sso-bridge.service';
import { logger } from '../../utils/logger';

const router = Router();

const REFRESH_COOKIE_NAME = 'cml_refresh';
const REFRESH_COOKIE_MAX_AGE = 24 * 60 * 60 * 1000;

// Mirrors auth.routes.ts setRefreshCookie. Re-implemented here rather than
// imported because auth.routes.ts keeps it un-exported; copying preserves
// future independence of the SSO bridge if auth.routes.ts changes its cookie
// strategy.
function setRefreshCookie(req: Request, res: Response, token: string) {
  res.cookie(REFRESH_COOKIE_NAME, token, {
    httpOnly: true,
    secure: req.secure,
    sameSite: req.secure ? 'strict' : 'lax',
    maxAge: REFRESH_COOKIE_MAX_AGE,
    path: '/api/auth',
  });
}

const bridgeRateLimit = rateLimit({
  windowMs: 60_000,
  max: 60,
  standardHeaders: true,
  legacyHeaders: false,
  store: new RedisStore({
    sendCommand: (command: string, ...args: string[]) =>
      redis.call(command, ...args) as Promise<any>,
    prefix: 'rl:bridge-sso:',
  }),
  message: { error: { message: 'Bridge rate limit exceeded', code: 'BRIDGE_RATE_LIMIT' } },
});

// Used only on the issue endpoint (server-to-server). The consume endpoint is
// browser-facing and authorized by JWT possession instead.
function requireBridgeSecret(req: Request, res: Response, next: NextFunction) {
  if (!env.JSN_BRIDGE_SECRET) {
    return res.status(503).json({ error: 'BRIDGE_DISABLED' });
  }
  const header = req.get('authorization') ?? '';
  const expected = `Bearer ${env.JSN_BRIDGE_SECRET}`;
  const headerBuf = Buffer.from(header);
  const expectedBuf = Buffer.from(expected);
  if (
    headerBuf.length !== expectedBuf.length ||
    !timingSafeEqual(headerBuf, expectedBuf)
  ) {
    return res.status(401).json({ error: 'UNAUTHORIZED' });
  }
  next();
}

// JSN api calls this server-to-server. Returns a one-time URL.
router.post(
  '/bridge/issue-sso-token',
  bridgeRateLimit,
  requireBridgeSecret,
  validate(issueSsoTokenSchema),
  async (req, res, next) => {
    try {
      const result = await ssoBridgeService.issueToken(req.body);
      res.json(result);
    } catch (err) {
      next(err);
    }
  },
);

// Browser-facing. Verifies + deletes the one-time JWT, mints the normal cmlite
// refresh-token cookie, redirects to the cmlite SPA at the sanitized path.
// The SPA's auth store hydrates by calling /api/auth/refresh on mount, which
// reads the refresh cookie we just set.
router.get(
  '/bridge/sso',
  bridgeRateLimit,
  validate(consumeSsoTokenSchema, 'query'),
  async (req, res, next) => {
    try {
      const { token } = req.query as { token: string; redirectTo: string };
      const { user, redirectTo } = await ssoBridgeService.consumeToken(token);

      // Mint cmlite's normal refresh token + set the cookie. Device fingerprint
      // ties the refresh token to this user-agent/IP combo, same as login.
      const fingerprint = computeDeviceFingerprint(req);
      const tokens = await authService.generateTokenPair(
        { id: user.id, email: user.email, role: user.role as never },
        fingerprint,
      );
      setRefreshCookie(req, res, tokens.refreshToken);

      logger.info('SSO bridge redirect', { userId: user.id, redirectTo });

      // Redirect to the cmlite SPA. Cookie is set on /api/auth path (matches
      // cmlite's normal login behavior), so /api/auth/refresh from the SPA on
      // hydrate will see it.
      const target = `${env.ADMIN_URL}${redirectTo}`;
      res.redirect(302, target);
    } catch (err) {
      next(err);
    }
  },
);

export { router as ssoBridgeRouter };
