import HotPocketExtension from '../common';

const POST_AUTH_PATH = '/integrations/extension/post-authenticate/';
const ACCOUNTS_RPC_PATH = '/accounts/rpc/';
const RPC_PATH = '/rpc/';

let authSessionToken = null;
let accountsRPCURL = null;
let rpcURL = null;

const updateRpcURLs = (options) => {
  options = {
    clear: false,
    ...(options || {}),
  };

  accountsRPCURL = null;
  rpcURL = null;

  if (HotPocketExtension.base_url !== null) {
    rpcURL = (new URL(RPC_PATH, HotPocketExtension.base_url)).toString();
    accountsRPCURL = (new URL(ACCOUNTS_RPC_PATH, HotPocketExtension.base_url)).toString();
  } else if (options.clear === true) {
    rpcURL = null;
    accountsRPCURL = null;
  }

  HotPocketExtension.LOGGER.debug(
    'HotPocketExtension.background.updateRpcURLs()',
    HotPocketExtension.base_url,
    rpcURL,
    accountsRPCURL,
  );
};

const makeJSONRPCCall = (method, params) => {
  return {
    'jsonrpc': '2.0',
    'id': (new Date().toISOString()),
    'method': method,
    'params': params,
  };
};

const executeJSONRPCCall = async (url, call, {accessToken}) => {
  HotPocketExtension.LOGGER.debug(
    'HotPocketExtension.background.executeJSONRPCCall()', url, call, accessToken,
  );

  const headers = {
    'Content-Type': 'application/json',
  };

  if (accessToken) {
    headers['Authorization'] = `Bearer ${accessToken}`;
  }

  let result = null;
  let error = null;

  const effectiveURL = new URL(url);
  effectiveURL.searchParams.append('method', call.method);

  try {
    const response = await fetch(
      effectiveURL.toString(),
      {
        body: JSON.stringify(call),
        credentials: 'include',
        headers: headers,
        method: 'POST',
      },
    );
    HotPocketExtension.LOGGER.debug(
      'HotPocketExtension.background.executeJSONRPCCall()', response,
    );

    if (response.status !== 200) {
      error = {
        code: -32000,
        message: 'Fetch error',
        data: response,
      };
    } else {
      const callResult = await response.json();
      if (callResult.error) {
        HotPocketExtension.LOGGER.error(
          'HotPocketExtension.background.executeJSONRPCCall(): RPC error',
          callResult.error.code,
          callResult.error.message,
          callResult.error.data,
        );

        error = callResult.error;
      } else {
        result = callResult.result;
      }
    }
  } catch (exception) {
    HotPocketExtension.LOGGER.error(
      'HotPocketExtension.background.executeJSONRPCCall(): Fetch error', exception,
    );
    error = {
      code: -32000,
      message: 'Fetch error',
      data: exception,
    };
  }

  return [result, error];
};

const getAccessTokenMeta = () => {
  return {
    platform: navigator.platform,
    version: HotPocketExtension.version,
  };
};

const doSave = async (accessToken, tab) => {
  const call = makeJSONRPCCall('saves.create', [tab.url]);
  const [result, error] = await executeJSONRPCCall(rpcURL, call, {accessToken});
  HotPocketExtension.LOGGER.debug(
    'HotPocketExtension.background.doSave():', result, error,
  );

  if (error !== null) {
    return null;
  }

  return result;
};

const doCreateAndStoreAccessToken = async (authKey) => {
  const accessTokenCall = makeJSONRPCCall(
    'accounts.access_tokens.create',
    [
      authKey,
      getAccessTokenMeta(),
    ],
  );

  const [accessToken, error] = await executeJSONRPCCall(
    accountsRPCURL, accessTokenCall, {accessToken: null},
  );

  if (error === null) {
    await HotPocketExtension.api.storage.local.set({
      accessToken: accessToken,
    });
  }

  return [accessToken, error];
};

const doHandleAuthFlow = (authTab) => {
  HotPocketExtension.LOGGER.debug(
    'HotPocketExtension.background.doHandleAuthFlow()', authTab,
  );
  let currentAuthTabId = authTab.id;

  return new Promise((resolve, reject) => {
    const onTabsUpdated = (tabId, changeInfo, updatedTab) => {
      const changedURL = changeInfo.url;

      HotPocketExtension.LOGGER.debug(
        'HotPocketExtension.background.doHandleAuthFlow.onTabsUpdated()',
        updatedTab,
        changedURL,
        (changedURL && changedURL.includes(POST_AUTH_PATH)),
        changeInfo,
      );

      const expectedSessionTabQuery = `?authSessionToken=${authSessionToken}`;
      if (tabId !== currentAuthTabId && changedURL && changedURL.includes(expectedSessionTabQuery)) {
        // When redirecting from the preauth page to the HotPocket instance,
        // Safari "replaces" the auth tab with a new one. This nasty hack will
        // allow the extension to keep track of it.
        // I hate computers.
        currentAuthTabId = tabId;
      }

      if (tabId === currentAuthTabId) {
        if (changedURL && changedURL.includes(POST_AUTH_PATH)) {
          const parsedChangedURL = new URL(changedURL);
          const authKey = parsedChangedURL.searchParams.get('auth_key');

          doCreateAndStoreAccessToken(authKey).
            then((result) => {
              HotPocketExtension.LOGGER.debug(
                'doHandleAuthFlow.onTabsUpdated.doGetAndStoreAccessToken.then()', result,
              );
              const [accessToken, error] = result;

              if (error !== null) {
                reject(error);
              } else {
                resolve(accessToken);
              }
            }).
            catch((error) => {
              reject(error);
            }).
            finally(() => {
              authSessionToken = null;
              HotPocketExtension.api.tabs.onUpdated.removeListener(onTabsUpdated);
              HotPocketExtension.api.tabs.remove(currentAuthTabId);
            });
        }
      }
    };

    HotPocketExtension.api.tabs.onUpdated.addListener(onTabsUpdated);
  });
};

const doCheckAuth = async (accessToken) => {
  if (accessToken === null) {
    return null;
  }

  const call = makeJSONRPCCall(
    'accounts.auth.check_access_token',
    [accessToken, getAccessTokenMeta()],
  );

  const [result, error] = await executeJSONRPCCall(accountsRPCURL, call, {
    accessToken,
  });

  if (error !== null) {
    if (error.data instanceof Error) {
      throw error;
    }

    if (error.data.status === 403) {
      return null;
    }
  }

  if (result === false) {
    return null;
  }

  return accessToken;
};

const doSetupRPC = async () => {
  let storageResult = await HotPocketExtension.api.storage.local.get(
    ['accessToken', 'baseURL'],
  );

  let accessToken = null;
  if (storageResult.baseURL) {
    HotPocketExtension.base_url = storageResult.baseURL;
    updateRpcURLs();

    accessToken = await doCheckAuth(
      storageResult.accessToken || null,
    );
  }

  if (accessToken === null) {
    authSessionToken = crypto.randomUUID();

    const authTab = await HotPocketExtension.api.tabs.create({
      url: HotPocketExtension.api.runtime.getURL(
        `preauth.html?authSessionToken=${authSessionToken}`,
      ),
    });

    accessToken = await doHandleAuthFlow(authTab);
  }

  return accessToken;
};

const doSendTabMessage = (tab, message) => {
  return HotPocketExtension.api.tabs.sendMessage(tab.id, message).
    then((result) => {
      HotPocketExtension.LOGGER.debug(
        'HotPocketExtension.background.doSendTabMessage(): message sent',
        message,
        result,
      );
    }).
    catch((error) => {
      HotPocketExtension.LOGGER.error(
        'HotPocketExtension.background.doSendTabMessage(): could not send message',
        error,
      );
    });
};

const doUpdateBaseURL = (nextBaseURL) => {
  HotPocketExtension.base_url = nextBaseURL;
  updateRpcURLs();

  HotPocketExtension.api.storage.local.
    set({
      baseURL: nextBaseURL,
    }).
    then(() => {
      HotPocketExtension.LOGGER.debug(
        'HotPocketExtension.background.doUpdateBaseURL()', 'Base URL saved',
      );
    }).
    catch((error) => {
      HotPocketExtension.LOGGER.error(
        'HotPocketExtension.background.doUpdateBaseURL()', error,
      );
    });
};

const onTabCreated = (tab) => {
  HotPocketExtension.LOGGER.debug(
    'HotPocketExtension.background.onTabCreated()', tab,
  );
  HotPocketExtension.api.action.enable(tab.id).catch((error) => {
    HotPocketExtension.LOGGER.error(
      'HotPocketExtension.background.onTabCreated()', tab.id, error,
    );
  });
};

const onBrowserActionClicked = async (tab) => {
  HotPocketExtension.LOGGER.debug(
    'HotPocketExtension.background.onBrowserActionClicked()', tab.url,
  );

  if (!tab.url) {
    return;
  }

  let result = false;
  let error = null;

  await doSendTabMessage(tab, {
    type: 'HotPocket:Extension:browserActionClicked',
  });

  try {
    let accessToken = await doSetupRPC();

    result = await doSave(accessToken, tab);
    HotPocketExtension.LOGGER.debug(
      'HotPocketExtension.background.onBrowserActionClicked()', result,
    );
  } catch (exception) {
    HotPocketExtension.LOGGER.error(
      'Unhandled exception when handling action click',
      exception,
    );
    error = exception;
  }

  const message = {
    type: 'HotPocket:Extension:save',
    result: result,
    error: error,
  };

  return await doSendTabMessage(tab, message);
};

const doLogOut = async () => {
  // eslint-disable-next-line no-unused-vars
  let storageResult = await HotPocketExtension.api.storage.local.clear();
  updateRpcURLs({clear: true});
};

const onMessage = (message, sender, sendResponse) => {
  HotPocketExtension.LOGGER.debug(
    'HotPocketExtension.background.onMessage()', message, sender, sendResponse,
  );

  let response = {ok: true};
  try {
    if (message.type === 'HotPocket:Extension:ping') {
      doSendTabMessage(sender.tab, {
        type: 'HotPocket:Extension:pong',
      });
    } else if (message.type === 'HotPocket:Extension:setBaseURL') {
      doUpdateBaseURL(message.result);
    } else if (message.type === 'HotPocket:ExtensionOptions:logOut') {
      doLogOut();
    } else if (message.type === 'HotPocket:ExtensionOptions:logIn') {
      doSetupRPC();
    }
  } catch (exception) {
    HotPocketExtension.LOGGER.error(
      'HotPocketExtension.background.onMessage(): Unhandled exception when handling content message',
      message,
      exception,
    );
    response.ok = false;
  }

  sendResponse(response);

  return true;
};

export default ({...configuration}) => {
  HotPocketExtension.configure(configuration, {
    background: true,
  });

  updateRpcURLs();

  HotPocketExtension.api.tabs.onCreated.addListener(onTabCreated);

  HotPocketExtension.api.action.onClicked.addListener(onBrowserActionClicked);

  HotPocketExtension.api.runtime.onMessage.addListener(onMessage);

  HotPocketExtension.LOGGER.info(`HotPocket v${HotPocketExtension.version} by BTHLabs`);
};
