import { getUserInfo } from '@/api';
import router, { type RoutePermission, type RoutePermissionRelation } from '@/router';
import type { UserInfo } from '@/schemas/response';
import { type idAndNameSchema } from '@/schemas/response';
import { ensureArray } from '@/utils/array';
import type { MaybeArray, MaybePromise } from '@/utils/types';
import { StorageSerializers, useLocalStorage } from '@vueuse/core';
import { ElMessage } from 'element-plus';
import { isEqual } from 'lodash-es';
import { defineStore } from 'pinia';
import { computed, ref, watch } from 'vue';
import type { RouteLocationAsRelativeGeneric, RouteMeta } from 'vue-router';
import { z } from 'zod';
import { PageErrorType } from '../views/ErrorPage.vue';
import type { ClubPermissionId, UserPermissionId } from '@/router/permissions';
type Permission = z.infer<typeof idAndNameSchema>;
function hasPermission(
  permissionMap: Map<number, Permission>,
  permissionId: RoutePermission<number>,
) {
  let arr: MaybeArray<number>, relation: RoutePermissionRelation;
  if (Array.isArray(permissionId) || typeof permissionId === 'number') {
    arr = permissionId;
    relation = 'AND';
  } else {
    ({ id: arr, relation = 'AND' } = permissionId);
  }
  arr = ensureArray(arr);
  const check = (id: number) => permissionMap.has(id);
  if (relation === 'AND') {
    return arr.every(check);
  } else {
    return arr.some(check);
  }
}
export const useUserStore = defineStore('user', () => {
  const token = useLocalStorage<string | null>('token', null, {
    serializer: StorageSerializers.string,
  });
  const userInfo = useLocalStorage<UserInfo | null>('user-info', null, {
    serializer: StorageSerializers.object,
  });
  const userPermissions = computed(
    () =>
      new Map<number, Permission>(
        (userInfo.value?.auth.permissions ?? []).map((permission) => [permission.id, permission]),
      ),
  );
  const clubPermissions = computed(
    () =>
      new Map<number, Permission>(
        (userInfo.value?.clubAuth?.permissions ?? []).map((permission) => [
          permission.id,
          permission,
        ]),
      ),
  );
  const hasUserPermission = (userPermission: RoutePermission<UserPermissionId> | undefined) =>
    userPermission === undefined || hasPermission(userPermissions.value, userPermission);
  const hasClubPermission = (clubPermission: RoutePermission<ClubPermissionId> | undefined) =>
    clubPermission === undefined || hasPermission(clubPermissions.value, clubPermission);
  function isRouteAccessible(to: string | RouteLocationAsRelativeGeneric): true | PageErrorType {
    const {
      meta: { shouldLogin, permission },
    } = router.resolve(to);
    if (shouldLogin && !logined.value) {
      return PageErrorType.NOT_LOGIN;
    }
    if (!permission) return true;
    if (typeof permission === 'function') {
      return permission(useUserStore()) ? true : PageErrorType.NO_PERMISSION;
    }
    const { user: userPermission, club: clubPermission } = permission;
    if (!hasUserPermission(userPermission) || !hasClubPermission(clubPermission)) {
      return PageErrorType.NO_PERMISSION;
    }
    return true;
  }
  const initializing = ref(false);
  const logined = computed(() => (userInfo.value && userInfo.value.id !== -1) ?? false);
  watch(
    userInfo,
    (info, old) => {
      if (isEqual(info, old)) return;
      router.push({
        path: info && info.id !== -1 ? router.currentRoute.value.fullPath : '/',
        force: true,
      });
    },
    { flush: 'sync' },
  );
  const isSelf = (id: string | number) => userInfo.value?.id === Number(id);
  async function updateSelfUserInfo(): Promise<boolean> {
    initializing.value = true;
    try {
      const resp = await getUserInfo({
        onValidationError(resp) {
          console.log('error');
          // 验证失败，则清除token
          if (resp.status === 401) {
            token.value = null;
            ElMessage.error('Token验证失败，请重新登录！');
            updateSelfUserInfo();
            return true;
          }
        },
      });
      if (!resp) {
        if (!token.value) {
          userInfo.value = null;
        }
        return false;
      }
      userInfo.value = resp.data;
      return true;
    } finally {
      initializing.value = false;
    }
  }

  const logoutInterceptors = new Set<LogoutInterceptor>();
  function addLogoutInterceptor(fn: LogoutInterceptor) {
    logoutInterceptors.add(fn);
  }
  function removeLogoutInterceptor(fn: LogoutInterceptor) {
    logoutInterceptors.delete(fn);
  }
  async function logout() {
    const res = await Promise.all(Array.from(logoutInterceptors).map((fn) => fn()));
    if (!res.every((r) => r === undefined || r)) return false;
    token.value = null;
    await updateSelfUserInfo();
    ElMessage.info('退出成功');
    return true;
  }
  return {
    token,
    userInfo,
    logined,
    userPermissions,
    hasUserPermission,
    clubPermissions,
    hasClubPermission,
    isRouteAccessible,
    initializing,
    isSelf,
    updateSelfUserInfo,
    logoutInterceptors,
    addLogoutInterceptor,
    removeLogoutInterceptor,
    logout,
  };
});
export type LogoutInterceptor = () => MaybePromise<boolean | void>;
