import { prisma } from '@documenso/prisma';
import type { JWTPayload } from 'jose';
import { decodeJwt, jwtVerify } from 'jose';

import { AppError, AppErrorCode } from '../../errors/app-error';

export type VerifyEmbeddingPresignTokenOptions = {
  token: string;
  scope?: string;
};

export const verifyEmbeddingPresignToken = async ({ token, scope }: VerifyEmbeddingPresignTokenOptions) => {
  // First decode the JWT to get the claims without verification
  let decodedToken: JWTPayload;

  try {
    decodedToken = decodeJwt<JWTPayload>(token);
  } catch (error) {
    console.error('Error decoding JWT token:', error);
    throw new AppError(AppErrorCode.UNAUTHORIZED, {
      message: 'Invalid presign token format',
    });
  }

  // Validate the required claims
  if (!decodedToken.sub || typeof decodedToken.sub !== 'string') {
    throw new AppError(AppErrorCode.UNAUTHORIZED, {
      message: 'Invalid presign token format: missing or invalid subject claim',
    });
  }

  if (!decodedToken.aud || typeof decodedToken.aud !== 'string') {
    throw new AppError(AppErrorCode.UNAUTHORIZED, {
      message: 'Invalid presign token format: missing or invalid audience claim',
    });
  }

  // Convert string IDs to numbers
  const tokenId = Number(decodedToken.sub);
  const audienceId = Number(decodedToken.aud);

  if (Number.isNaN(tokenId) || !Number.isInteger(tokenId)) {
    throw new AppError(AppErrorCode.UNAUTHORIZED, {
      message: 'Invalid token ID format in subject claim',
    });
  }

  if (Number.isNaN(audienceId) || !Number.isInteger(audienceId)) {
    throw new AppError(AppErrorCode.UNAUTHORIZED, {
      message: 'Invalid user ID format in audience claim',
    });
  }

  // Get the API token to use as the verification secret
  const apiToken = await prisma.apiToken.findFirst({
    where: {
      id: tokenId,
    },
    include: {
      user: {
        select: {
          id: true,
          name: true,
          email: true,
        },
      },
    },
  });

  if (!apiToken) {
    throw new AppError(AppErrorCode.UNAUTHORIZED, {
      message: 'Invalid presign token: API token not found',
    });
  }

  // This should never happen but we need to narrow types
  if (!apiToken.userId || !apiToken.user) {
    throw new AppError(AppErrorCode.UNAUTHORIZED, {
      message: 'Invalid presign token: API token does not have a user attached',
    });
  }

  const userId = apiToken.userId;

  if (audienceId !== apiToken.teamId && audienceId !== apiToken.userId) {
    throw new AppError(AppErrorCode.UNAUTHORIZED, {
      message: 'Invalid presign token: API token does not match audience',
    });
  }

  if (decodedToken.scope && scope && decodedToken.scope !== scope) {
    throw new AppError(AppErrorCode.UNAUTHORIZED, {
      message: 'Presign token scope not matched',
    });
  }

  // Now verify the token with the actual secret
  const secret = new TextEncoder().encode(apiToken.token);

  try {
    await jwtVerify(token, secret);
  } catch (error) {
    // Check if the token has expired
    if (error instanceof Error && error.name === 'JWTExpired') {
      throw new AppError(AppErrorCode.UNAUTHORIZED, {
        message: 'Presign token has expired',
      });
    }

    // Handle invalid signature
    if (error instanceof Error && error.name === 'JWSInvalid') {
      throw new AppError(AppErrorCode.UNAUTHORIZED, {
        message: 'Invalid presign token signature',
      });
    }

    // Log and rethrow other errors
    console.error('Error verifying JWT token:', error);
    throw new AppError(AppErrorCode.UNAUTHORIZED, {
      message: 'Failed to verify presign token',
    });
  }

  return {
    ...apiToken,
    userId,
    user: {
      id: apiToken.user.id,
      name: apiToken.user.name,
      email: apiToken.user.email,
    },
  };
};
