import jwt from 'jsonwebtoken';
import { randomUUID } from 'node:crypto';
import { prisma } from '../../config/database';
import { redis } from '../../config/redis';
import { env } from '../../config/env';
import { AppError } from '../../middleware/error-handler';
import { logger } from '../../utils/logger';
import { authService } from './auth.service';
import { sanitizeRedirectTo } from './sso-bridge.schemas';
import type { IssueSsoTokenInput } from './sso-bridge.schemas';

const TOKEN_TTL_SECONDS = 60;
const TOKEN_ALGORITHM = 'HS256' as const;

interface SsoTokenPayload {
  sub: string; // cmlite user id
  jti: string; // unique id used for the one-time-use Redis sentinel
  redirectTo: string;
}

export const ssoBridgeService = {
  async issueToken(input: IssueSsoTokenInput): Promise<{ url: string }> {
    if (!env.SSO_BRIDGE_SECRET) {
      throw new AppError(503, 'SSO bridge not configured', 'SSO_BRIDGE_DISABLED');
    }
    const email = input.email.trim().toLowerCase();
    const redirectTo = sanitizeRedirectTo(input.redirectTo);

    const user = await prisma.user.findUnique({
      where: { email },
      select: { id: true },
    });
    if (!user) {
      throw new AppError(404, 'No cmlite user for this email', 'NO_CMLITE_USER');
    }

    const jti = randomUUID();
    const payload: SsoTokenPayload = { sub: user.id, jti, redirectTo };
    const token = jwt.sign(payload, env.SSO_BRIDGE_SECRET, {
      algorithm: TOKEN_ALGORITHM,
      expiresIn: TOKEN_TTL_SECONDS,
    });

    // One-time-use sentinel. Setting with NX prevents accidental overwrite of
    // an unrelated key (impossibly low collision risk on uuid, but cheap to
    // be paranoid). EX matches the JWT expiry so Redis cleans up on its own.
    await redis.set(`sso:${jti}`, '1', 'EX', TOKEN_TTL_SECONDS, 'NX');

    const url = `${env.API_URL}/api/auth/bridge/sso?token=${encodeURIComponent(token)}&redirectTo=${encodeURIComponent(redirectTo)}`;
    return { url };
  },

  async consumeToken(rawToken: string): Promise<{
    user: { id: string; email: string; role: string };
    redirectTo: string;
  }> {
    if (!env.SSO_BRIDGE_SECRET) {
      throw new AppError(503, 'SSO bridge not configured', 'SSO_BRIDGE_DISABLED');
    }
    let payload: SsoTokenPayload;
    try {
      payload = jwt.verify(rawToken, env.SSO_BRIDGE_SECRET, {
        algorithms: [TOKEN_ALGORITHM],
      }) as SsoTokenPayload;
    } catch (err) {
      logger.debug('SSO token verify failed', {
        err: err instanceof Error ? err.message : String(err),
      });
      throw new AppError(401, 'Invalid or expired SSO token', 'SSO_TOKEN_INVALID');
    }

    // Atomically delete the sentinel — if it returns 0, the token was already
    // consumed (or expired, or was never minted by us). Either way, reject.
    const deleted = await redis.del(`sso:${payload.jti}`);
    if (deleted === 0) {
      throw new AppError(401, 'SSO token already used or expired', 'SSO_TOKEN_CONSUMED');
    }

    const user = await prisma.user.findUnique({
      where: { id: payload.sub },
      select: { id: true, email: true, role: true },
    });
    if (!user) {
      throw new AppError(404, 'User no longer exists', 'NO_CMLITE_USER');
    }

    return { user, redirectTo: sanitizeRedirectTo(payload.redirectTo) };
  },
};
