import { useMemo, useState, useEffect, useRef, useCallback } from 'react';
import { customRequest } from 'helpers/http';
import type { Role } from 'types/role';

const RoleCacheKey = 'oiterproles';
const PagesCacheKey = 'oiterpages';
const RolePagesCacheKey = 'oiterprolepages';
const RolesDataCacheKey = 'oiterprolesdatacache';

/**
 * Base Hook: User Page Access Data
 *
 * This hook fetches and provides user's accessible page data.
 * It's designed to be reused by:
 * - useDynamicResources (for menu generation)
 * - Any other component that needs page access info
 *
 * Data Flow:
 * SessionStorage(roles) → Role IDs → RolePages → Accessible Page IDs → Page Codes
 */

export interface Page {
  id: number;
  code: string;
  name: string;
  description?: string;
  zhName?: string;
  enName?: string;
  iframeUrl?: string;
  externalRedirectUrl?: string;
  icon?: string;
  path: string;
  parentId?: number | null;
  isVirtual?: boolean;
  sortOrder: number;
}

interface RolePage {
  id: number;
  roleId: number;
  pageId: number;
}

export interface UseUserPageAccessResult {
  /** Set of page IDs the user can access */
  accessiblePageIds: Set<number>;
  /** Set of page codes the user can access */
  accessiblePageCodes: Set<string>;
  /** All pages data from backend */
  pagesData: Page[] | null;
  /** Role pages mapping data */
  rolePagesData: RolePage[] | null;
  /** Map of pageId -> Page object */
  pageMap: Map<number, Page>;
  /** Map of pageCode -> Page object */
  pageCodeMap: Map<string, Page>;
  /** User's role names */
  roleNames: string[];
  /** User's role IDs */
  roleIds: number[];
  /** Whether data is still loading */
  isLoading: boolean;
  /** Check if user can access a page by code */
  canAccessPage: (pageCode: string) => boolean;
}

function readCache<T>(key: string): T | null {
  try {
    const raw = sessionStorage.getItem(key);
    return raw ? (JSON.parse(raw) as T) : null;
  } catch {
    return null;
  }
}

function writeCache(key: string, value: unknown): void {
  try {
    sessionStorage.setItem(key, JSON.stringify(value));
  } catch {
    // ignore quota errors
  }
}

export const useUserPageAccess = (): UseUserPageAccessResult => {
  // Role names — initialise from cache so first render is non-empty
  const [roleNames, setRoleNames] = useState<string[]>(
    () => readCache<string[]>(RoleCacheKey) ?? [],
  );
  // isRoleLoading only stays true while we wait for the live /userinfo response
  const [isRoleLoading, setIsRoleLoading] = useState(true);

  // Data states — initialise from cache for instant first render
  const [rolesData, setRolesData] = useState<Role[] | null>(() =>
    readCache<Role[]>(RolesDataCacheKey),
  );
  const [rolePagesData, setRolePagesData] = useState<RolePage[] | null>(() =>
    readCache<RolePage[]>(RolePagesCacheKey),
  );
  const [pagesData, setPagesData] = useState<Page[] | null>(() =>
    readCache<Page[]>(PagesCacheKey),
  );

  // Track previous roles to detect changes
  const prevRolesRef = useRef<string>('');

  // Function to update roles in state and sessionStorage
  const updateRoles = useCallback((newRoles: string[]) => {
    const rolesKey = JSON.stringify(newRoles.sort());
    if (rolesKey !== prevRolesRef.current) {
      console.log('[useUserPageAccess] Roles changed:', newRoles);
      prevRolesRef.current = rolesKey;
      setRoleNames(newRoles);
      writeCache(RoleCacheKey, newRoles);
    }
  }, []);

  // Step 1: Fetch roles directly from backend to ensure fresh data
  useEffect(() => {
    const fetchRolesFromBackend = async () => {
      try {
        const res = await customRequest<any>('userinfo', 'GET', {});
        const roles = res?.configPerms?.roles;
        if (roles && Array.isArray(roles)) {
          updateRoles(roles as string[]);
        }
      } catch (error) {
        console.error(
          '[useUserPageAccess] Failed to fetch roles from backend:',
          error,
        );
        // Fallback to sessionStorage if backend request fails
        try {
          const cached = readCache<string[]>(RoleCacheKey);
          if (cached) {
            updateRoles(cached);
          }
        } catch (e) {
          console.error('[useUserPageAccess] Failed to parse cached roles:', e);
        }
      } finally {
        setIsRoleLoading(false);
      }
    };

    fetchRolesFromBackend();
  }, [updateRoles]);

  // Step 2: Fetch all page definitions
  useEffect(() => {
    const fetchPages = async () => {
      try {
        const res = await customRequest<any>('page/findMany', 'post', {});
        const pages = res?.data as Page[];
        // Keep existing reference if page IDs haven't changed — prevents
        // downstream memos (accessiblePageIds, dynamicResources, allResources)
        // from rebuilding and giving Refine a new resources prop.
        setPagesData((prev) => {
          if (
            prev &&
            prev.length === pages.length &&
            prev.every((p, i) => p.id === pages[i].id)
          ) {
            return prev;
          }
          writeCache(PagesCacheKey, pages);
          return pages;
        });
      } catch (error) {
        console.error('[useUserPageAccess] Failed to fetch pages:', error);
      }
    };
    fetchPages();
  }, []);

  // Step 3: Fetch role IDs when roleNames are available
  useEffect(() => {
    if (roleNames.length === 0) {
      setRolesData(null);
      return;
    }

    const fetchRoles = async () => {
      try {
        const query = {
          where: { name: { in: roleNames } },
          select: { id: true, name: true },
        };
        const res = await customRequest<any>('role/findMany', 'post', query);
        const roles = res?.data as Role[];
        setRolesData(roles);
        writeCache(RolesDataCacheKey, roles);
      } catch (error) {
        console.error('[useUserPageAccess] Failed to fetch roles:', error);
      }
    };
    fetchRoles();
  }, [roleNames.join(',')]);

  // Calculate roleIds
  const roleIds = useMemo(() => {
    if (!rolesData) return [];
    return rolesData.map((role) => role.id);
  }, [rolesData]);

  // Step 4: Fetch role-page relationships when roleIds are available
  useEffect(() => {
    if (roleIds.length === 0) {
      setRolePagesData(null);
      return;
    }

    const fetchRolePages = async () => {
      try {
        const query = { where: { roleId: { in: roleIds } } };
        const res = await customRequest<any>(
          'rolePages/findMany',
          'post',
          query,
        );
        const rolePages = res?.data as RolePage[];
        // Same stable-reference trick: keep existing ref if content unchanged.
        setRolePagesData((prev) => {
          if (
            prev &&
            prev.length === rolePages.length &&
            prev.every((rp, i) => rp.id === rolePages[i].id)
          ) {
            return prev;
          }
          writeCache(RolePagesCacheKey, rolePages);
          return rolePages;
        });
      } catch (error) {
        console.error('[useUserPageAccess] Failed to fetch rolePages:', error);
      }
    };

    fetchRolePages();
  }, [roleIds.join(',')]);

  // Build page maps
  const { pageMap, pageCodeMap } = useMemo(() => {
    const idMap = new Map<number, Page>();
    const codeMap = new Map<string, Page>();

    if (pagesData) {
      pagesData.forEach((page) => {
        idMap.set(page.id, page);
        codeMap.set(page.code, page);
      });
    }

    return { pageMap: idMap, pageCodeMap: codeMap };
  }, [pagesData]);

  // Track previous accessiblePageIds to prevent unnecessary reference changes
  const prevAccessiblePageIdsRef = useRef<Set<number>>(new Set());

  // Build accessible page IDs (with ancestor pages)
  const accessiblePageIds = useMemo(() => {
    const ids = new Set<number>();

    if (!rolePagesData || !pagesData) {
      return prevAccessiblePageIdsRef.current;
    }

    // Add directly accessible pages
    rolePagesData.forEach((rolePage) => {
      ids.add(rolePage.pageId);
    });

    // Recursively add ancestor pages
    const addAncestorPages = (pageId: number, visited: Set<number>) => {
      if (visited.has(pageId)) return;
      visited.add(pageId);

      const page = pageMap.get(pageId);
      if (!page) return;

      if (page.parentId) {
        ids.add(page.parentId);
        addAncestorPages(page.parentId, visited);
      }
    };

    const originalIds = Array.from(ids);
    const visited = new Set<number>();
    originalIds.forEach((pageId) => {
      addAncestorPages(pageId, visited);
    });

    // Keep existing reference if IDs haven't changed
    const prev = prevAccessiblePageIdsRef.current;
    if (prev.size === ids.size && Array.from(ids).every((id) => prev.has(id))) {
      return prev;
    }

    prevAccessiblePageIdsRef.current = ids;
    return ids;
  }, [rolePagesData, pagesData, pageMap]);

  // Build accessible page codes
  const accessiblePageCodes = useMemo(() => {
    const codes = new Set<string>();

    accessiblePageIds.forEach((pageId) => {
      const page = pageMap.get(pageId);
      if (page) {
        codes.add(page.code);
      }
    });

    return codes;
  }, [accessiblePageIds, pageMap]);

  // Check if user can access a specific page
  const canAccessPage = useMemo(() => {
    return (pageCode: string): boolean => {
      return accessiblePageCodes.has(pageCode);
    };
  }, [accessiblePageCodes]);

  // Calculate loading state
  // If we have cached data, isLoading is false even while background-refreshing
  const hasCache = useMemo(
    () => !!(readCache(PagesCacheKey) && readCache(RolePagesCacheKey)),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [],
  );

  const isLoading = useMemo(() => {
    // If cache was available on mount, never block rendering
    if (hasCache && pagesData && rolePagesData) return false;
    if (isRoleLoading) return true;
    if (!pagesData) return true;
    // 等待 /role/findMany 返回（roleNames 已拿到但 rolesData 还在路上）
    if (roleNames.length > 0 && !rolesData) return true;
    // 等待 /rolePages/findMany 返回
    if (roleIds.length > 0 && !rolePagesData) return true;
    return false;
  }, [
    isRoleLoading,
    pagesData,
    roleNames,
    rolesData,
    roleIds,
    rolePagesData,
    hasCache,
  ]);

  return {
    accessiblePageIds,
    accessiblePageCodes,
    pagesData,
    rolePagesData,
    pageMap,
    pageCodeMap,
    roleNames,
    roleIds,
    isLoading,
    canAccessPage,
  };
};
