import { createHmac, timingSafeEqual } from 'node:crypto';

import { config } from '../config.js';
import { prisma } from '../prisma.js';

type TelegramInitDataUser = {
  id: number;
  username?: string;
  first_name?: string;
  last_name?: string;
  photo_url?: string;
  language_code?: string;
};

function hmacSha256(key: string | Buffer, value: string) {
  return createHmac('sha256', key).update(value).digest();
}

function assertValidHash(receivedHash: string, expectedHash: Buffer) {
  const received = Buffer.from(receivedHash, 'hex');
  if (received.length !== expectedHash.length) {
    throw new Error('Telegram init data hash is invalid.');
  }

  if (!timingSafeEqual(received, expectedHash)) {
    throw new Error('Telegram init data hash is invalid.');
  }
}

function parseTelegramInitData(initData: string) {
  if (!config.telegramMiniAppBotToken) {
    throw new Error('TELEGRAM_MINI_APP_BOT_TOKEN is required.');
  }

  const params = new URLSearchParams(initData);
  const receivedHash = params.get('hash');
  if (!receivedHash) {
    throw new Error('Telegram init data hash is required.');
  }

  const authDate = Number(params.get('auth_date'));
  if (!Number.isFinite(authDate)) {
    throw new Error('Telegram auth_date is required.');
  }

  const ageSeconds = Math.floor(Date.now() / 1000) - authDate;
  if (ageSeconds > config.telegramAuthMaxAgeSeconds) {
    throw new Error('Telegram init data is expired.');
  }

  const dataCheckString = [...params.entries()]
    .filter(([key]) => key !== 'hash')
    .sort(([left], [right]) => left.localeCompare(right))
    .map(([key, value]) => `${key}=${value}`)
    .join('\n');

  const secretKey = hmacSha256('WebAppData', config.telegramMiniAppBotToken);
  const expectedHash = hmacSha256(secretKey, dataCheckString);
  assertValidHash(receivedHash, expectedHash);

  const rawUser = params.get('user');
  if (!rawUser) {
    throw new Error('Telegram user is required.');
  }

  return JSON.parse(rawUser) as TelegramInitDataUser;
}

export async function getOrCreateTelegramUser(initData: string) {
  const user = parseTelegramInitData(initData);

  return prisma.user.upsert({
    where: { telegramId: String(user.id) },
    create: {
      telegramId: String(user.id),
      username: user.username,
      firstName: user.first_name,
      lastName: user.last_name,
      photoUrl: user.photo_url,
      languageCode: user.language_code,
    },
    update: {
      username: user.username,
      firstName: user.first_name,
      lastName: user.last_name,
      photoUrl: user.photo_url,
      languageCode: user.language_code,
    },
  });
}

export async function requireTelegramUser(initData?: string) {
  if (!initData) {
    throw new Error('Telegram authorization is required.');
  }

  return getOrCreateTelegramUser(initData);
}
