import type { NavigationGuardNext, RouteLocationNormalized, RouteLocationRaw, Router } from 'vue-router';
import type { RouteKey, RoutePath } from '@elegant-router/types';
import { useAuthStore } from '@/store/modules/auth';
import { useRouteStore } from '@/store/modules/route';
import { localStg } from '@/utils/storage';

/**
 * create route guard
 *
 * @param router router instance
 */
export function createRouteGuard(router: Router) {
  router.beforeEach(async (to, from, next) => {
    const location = await initRoute(to);

    if (location) {
      next(location);
      return;
    }

    const authStore = useAuthStore();

    const loginRoute: RouteKey = 'login';
    const noAuthorizationRoute: RouteKey = '403';

    // IsAuthenticated 字段具有决定性权威
    const globalIsAuthenticated = window.uiGlobalConfig?.IsAuthenticated;
    const hasLocalToken = Boolean(localStg.get('token'));

    // 如果 IsAuthenticated 明确为 false，则必须跳转登录页
    if (globalIsAuthenticated === false) {
      // 清除所有本地认证数据
      localStg.remove('token');
      localStg.remove('refreshToken');
      localStg.remove('userInfo');

      const needLogin = !to.meta.constant;
      if (needLogin) {
        next({ name: loginRoute, query: { redirect: to.fullPath } });
        return;
      }
    }

    // 如果 IsAuthenticated 为 true 或 undefined，则检查 token 状态
    const isAuthenticated = globalIsAuthenticated === true || (globalIsAuthenticated === undefined && hasLocalToken);

    const needLogin = !to.meta.constant;
    const routeRoles = to.meta.roles || [];

    const hasRole = authStore.userInfo.roles.some(role => routeRoles.includes(role));
    const hasAuth = authStore.isStaticSuper || !routeRoles.length || hasRole;

    // 如果有token但uiGlobalConfig未设置，则设置认证状态
    if (hasLocalToken && globalIsAuthenticated === undefined && window.uiGlobalConfig) {
      window.uiGlobalConfig.IsAuthenticated = true;
    }

    // 如果未认证，清除token并跳转登录页
    if (!isAuthenticated && needLogin) {
      localStg.remove('token');
      localStg.remove('refreshToken');
      localStg.remove('userInfo');
      next({ name: loginRoute, query: { redirect: to.fullPath } });
      return;
    }

    const routeSwitches: CommonType.StrategicPattern[] = [
      // if it is constant route, then it is allowed to access directly
      {
        condition: !needLogin,
        callback: () => {
          handleRouteSwitch(to, from, next);
        }
      },
      // if the user has authorization, then it is allowed to access
      {
        condition: needLogin && hasAuth,
        callback: () => {
          handleRouteSwitch(to, from, next);
        }
      },
      // if the user does not have authorization, then switch to the 403 page
      {
        condition: needLogin && !hasAuth,
        callback: () => {
          next({ name: noAuthorizationRoute });
        }
      }
    ];

    routeSwitches.some(({ condition, callback }) => {
      if (condition) {
        callback();
      }

      return condition;
    });
  });
}

/**
 * initialize route
 *
 * @param to to route
 */
async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw | null> {
  const authStore = useAuthStore();
  const routeStore = useRouteStore();

  const notFoundRoute: RouteKey = 'not-found';
  const isNotFoundRoute = to.name === notFoundRoute;

  // if the constant route is not initialized, then initialize the constant route
  if (!routeStore.isInitConstantRoute) {
    await routeStore.initConstantRoute();

    // the route is captured by the "not-found" route because the constant route is not initialized
    // after the constant route is initialized, redirect to the original route
    if (isNotFoundRoute) {
      const path = to.fullPath;

      const location: RouteLocationRaw = {
        path,
        replace: true,
        query: to.query,
        hash: to.hash
      };

      return location;
    }
  }

  // if the route is the constant route but is not the "not-found" route, then it is allowed to access.
  if (to.meta.constant && !isNotFoundRoute) {
    return null;
  }

  // the auth route is initialized
  // it is not the "not-found" route, then it is allowed to access
  if (routeStore.isInitAuthRoute && !isNotFoundRoute) {
    return null;
  }
  // it is captured by the "not-found" route, then check whether the route exists
  if (routeStore.isInitAuthRoute && isNotFoundRoute) {
    const exist = await routeStore.getIsAuthRouteExist(to.path as RoutePath);
    const noPermissionRoute: RouteKey = '403';

    if (exist) {
      const location: RouteLocationRaw = {
        name: noPermissionRoute
      };

      return location;
    }

    return null;
  }

  // if the auth route is not initialized, then initialize the auth route
  await authStore.initUserInfo();

  // initialize the auth route
  await routeStore.initAuthRoute();

  // the route is captured by the "not-found" route because the auth route is not initialized
  // after the auth route is initialized, redirect to the original route
  if (isNotFoundRoute) {
    const rootRoute: RouteKey = 'root';
    const path = to.redirectedFrom?.name === rootRoute ? '/' : to.fullPath;

    const location: RouteLocationRaw = {
      path,
      replace: true,
      query: to.query,
      hash: to.hash
    };

    return location;
  }

  return null;
}

function handleRouteSwitch(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
  // route with href
  if (to.meta.href) {
    window.open(to.meta.href, '_blank');

    next({ path: from.fullPath, replace: true, query: from.query, hash: to.hash });

    return;
  }

  next();
}
