import { createHmac, timingSafeEqual } from 'crypto';
import { getRequiredEnv } from './env';

type DeviceRole = 'camera' | 'client';

export type DeviceTokenPayload = {
  userId: string;
  deviceId: string;
  role: DeviceRole;
  exp: number;
};

const secret = getRequiredEnv('BETTER_AUTH_SECRET');

const base64UrlEncode = (input: string): string => Buffer.from(input, 'utf8').toString('base64url');
const base64UrlDecode = (input: string): string => Buffer.from(input, 'base64url').toString('utf8');

const sign = (data: string): string => createHmac('sha256', secret).update(data).digest('base64url');

export const createDeviceToken = (
  payload: Omit<DeviceTokenPayload, 'exp'>,
  ttlSeconds = 60 * 60 * 24 * 30,
): string => {
  const body: DeviceTokenPayload = {
    ...payload,
    exp: Math.floor(Date.now() / 1000) + ttlSeconds,
  };

  const encodedPayload = base64UrlEncode(JSON.stringify(body));
  const signature = sign(encodedPayload);

  return `${encodedPayload}.${signature}`;
};

export const verifyDeviceToken = (token: string): DeviceTokenPayload | null => {
  const [encodedPayload, providedSignature] = token.split('.');

  if (!encodedPayload || !providedSignature) {
    return null;
  }

  const expectedSignature = sign(encodedPayload);

  const providedBuffer = Buffer.from(providedSignature, 'utf8');
  const expectedBuffer = Buffer.from(expectedSignature, 'utf8');

  if (providedBuffer.length !== expectedBuffer.length) {
    return null;
  }

  if (!timingSafeEqual(providedBuffer, expectedBuffer)) {
    return null;
  }

  let parsedPayload: unknown;

  try {
    parsedPayload = JSON.parse(base64UrlDecode(encodedPayload));
  } catch {
    return null;
  }

  if (!parsedPayload || typeof parsedPayload !== 'object') {
    return null;
  }

  const payload = parsedPayload as Partial<DeviceTokenPayload>;

  if (
    typeof payload.userId !== 'string' ||
    typeof payload.deviceId !== 'string' ||
    (payload.role !== 'camera' && payload.role !== 'client') ||
    typeof payload.exp !== 'number'
  ) {
    return null;
  }

  if (payload.exp <= Math.floor(Date.now() / 1000)) {
    return null;
  }

  return payload as DeviceTokenPayload;
};
