import enigma from 'enigma.js';
import qixSchema from 'enigma.js/schemas/12.2015.0.json';
import SenseUtilities from 'enigma.js/sense-utilities';
import { Auth, AuthType } from '@qlik/sdk';
import getCsrfToken from './utils/getCsrfToken';

const getParams = () => {
  const opts = {};
  window.location.search
    .substring(1)
    .split('&')
    .forEach((pair) => {
      const idx = pair.indexOf('=');
      const name = pair.substr(0, idx);
      let value = decodeURIComponent(pair.substring(idx + 1));
      if (name === 'cols') {
        value = value.split(',');
      }
      opts[name] = value;
    });
  return opts;
};

// These URLS are fully constructed later using the sdk
// QCS:       wss://<tenant-url>.<region>.qlikcloud.com/app/<app-GUID>
// QSEoW:     wss://<host>/<virtual-proxy-prefix>/app/<app-GUID>

// matches the wss://<host>/<virtual-proxy-prefix>/
const urlRegex = /(wss?):\/\/([^/:?&]+)(?::(\d+))?/;
const appRegex = /\/app\/([^?&#:]+)/;
// The prefix must be unique for all virtual proxies used by the same proxy service,
// as this differentiates the virtual proxies and will be a part of the URL (https://[node]/[prefix]/).
// Valid characters for prefix are "a-z", "0-9", "-", ".", "_", "~".
const prefixRegx = /(wss?):\/\/([^/:?&]+)(?::(\d+))?\/?([a-z0-9-._~]+)?/;

const parseEngineURL = (url) => {
  const match = urlRegex.exec(url);
  if (!match) {
    return {
      engineUrl: url,
      invalid: true,
    };
  }

  let appId;
  const trimmedUrl = url.trim();
  let engineUrl = trimmedUrl;
  let appUrl;

  const appMatch = appRegex.exec(trimmedUrl);

  if (appMatch) {
    [, appId] = appMatch;
    engineUrl = trimmedUrl.substring(0, appMatch.index);
    appUrl = trimmedUrl;
  }

  const engineMatch = prefixRegx.exec(engineUrl);

  if (!engineMatch) {
    return {
      engineUrl: url,
      invalid: true,
    };
  }
  return {
    enigma: {
      secure: match[1] === 'wss',
      host: match[2],
      port: match[3] || undefined,
      prefix: engineMatch[4] || undefined,
      appId,
    },
    engineUrl,
    appUrl,
  };
};

const getConnectionInfo = () =>
  fetch('/info')
    .then((response) => response.json())
    .then(async (n) => {
      let info = n;
      const params = getParams();
      if (params.engine_url) {
        info = {
          ...info,
          // TODO:
          // instead of calling "/info" and then parsing url inside of `getConnectionInfo()`
          // we can have a hook which listens on location.search and provides this information for us
          ...parseEngineURL(params.engine_url),
        };
      } else if (params.app) {
        info = {
          ...info,
          enigma: {
            ...info.enigma,
            appId: params.app,
          },
        };
      }
      if (params['qlik-web-integration-id']) {
        info.webIntegrationId = params['qlik-web-integration-id'];
      }
      if (params['qlik-client-id']) {
        info.clientId = params['qlik-client-id'];
      }
      if (info.invalid) {
        return info;
      }
      const rootPath = `${info.enigma.secure ? 'https' : 'http'}://${info.enigma.host}`;
      return {
        ...info,
        rootPath,
      };
    });

const getAuthInstance = async ({ webIntegrationId, host }) => {
  const authInstance = new Auth({
    webIntegrationId,
    autoRedirect: true,
    authType: AuthType.WebIntegration,
    host,
  });
  const isAuth = await authInstance.isAuthenticated();
  if (!isAuth) await authInstance.authenticate();
  return authInstance;
};

const connect = async () => {
  try {
    const {
      clientId,
      webIntegrationId,
      enigma: enigmaInfo,
      enigma: { host },
    } = await getConnectionInfo();

    // if no clientId + user is already authorized -> deAuthorize user
    const { isAuthorized } = await (await fetch('/auth/isAuthorized')).json();
    if (!clientId && isAuthorized) {
      await (await fetch('/auth/deauthorize')).json();
    }

    if (webIntegrationId) {
      const authInstance = await getAuthInstance({ webIntegrationId, host });
      return {
        getDocList: async () => {
          const url = `/items?resourceType=app&limit=30&sort=-updatedAt`;
          const { data = [] } = await (await authInstance.rest(url)).json();
          return data.map((d) => ({
            qDocId: d.resourceId,
            qTitle: d.name,
          }));
        },
        getConfiguration: async () => ({}),
      };
    }

    if (clientId) {
      return {
        getDocList: async () => {
          const URL = `/auth/oauth?host=${host}&clientId=${clientId}`;
          const resp = await (await fetch(URL)).json();
          if (resp.redirectUrl) window.location.href = resp.redirectUrl;
        },
        getConfiguration: async () => ({}),
      };
    }

    const csrfToken = await getCsrfToken(
      `https://${enigmaInfo.host}${enigmaInfo.prefix ? `/${enigmaInfo.prefix}` : ''}`
    );
    const url = SenseUtilities.buildUrl({
      secure: false,
      ...enigmaInfo,
      ...{ urlParams: { 'qlik-csrf-token': csrfToken } },
    });

    return enigma.create({ schema: qixSchema, url }).open();
  } catch (error) {
    throw new Error('Failed to return enigma instance');
  }
};

const openApp = async (id) => {
  try {
    const {
      clientId,
      webIntegrationId,
      enigma: enigmaInfo,
      enigma: { host },
    } = await getConnectionInfo();

    let url = '';
    if (webIntegrationId) {
      const authInstance = await getAuthInstance({ webIntegrationId, host });
      url = await authInstance.generateWebsocketUrl(id);
    } else if (clientId) {
      const { webSocketUrl } = await (await fetch(`/auth/getSocketUrl/${id}`)).json();
      url = webSocketUrl;
    } else {
      url = SenseUtilities.buildUrl(enigmaInfo);
    }

    const enigmaGlobal = await enigma.create({ schema: qixSchema, url }).open();
    return enigmaGlobal.openDoc(id);
  } catch (error) {
    throw new Error('Failed to open app!');
  }
};

export { connect, openApp, getParams, getConnectionInfo, getAuthInstance, parseEngineURL };
