// Copyright 2022 Matvey Ryabchikov
// MIT License

import { encode, decode } from 'https://deno.land/std@0.141.0/encoding/base64.ts';
import { crypto } from 'https://deno.land/std@0.141.0/crypto/mod.ts';

class SPWorlds {
  private authHeader: string;
  private token: string;

  private fetchApi = (path: string, body: Record<string, unknown> | null, getResult: boolean) =>
    fetch(`https://spworlds.ru/api/public/${path}`, {
      method: body === null ? 'GET' : 'POST',
      body: body ? JSON.stringify(body) : undefined,
      headers: { Authorization: this.authHeader }
    }).then(res => {
      if (![200, 404].includes(res.status))
        throw new Error(`Ошибка при запросе к API ${res.status} ${res.statusText}`);
      if (getResult) return res.json();
    });

  /**
   * @param cardId ID карты
   * @param cardToken Секретный токен карты
   */
  constructor(cardId: string, cardToken: string) {
    this.authHeader = `Bearer ${encode(`${cardId}:${cardToken}`)}`;
    this.token = cardToken;
  }

  /**
   * Создает платежную форму
   * @param amount Стоимость покупки в АРах
   * @param redirectUrl Ссылка успешной оплаты, на которую перенаправят пользователя после успешной оплаты
   * @param webhookUrl Ссылка, на которую прейдет уведомление об успешной оплате
   * @param data Любые полезные данные пользователя
   * @returns URL оплаты
   */
  initPayment = (
    amount: number,
    redirectUrl: string,
    webhookUrl?: string,
    data?: string
  ): Promise<string> =>
    this.fetchApi(
      'payment',
      { amount, redirectUrl, webhookUrl: webhookUrl || null, data: data || null },
      true
    ).then(({ url }: { url: string }) => url);

  /**
   * Переводит АРы с карты кому-то еще
   * @param receiver Номер карты получателя
   * @param amount Кол-во аров
   * @param comment Комментарий к переводу
   */
  createTransaction = (receiver: string, amount: number, comment: string): Promise<void> =>
    this.fetchApi('transactions', { receiver, amount, comment }, false).then(() => {});

  /**
   * Переводит АРы с карты кому-то еще
   * @param discordId ID игрока к дискорде
   * @returns Ник игрока
   */
  findUser = (discordId: string): Promise<string | null> =>
    this.fetchApi(`users/${discordId}`, null, true).then(
      ({ username }: { username: string | null }) => username
    );

  /**
   * Проверяет валидность вебхука
   * @param body Все данные, пришедшие по вебхуку
   * @param hashHeader Значение хедера `X-Body-Hash`
   */
  verifyHash = async (
    body: string | Record<string, unknown>,
    hashHeader: string
  ): Promise<boolean> => {
    const encoder = new TextEncoder();

    return crypto.subtle.verify(
      { name: 'HMAC' },
      await crypto.subtle.importKey(
        'raw',
        encoder.encode(this.token),
        { name: 'HMAC', hash: 'SHA-256' },
        false,
        ['verify']
      ),
      decode(hashHeader),
      encoder.encode(typeof body === 'string' ? body : JSON.stringify(body))
    );
  };
}

export default SPWorlds;
