import { isEmpty } from 'lodash';
import { OpenGroupData } from '../../../../data/opengroups';
import { getOpenGroupManager } from '../opengroupV2/OpenGroupManagerV2';
import { SessionUtilUserGroups } from '../../../utils/libsession/libsession_utils_user_groups';
import { getConversationController } from '../../../conversations';
import { OpenGroupV2Room, OpenGroupRequestCommonType } from '../../../../data/types';

// eslint-disable-next-line prefer-regex-literals
const protocolRegex = new RegExp('https?://');

const dot = '\\.';
const qMark = '\\?';
const hostSegment = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
const hostnameRegex = new RegExp(`(?:${hostSegment}${dot})+${hostSegment}`);
const portRegex = ':[1-9][0-9]{0,4}';

// roomIds allow up to 64 ascii numbers, letters, '_', or '-' chars
const roomIdV2Regex = '[0-9a-zA-Z_-]{1,64}';
const publicKeyRegex = '[0-9a-fA-F]{64}';
export const publicKeyParam = 'public_key=';
const openGroupV2ServerUrlRegex = new RegExp(
  `(?:${protocolRegex.source})?${hostnameRegex.source}(?:${portRegex})?`
);

/**
 * Regex to use to check if a string is a v2open completeURL with pubkey.
 * Be aware that the /g flag is not set as .test() will otherwise return alternating result
 *
 * see https://stackoverflow.com/a/9275499/1680951
 */
export const openGroupV2CompleteURLRegex = new RegExp(
  // eslint-disable-next-line no-useless-escape
  `^${openGroupV2ServerUrlRegex.source}\/${roomIdV2Regex}${qMark}${publicKeyParam}${publicKeyRegex}$`
);

/**
 * Just a constant to have less 'http' everywhere.
 * This is the prefix used to identify our open groups in the conversation database (v1 or v2)
 */

const openGroupPrefix = 'http'; // can be http:// or https://

/**
 * This function returns a full url on an open group v2 room used for sync messages for instance.
 * This is basically what the QRcode encodes
 *
 */
export function getCompleteUrlFromRoom(roomInfos: OpenGroupV2Room) {
  if (
    isEmpty(roomInfos.serverUrl) ||
    isEmpty(roomInfos.roomId) ||
    isEmpty(roomInfos.serverPublicKey)
  ) {
    throw new Error('getCompleteUrlFromRoom needs serverPublicKey, roomid and serverUrl to be set');
  }
  // serverUrl has the port and protocol already
  return `${roomInfos.serverUrl}/${roomInfos.roomId}?${publicKeyParam}${roomInfos.serverPublicKey}`;
}

/**
 * Prefix server with https:// if it's not already prefixed with http or https.
 */
export function prefixify(server: string): string {
  const hasPrefix = server.match('^https?://');
  if (hasPrefix) {
    return server;
  }

  return `http://${server}`;
}

/**
 * No sql access. Just how our open groupv2 url looks like.
 * ServerUrl can have the protocol and port included, or not
 * @returns `${openGroupPrefix}${roomId}@${serverUrl}`
 */
export function getOpenGroupV2ConversationId(serverUrl: string, roomId: string) {
  // TODOLATER we should probably make this force the serverURL to be our sogs with https when it matches pubkey or domain name
  if (!roomId.match(`^${roomIdV2Regex}$`)) {
    throw new Error('getOpenGroupV2ConversationId: Invalid roomId');
  }
  if (!serverUrl.match(openGroupV2ServerUrlRegex)) {
    throw new Error('getOpenGroupV2ConversationId: Invalid serverUrl');
  }
  return `${serverUrl}/${roomId}`;
}

/**
 * No sql access. Just plain string logic
 */
export function getOpenGroupV2FromConversationId(
  conversationId: string
): OpenGroupRequestCommonType {
  if (isOpenGroupV2(conversationId)) {
    const endProtocolStr = '://';
    const startOfDoubleSlashes = conversationId.indexOf(endProtocolStr); // works for both http or https
    if (startOfDoubleSlashes < 0) {
      throw new Error('We need :// to be present in an opengroup URL');
    }
    const firstSlashAfterProtocol = conversationId.indexOf(
      '/',
      startOfDoubleSlashes + endProtocolStr.length + 1
    );
    const baseUrlWithProtocol = conversationId.substring(0, firstSlashAfterProtocol);
    const lastSlash = conversationId.lastIndexOf('/');
    const roomId = conversationId.slice(lastSlash + 1);
    return {
      serverUrl: baseUrlWithProtocol,
      roomId,
    };
  }
  throw new Error('Not a v2 open group convo id');
}

/**
 * Check if this conversation id corresponds to an OpenGroupV2 conversation.
 */
export function isOpenGroupV2(conversationId: string) {
  return Boolean(conversationId?.startsWith(openGroupPrefix));
}

/**
 * Fetches all roomInfos for all of our opengroup conversations.
 * We consider the conversations as our source-of-truth, so if there is a roomInfo without an associated convo, we remove it before returning.
 * @returns A map of conversationIds to roomInfos for all valid open group conversations or undefined
 */
export async function getAllValidOpenGroupV2ConversationRoomInfos() {
  const inWrapperCommunities = await SessionUtilUserGroups.getAllCommunitiesNotCached();

  const inWrapperIds = inWrapperCommunities.map(m =>
    getOpenGroupV2ConversationId(m.baseUrl, m.roomCasePreserved)
  );

  let allRoomInfos = OpenGroupData.getAllV2OpenGroupRoomsMap();

  // It is time for some cleanup!
  // We consider the wrapper to be our source-of-truth,
  // so if there is a roomInfos without an associated entry in the wrapper, we remove it from the map of opengroups rooms
  if (allRoomInfos?.size) {
    const roomInfosAsArray = [...allRoomInfos.values()];
    for (let index = 0; index < roomInfosAsArray.length; index++) {
      const infos = roomInfosAsArray[index];
      try {
        const roomConvoId = getOpenGroupV2ConversationId(infos.serverUrl, infos.roomId);
        if (!inWrapperIds.includes(roomConvoId)) {
          // remove the roomInfos locally for this open group room.

          /* eslint-disable no-await-in-loop */
          await OpenGroupData.removeV2OpenGroupRoom(roomConvoId);
          getOpenGroupManager().removeRoomFromPolledRooms(infos);
          await getConversationController().deleteCommunity(roomConvoId, {
            fromSyncMessage: false,
          });
          /* eslint-enable no-await-in-loop */
        }
      } catch (e) {
        window?.log?.warn('cleanup roomInfos error', e);
      }
    }
  }

  // refresh our roomInfos list
  allRoomInfos = OpenGroupData.getAllV2OpenGroupRoomsMap();
  return allRoomInfos;
}
