import { PluginFunction, PluginObject, VueConstructor } from 'vue';
import { isString, mergeWith, isArray } from 'lodash';
import { Vue as _Vue } from 'vue/types/vue';
import { DirectiveBinding } from 'vue/types/options';
import { Route, RouteRecord } from 'vue-router';
import moment from 'moment';

import WebsocketService from '@/recruits/services/web-socket';
import { router } from '@/router';
import store from '@state/store';
import { uiActionService as uiActionServiceApi } from '@/recruits/api/ervu-ui-action-service';

import pluginContext from './activity-recorder-context';
import { i18n } from '@plugins/i18n';

const RECORD_EVENT = {
  action: 'action',
  navigation: 'navigation',
};

interface IActivityRecorderPlugin<T> extends PluginObject<T> {
  install: PluginFunction<T>;
  uninstall: () => void;
}

const actionDictionary: {
  [key: string]: { [key: string]: string };
} = {};

function buildActionParams(inputParams: any) {
  const dictionaryItem =
    actionDictionary[inputParams?.buttonCode ?? inputParams] || {};
  const inlineAttributes = isString(inputParams)
    ? { buttonCode: inputParams }
    : inputParams;
  return mergeWith(
    {
      event: RECORD_EVENT.action,
      extraInfo: { breadcrumbs: pluginContext.currentBreadcrumbs },
    },
    dictionaryItem,
    inlineAttributes,
    (a, b) => {
      if (isArray(a)) {
        return a.concat(b);
      }
    }
  );
}

async function recordActivity(ws: WebSocket | null, inputParams: any) {
  const params = buildActionParams(inputParams);
  const currentUserInfo = store.getters['oidcClient/currentUser'];
  if (currentUserInfo) {
    const {
      sub: userId,
      domain_id: vkId,
      name: userName,
      position_id: position,
      session_state: armId,
      session_state: eventTraceId,
      family_name: lastName,
      given_name: firstName,
      middle_name: middleName,
    } = currentUserInfo;
    const isNavigationEvent = params.event === RECORD_EVENT.navigation;
    if (isNavigationEvent) {
      // Дублируем сохранение "хлебных крошек", так как журналирование перехода может быть вызвано в ручном режиме.
      pluginContext.currentBreadcrumbs = params.extraInfo?.breadcrumbs || [];
    }
    if (isNavigationEvent || params.buttonCode) {
      const finalParams = {
        userId,
        vkId,
        vkName: store.getters['idm/organizations/organizationName'] || '',
        userName,
        lastName,
        firstName,
        middleName,
        position,
        eventTime: moment(),
        armId,
        eventTraceId,
        url: params.url ? params.url : router.currentRoute.path,
        ...params,
      };
      if (ws && ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify(finalParams));
      } else {
        console.warn(
          `Socket(${ws?.readyState}) not ready to register action. Falling back to HTTP`
        );
        try {
          await uiActionServiceApi.recordActivity(finalParams);
        } catch (error) {
          console.error(
            `Не смог зарегистрировать действие пользователя`,
            finalParams,
            error
          );
        }
      }
    } else
      console.warn(
        'Действие не может быть журналировано, не указан buttonCode!'
      );
  }
}

const ActivityRecorderPlugin: IActivityRecorderPlugin<VueConstructor> = {
  install(Vue: typeof _Vue) {
    Vue.prototype.$recordActivity = (message: any) => {
      if (pluginContext.socketService.instance) {
        recordActivity(pluginContext.socketService?.instance, message);
      }
    };
    Vue.directive('activity-recorder', {
      inserted(el: HTMLElement, binding: DirectiveBinding, vNode: any) {
        vNode.context.bindingValue = binding.value;
        const handleClick = (e: Event) => {
          if (
            typeof vNode.context.bindingValue === 'object' &&
            !('buttonCode' in vNode.context.bindingValue)
          ) {
            console.warn(
              `Директива v-activity-recorder='${JSON.stringify(
                vNode.context.bindingValue
              )}' не имеет атрибута buttonCode и поэтому не может быть журналирована!`
            );
          } else {
            recordActivity(
              pluginContext.socketService?.instance,
              vNode.context.bindingValue
            );
          }
        };
        el.addEventListener('click', handleClick);
      },
      update(el: HTMLElement, binding: DirectiveBinding, vNode: any) {
        vNode.context.bindingValue = binding.value;
      },
    });

    // В режиме DEV из-за hot-reload, лучше убедиться что старый обработчик отписан
    pluginContext.cancelSubscription();
    pluginContext.cancelSubscription = router.afterEach(
      (to: Route, from: Route) => {
        // исключаем повторные записи при мелких изменениях route-ов
        // например на страницах отображения данных меняются параметры в URL строке
        if (
          pluginContext.lastRoute == null ||
          (to.name && pluginContext.lastRoute.name !== to.name)
        ) {
          pluginContext.lastRoute = to;
          // Некоторые роуты не имеют "хлебных крошек" (.matched),
          // либо для некоторых роутов необходимо записывать дополнительную информацию при переходе.
          // Для таких случаев мы присваиваем мета-данным значение isTrackable: false,
          // и журналируем переход вручную.
          if (to.meta?.isTrackable ?? true) {
            pluginContext.currentBreadcrumbs = [];
            to.matched.forEach((route: RouteRecord) => {
              if (route?.meta?.label) {
                const translateResult = i18n.t(route.meta.label);
                if (typeof translateResult === 'string')
                  pluginContext.currentBreadcrumbs.push(translateResult);
              }
            });
            recordActivity(pluginContext.socketService?.instance, {
              event: RECORD_EVENT.navigation,
            });
          }
        }
      }
    );
  },
  uninstall() {
    pluginContext.cancelSubscription();
  },
};

// Дожидаемся авторизации пользователя что бы открыть сокет
// в котором будет передан токен через Cookie под именем JWT
const interval = setInterval(() => {
  store.dispatch('oidcClient/getAccessToken').then((accessToken: string) => {
    if (accessToken) {
      WebsocketService.create({
        url: `wss://${document.location.host}/service/ervu-ui-action-service/action/message`,
      });
      clearInterval(interval);
    }
  });
}, 500);

export default ActivityRecorderPlugin;
