import { useEffect, useCallback } from 'react';
import { MatrixClient, Room, RoomEvent } from 'matrix-js-sdk';
import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces';
import { useAtomValue, useSetAtom } from 'jotai';
import { Membership } from '../../types/matrix/room';
import { isSpace } from '../utils/room';
import { settingsAtom } from '../state/settings';
import {
  setRoomNotificationPreference,
  RoomNotificationMode,
} from './useRoomsNotificationPreferences';
import { updateJoiningProgressAtom } from '../state/joiningProgress';
import { useMatrixClient } from './useMatrixClient';

const PER_PAGE_COUNT = 100;
const MAX_PAGES = 50;
const JOIN_DELAY_MS = 500;

/** Join rules that allow automatic joining */
const AUTO_JOINABLE_RULES = ['public', 'restricted', 'knock_restricted'];

const processingSpaces = new Set<string>();

/**
 * Extracts the server name from a Matrix room ID
 * @param roomId - The room ID (e.g., "!abc123:matrix.org")
 * @returns The server name (e.g., "matrix.org")
 */
function getServerFromRoomId(roomId: string): string {
  const parts = roomId.split(':');
  return parts.length > 1 ? parts[1] : '';
}

/**
 * Checks if a room/space can be automatically joined based on its join rule
 * @param hierarchyRoom - The room info from the space hierarchy
 * @returns Whether the room/space should be auto-joined
 */
function canAutoJoin(hierarchyRoom: IHierarchyRoom): boolean {
  const { join_rule: joinRule } = hierarchyRoom;

  // Only join public or restricted rooms/spaces
  // Restricted ones are joinable if you're a member of the parent space
  if (!joinRule) {
    return false;
  }

  return AUTO_JOINABLE_RULES.includes(joinRule);
}

/**
 * Checks if a hierarchy item is a space (including m.forum category spaces)
 * @param hierarchyRoom - The room info from the space hierarchy
 * @returns Whether the item is a space
 */
function isHierarchySpace(hierarchyRoom: IHierarchyRoom): boolean {
  return hierarchyRoom.room_type === 'm.space' || hierarchyRoom.room_type === 'm.forum';
}

/**
 * Fetches all rooms from a space hierarchy
 * @param mx - Matrix client
 * @param spaceId - The space room ID
 * @returns Array of hierarchy rooms
 */
async function fetchSpaceHierarchy(
  mx: MatrixClient,
  spaceId: string
): Promise<IHierarchyRoom[]> {
  const allRooms: IHierarchyRoom[] = [];
  let nextBatch: string | undefined;
  let pageCount = 0;

  while (pageCount < MAX_PAGES) {
    try {
      // eslint-disable-next-line no-await-in-loop
      const result = await mx.getRoomHierarchy(spaceId, PER_PAGE_COUNT, undefined, false, nextBatch);
      allRooms.push(...result.rooms);
      nextBatch = result.next_batch;
      pageCount += 1;

      if (!nextBatch) {
        break;
      }
    } catch {
      break;
    }
  }

  return allRooms;
}

/**
 * Delays execution for a specified time
 * @param ms - Milliseconds to delay
 */
function delay(ms: number): Promise<void> {
  return new Promise<void>((resolve) => {
    setTimeout(resolve, ms);
  });
}

/** Callback for reporting join progress */
type ProgressCallback = (current: number, total: number) => void;

/**
 * Attempts to join rooms/spaces with rate limiting and sets notification to Mentions & Keywords for rooms
 * @param mx - Matrix client
 * @param rooms - Array of hierarchy rooms to join
 * @param spaceId - The parent space ID (for via servers)
 * @param onProgress - Callback to report progress
 */
async function joinRoomsSequentially(
  mx: MatrixClient,
  rooms: IHierarchyRoom[],
  spaceId: string,
  onProgress?: ProgressCallback
): Promise<void> {
  const spaceServer = getServerFromRoomId(spaceId);

  const itemsToJoin = rooms.filter((room) => {
    // Check if already joined
    const existingRoom = mx.getRoom(room.room_id);
    if (existingRoom?.getMyMembership() === Membership.Join) {
      return false;
    }

    // Check if can auto-join
    return canAutoJoin(room);
  });

  // Separate spaces and rooms - join spaces first (rooms may depend on space membership)
  const spacesToJoin = itemsToJoin.filter(isHierarchySpace);
  const roomsToJoin = itemsToJoin.filter((r) => !isHierarchySpace(r));

  const total = itemsToJoin.length;
  let current = 0;

  // Report initial progress
  if (total > 0) {
    onProgress?.(current, total);
  }

  // Process item (space or room)
  const joinItem = async (item: IHierarchyRoom, isItemSpace: boolean): Promise<void> => {
    try {
      const itemServer = getServerFromRoomId(item.room_id);
      const viaServers = [itemServer, spaceServer].filter(Boolean);

      await mx.joinRoom(item.room_id, { viaServers });

      // Only set notification preferences for rooms, not spaces
      if (!isItemSpace) {
        await setRoomNotificationPreference(
          mx,
          item.room_id,
          RoomNotificationMode.SpecialMessages,
          RoomNotificationMode.Unset
        );
      }

      await delay(JOIN_DELAY_MS);
    } catch {
      // Continue with other items even if one fails
    } finally {
      current += 1;
      onProgress?.(current, total);
    }
  };

  // Join spaces first, then rooms
  await spacesToJoin.reduce(
    (promise, space) => promise.then(() => joinItem(space, true)),
    Promise.resolve()
  );

  await roomsToJoin.reduce(
    (promise, room) => promise.then(() => joinItem(room, false)),
    Promise.resolve()
  );
}

/**
 * Joins all auto-joinable children of a space (and nested spaces/rooms).
 */
export async function autoJoinSpaceChildren(
  mx: MatrixClient,
  spaceId: string,
  onProgress?: ProgressCallback
): Promise<void> {
  if (processingSpaces.has(spaceId)) {
    return;
  }

  processingSpaces.add(spaceId);

  try {
    const hierarchyRooms = await fetchSpaceHierarchy(mx, spaceId);
    const childRooms = hierarchyRooms.filter((r) => r.room_id !== spaceId);
    await joinRoomsSequentially(mx, childRooms, spaceId, onProgress);
  } finally {
    processingSpaces.delete(spaceId);
  }
}

/**
 * Hook that automatically joins all accessible rooms when joining a space
 * @param mx - Matrix client instance
 */
export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
  const settings = useAtomValue(settingsAtom);
  const setJoiningProgress = useSetAtom(updateJoiningProgressAtom);

  const handleSpaceJoin = useCallback(
    async (room: Room) => {
      if (!settings.autoJoinSpaceRooms) {
        return;
      }

      // Only process spaces (includes m.forum containers)
      if (!isSpace(room)) {
        return;
      }

      // Only process when joining (not invites or leaves)
      if (room.getMyMembership() !== Membership.Join) {
        return;
      }

      const spaceId = room.roomId;

      try {
        await autoJoinSpaceChildren(mx, spaceId, (current, total) => {
          setJoiningProgress({ spaceId, progress: { current, total } });
        });
      } finally {
        setJoiningProgress({ spaceId, progress: null });
      }
    },
    [mx, settings.autoJoinSpaceRooms, setJoiningProgress]
  );

  useEffect(() => {
    const onMembershipChange = (room: Room, membership: string) => {
      if (membership === Membership.Join) {
        handleSpaceJoin(room);
      }
    };

    mx.on(RoomEvent.MyMembership, onMembershipChange);

    return () => {
      mx.removeListener(RoomEvent.MyMembership, onMembershipChange);
    };
  }, [mx, handleSpaceJoin]);
}

/**
 * Auto-joins space children when visiting an already-joined space (e.g. forum spaces).
 */
export function useAutoJoinOnSpaceVisit(space: Room | undefined): void {
  const mx = useMatrixClient();
  const settings = useAtomValue(settingsAtom);
  const setJoiningProgress = useSetAtom(updateJoiningProgressAtom);

  useEffect(() => {
    if (!space || !settings.autoJoinSpaceRooms) {
      return;
    }
    if (!isSpace(space)) {
      return;
    }
    if (space.getMyMembership() !== Membership.Join) {
      return;
    }

    const spaceId = space.roomId;
    let cancelled = false;

    void (async () => {
      try {
        await autoJoinSpaceChildren(mx, spaceId, (current, total) => {
          if (!cancelled) {
            setJoiningProgress({ spaceId, progress: { current, total } });
          }
        });
      } finally {
        if (!cancelled) {
          setJoiningProgress({ spaceId, progress: null });
        }
      }
    })();

    return () => {
      cancelled = true;
    };
  }, [space, settings.autoJoinSpaceRooms, mx, setJoiningProgress]);
}