import Keycloak, { KeycloakConfig, KeycloakOnLoad } from 'keycloak-js';

import type { App, InjectionKey, Ref } from 'vue';
import { inject, ref } from 'vue';

export interface KeycloakAuthAdapterOptions {
  keycloakOptions: KeycloakConfig;
  onLoad: KeycloakOnLoad;
  refreshRate: number;
  updateTokenMinValidity: number;
  onTokenRefreshed: (_: string | undefined) => void;
  onTokenRefreshFailed: (_: any) => void;
  checkAccess: (keycloak: Keycloak) => boolean;
}

export type KeycloakAuthAdapterInitOptions = Pick<
  KeycloakAuthAdapterOptions,
  'keycloakOptions'
> &
  Partial<KeycloakAuthAdapterOptions>;

export const KeycloakAuthAdapterInstance = Symbol() as InjectionKey<
  Ref<Keycloak | undefined>
>;

export const keycloakAuthAdapterInit = (
  options: KeycloakAuthAdapterInitOptions,
): Promise<Ref<Keycloak | undefined>> => {
  let token: string | undefined;

  const keycloakRef = ref<Keycloak>();

  return new Promise(async (resolve, reject) => {
    const initOptions: KeycloakAuthAdapterOptions = Object.assign(
      {},
      {
        onLoad: 'login-required',
        refreshRate: 60000,
        updateTokenMinValidity: 70,
        checkLoginIframe: true,
        onTokenRefreshed: () => {},
        onTokenRefreshFailed: () => {},
        checkAccess: () => true,
      },
      options,
    );

    try {
      const keycloak = new Keycloak(initOptions.keycloakOptions);

      const auth = await keycloak.init({
        onLoad: initOptions.onLoad,
        checkLoginIframe: false,
      });
      if (!auth) {
        throw 'not-authorized';
      }

      token = keycloak.token;
      keycloakRef.value = keycloak;

      if (!initOptions.checkAccess(keycloak)) {
        reject(keycloakRef);
        return;
      }

      setInterval(() => {
        keycloak
          .updateToken(initOptions.updateTokenMinValidity)
          .then((refreshed: boolean) => {
            if (refreshed) {
              token = keycloak.token;
              keycloakRef.value = keycloak;
              initOptions.onTokenRefreshed(token);
            }
          })
          .catch((error) => {
            initOptions.onTokenRefreshFailed(error);
          });
      }, initOptions.refreshRate);
      resolve(keycloakRef);
    } catch (error) {
      reject(error);
    }
  });
};

export const useKeycloakAuthAdapter = () => {
  const kcInstance = inject(KeycloakAuthAdapterInstance);

  return {
    auth: kcInstance,
  };
};

export const keycloakAuthAdapter = {
  install: (app: App, kcInstance: Ref<Keycloak | undefined>) => {
    app.config.globalProperties.$auth = kcInstance;

    app.provide(KeycloakAuthAdapterInstance, kcInstance);
  },
};
