import type { ElegantConstRoute, LastLevelRouteKey } from '@elegant-router/types';
import { objectContextDomainConfigs } from '@/constants/object-context';
import { SYSTEM_SERVICE_PREFIX } from '@/constants/service';
import { createStaticRoutes } from '@/router/routes';
import { request } from '../request';
import { type ServiceRequestResult, safeJsonRequestConfig } from './shared';

type BackendMenuRoute = Omit<Api.Route.MenuRoute, 'id' | 'children'> & {
  id: string | number;
  children?: BackendMenuRoute[];
};

interface BackendUserRouteDTO {
  routes?: BackendMenuRoute[] | null;
  home?: string | null;
}

let userRoutePromise: Promise<ServiceRequestResult<BackendUserRouteDTO>> | null = null;

const staticObjectContextRouteMap = new Map<App.ObjectContext.DomainKey, ElegantConstRoute>(
  createStaticRoutes()
    .authRoutes.filter(route => objectContextDomainConfigs.some(config => config.domainKey === route.name))
    .map(route => [route.name as App.ObjectContext.DomainKey, route])
);

export function clearUserRouteCache() {
  userRoutePromise = null;
}

function normalizeMenuRoute(route: BackendMenuRoute): Api.Route.MenuRoute {
  return {
    ...route,
    id: String(route.id),
    children: route.children?.map(child => normalizeMenuRoute(child))
  };
}

function normalizePath(path?: string | null) {
  if (!path) {
    return '/';
  }

  return path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;
}

function isPathMatchedByPrefix(path: string, prefix: string) {
  const normalizedPath = normalizePath(path);
  const normalizedPrefix = normalizePath(prefix);

  return normalizedPath === normalizedPrefix || normalizedPath.startsWith(`${normalizedPrefix}/`);
}

function isTopLevelObjectContextEntryRoute(route: Api.Route.MenuRoute, config: App.ObjectContext.DomainConfig) {
  const routePath = normalizePath(route.path);

  return (
    route.component?.startsWith('view.') &&
    !route.children?.length &&
    (route.name === config.entryRouteKey || routePath === normalizePath(config.entryRoutePath))
  );
}

function cloneStaticRouteAsMenuRoute(route: ElegantConstRoute, idPrefix: string): Api.Route.MenuRoute {
  return {
    ...route,
    id: `${idPrefix}:${String(route.name || route.path)}`,
    children: route.children?.map(child => cloneStaticRouteAsMenuRoute(child, idPrefix))
  };
}

function replaceWithStaticObjectContextDomainRoute(routes: Api.Route.MenuRoute[]) {
  let normalizedRoutes = [...routes];

  objectContextDomainConfigs.forEach(config => {
    const hasDomainRootRoute = normalizedRoutes.some(route => route.name === config.domainKey);

    if (hasDomainRootRoute) {
      return;
    }

    const domainTopLevelRoutes = normalizedRoutes.filter(route =>
      config.routePathPrefixes.some(prefix => isPathMatchedByPrefix(route.path, prefix))
    );

    const entryRoute = domainTopLevelRoutes.find(route => isTopLevelObjectContextEntryRoute(route, config));

    if (!entryRoute) {
      return;
    }

    const staticDomainRoute = staticObjectContextRouteMap.get(config.domainKey);

    if (!staticDomainRoute) {
      return;
    }

    // Create a map of backend routes by name for quick lookup
    const backendRouteMap = new Map<string, Api.Route.MenuRoute>();
    domainTopLevelRoutes.forEach(route => {
      if (route.name) {
        backendRouteMap.set(String(route.name), route);
      }
    });

    // Clone static route but preserve backend route's meta for children
    // 待重构：拆 helper 以降低复杂度，暂以 disable 注释临时放行
    // eslint-disable-next-line complexity
    function cloneStaticRoutePreservingBackendMeta(route: ElegantConstRoute, idPrefix: string): Api.Route.MenuRoute {
      const backendRoute = route.name ? backendRouteMap.get(String(route.name)) : undefined;
      const { children: _children, ...routeWithoutChildren } = route;
      const baseRoute: Api.Route.MenuRoute = {
        ...routeWithoutChildren,
        id: `${idPrefix}:${String(route.name || route.path)}`
      };

      // If there's a backend route, preserve its meta
      if (backendRoute?.meta) {
        baseRoute.meta = {
          ...baseRoute.meta,
          title: backendRoute.meta.title || baseRoute.meta?.title || String(route.name || route.path),
          icon: backendRoute.meta.icon || baseRoute.meta?.icon,
          localIcon: backendRoute.meta.localIcon || baseRoute.meta?.localIcon,
          order:
            backendRoute.meta.order !== undefined && backendRoute.meta.order !== null
              ? backendRoute.meta.order
              : baseRoute.meta?.order,
          keepAlive:
            backendRoute.meta.keepAlive !== undefined && backendRoute.meta.keepAlive !== null
              ? backendRoute.meta.keepAlive
              : baseRoute.meta?.keepAlive,
          i18nKey: backendRoute.meta.i18nKey || baseRoute.meta?.i18nKey
        };
      }

      // Recursively process children
      if (route.children?.length) {
        baseRoute.children = route.children.map(child => cloneStaticRoutePreservingBackendMeta(child, idPrefix));
      }

      return baseRoute;
    }

    const wrappedDomainRoute = cloneStaticRoutePreservingBackendMeta(
      staticDomainRoute,
      `object-context:${config.domainKey}`
    );

    // Merge entry route's meta to domain route
    if (entryRoute.meta) {
      wrappedDomainRoute.meta = {
        ...wrappedDomainRoute.meta,
        title: entryRoute.meta.title || wrappedDomainRoute.meta?.title || config.domainKey,
        icon: entryRoute.meta.icon || wrappedDomainRoute.meta?.icon,
        localIcon: entryRoute.meta.localIcon || wrappedDomainRoute.meta?.localIcon,
        order:
          entryRoute.meta.order !== undefined && entryRoute.meta.order !== null
            ? entryRoute.meta.order
            : wrappedDomainRoute.meta?.order,
        keepAlive:
          entryRoute.meta.keepAlive !== undefined && entryRoute.meta.keepAlive !== null
            ? entryRoute.meta.keepAlive
            : wrappedDomainRoute.meta?.keepAlive
      };
    }

    const entryRouteIndex = normalizedRoutes.findIndex(route => route.id === entryRoute.id);
    const domainRouteIds = new Set(domainTopLevelRoutes.map(route => route.id));

    normalizedRoutes = normalizedRoutes.filter(route => !domainRouteIds.has(route.id));
    normalizedRoutes.splice(entryRouteIndex < 0 ? normalizedRoutes.length : entryRouteIndex, 0, wrappedDomainRoute);
  });

  return normalizedRoutes;
}

function normalizeUserRoute(data: BackendUserRouteDTO): Api.Route.UserRoute {
  return {
    routes: replaceWithStaticObjectContextDomainRoute((data.routes ?? []).map(route => normalizeMenuRoute(route))),
    home: (data.home || 'system_user') as LastLevelRouteKey
  };
}

/** 获取常量路由 */
export function fetchGetConstantRoutes() {
  return request<Api.Route.MenuRoute[]>({
    ...safeJsonRequestConfig,
    url: '/route/getConstantRoutes'
  });
}

/** 获取用户路由 */
export async function fetchGetUserRoutes(force = false): Promise<ServiceRequestResult<Api.Route.UserRoute>> {
  if (!userRoutePromise || force) {
    userRoutePromise = request<BackendUserRouteDTO>({
      ...safeJsonRequestConfig,
      url: `${SYSTEM_SERVICE_PREFIX}/auth/get-user-routes`
    }).then(result => result as ServiceRequestResult<BackendUserRouteDTO>);
  }

  const result = await userRoutePromise;

  if (result.error || !result.data) {
    userRoutePromise = null;
    return result as ServiceRequestResult<Api.Route.UserRoute>;
  }

  return {
    ...result,
    data: normalizeUserRoute(result.data)
  };
}

/**
 * 判断路由是否存在
 *
 * @param routeName 路由名称
 */
export async function fetchIsRouteExist(routeName: string): Promise<ServiceRequestResult<boolean>> {
  const result = await fetchGetUserRoutes();

  if (result.error || !result.data) {
    return {
      ...result,
      data: false
    } as unknown as ServiceRequestResult<boolean>;
  }

  const isExist = result.data.routes.some(route => recursiveIsRouteExist(route, routeName));

  return {
    ...result,
    data: isExist
  };
}

function recursiveIsRouteExist(route: Api.Route.MenuRoute, routeName: string): boolean {
  if (route.name === routeName) {
    return true;
  }

  return route.children?.some(child => recursiveIsRouteExist(child, routeName)) || false;
}
