import { Cookies } from 'quasar';
import Vue from 'vue';
import store from '@state/store';
import get from 'lodash/get';

interface WebsocketsConfig {
  url: string;
  protocols?: string | string[];
}

const websockets: {
  instance: WebSocket | null;
} = {
  instance: null,
};

function initCookie(): Promise<void> {
  return new Promise((resolve, reject) => {
    const currentDate = new Date();

    currentDate.setDate(currentDate.getDate() + 1);

    const createCookie = () => {
      store
        .dispatch('oidcClient/getAccessToken')
        .then((accessToken) => {
          Cookies.set('JWT', accessToken, {
            expires: currentDate.toUTCString(),
            path: '/',
            secure: true,
          });

          return resolve();
        })
        .catch((e) => reject(e));
    };

    try {
      createCookie();
    } catch (e) {
      setTimeout(() => {
        createCookie();
      }, 2000);
    }
  });
}

function resetCookie(): void {
  Cookies.remove('JWT', {
    path: '/',
  });
}

/**
 * Функция проверяет - является ли сообщение PING|PONG типом.
 * @param e - Сообщение сокет-канала.
 */
function isPingMessage(e: MessageEvent) {
  const data: any = get(e, 'data');

  if (!data || data === "ok") {
    return false;
  }
  try {
    const body = get(JSON.parse(data), 'body');

    return body && get(body, 'type') === 'PING';
  } catch (e) {
    return false;
  }
}

/**
 * Массив функций, которые будут выполняться по событию onMessage.
 */
let listeners: ((e: MessageEvent) => void)[] = [];

function tryReconnect(
  config: WebsocketsConfig
): Promise<{
  instance: WebSocket | null;
}> {
  return new Promise((resolve, reject) => {
    initCookie().then(() => {
      let webSocket: WebSocket | null = null;
      try {
        webSocket = config.protocols
          ? new WebSocket(config.url, config.protocols)
          : new WebSocket(config.url);
      } catch (e) {
        return reject(e);
      }

      if (webSocket) {
        let isBisy = false;

        webSocket.onopen = resetCookie;

        webSocket.onmessage = (e: MessageEvent) => {
          if (isPingMessage(e) && webSocket) {
            webSocket.send(
              JSON.stringify({
                type: 'PONG',
                message: 'Available',
              })
            );
          }
          listeners.forEach((listener: (eMessage: MessageEvent) => void) => {
            listener(e);
          });
        };

        webSocket.onclose = (event: any) => {
          if (!isBisy) {
            isBisy = true;
            setTimeout(() => {
              tryReconnect(config).then(
                (webSocketCast: { instance: WebSocket | null }) => {
                  websockets.instance = webSocketCast.instance;
                }
              );
            }, 4000);
          }
        };

        webSocket.onerror = () => {
          if (!isBisy) {
            isBisy = true;
            setTimeout(() => {
              tryReconnect(config).then(
                (webSocketCast: { instance: WebSocket | null }) => {
                  websockets.instance = webSocketCast.instance;
                }
              );
            }, 4000);
          }
        };
      }
      websockets.instance = webSocket;

      return resolve(websockets);
    });
  });
}

export default class WebsocketService extends Vue {
  /**
   * Геттер для получения instance, не рекоммендуется переопределять onMessage, для этого
   * лучше использовать addListener(Function) и removeListener(Function)
   */
  public get instance(): WebSocket | null {
    return websockets.instance;
  }

  /**
   * Геттер для вывода всех слушателей.
   */
  public get listeners(): ((e: MessageEvent) => void)[] {
    return listeners;
  }

  /**
   * Добавляет функцию прослушки для события onMessage.
   * @param listener{(e: MessageEvent) => void)} - Сама функция для прослушивания пуша.
   */
  public addListener(listener: (e: MessageEvent) => void): void {
    listeners.push(listener);
  }

  /**
   * Удаляет функцию прослушки для события onMessage.
   * @param listener{(e: MessageEvent) => void)} - Передаётся сама функция(сравниваются их ссылки для удаления).
   */
  public removeListener(listener: (e: MessageEvent) => void): void {
    listeners = listeners.filter(
      (itemListener: (e: MessageEvent) => void) => itemListener !== listener
    );
  }

  public static create(
    config: WebsocketsConfig
  ): Promise<{
    instance: WebSocket | null;
  }> {
    return tryReconnect(config);
  }
}
