import React, { useMemo, useState } from 'react';
import FocusTrap from 'focus-trap-react';
import { Dialog, Overlay, OverlayCenter, OverlayBackdrop, Header, config, Box, Text, IconButton, color, Button, Spinner, Scroll, Checkbox, Avatar, Badge } from 'folds';
import { Icon, Icons } from '../icons';
import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { stopPropagation } from '../../utils/keyboard';
import { RoomType, Membership } from '../../../types/matrix/room';
import { RoomAvatar } from '../room-avatar';
import { nameInitials } from '../../utils/common';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import {
  setRoomNotificationPreference,
  RoomNotificationMode,
} from '../../hooks/useRoomsNotificationPreferences';

const PER_PAGE_COUNT = 100;
const MAX_PAGES = 5;
const HIERARCHY_TIMEOUT_MS = 10000;
const JOIN_DELAY_MS = 500;

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

/**
 * Extracts the server name from a Matrix room ID
 */
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
 */
function canAutoJoin(hierarchyRoom: IHierarchyRoom): boolean {
  const { join_rule: joinRule } = hierarchyRoom;
  if (!joinRule) return false;
  return AUTO_JOINABLE_RULES.includes(joinRule);
}

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

type HierarchyRoomItemProps = {
  room: IHierarchyRoom;
  selected: boolean;
  onToggle: () => void;
  useAuthentication: boolean;
  disabled?: boolean;
};

function HierarchyRoomItem({
  room,
  selected,
  onToggle,
  useAuthentication,
  disabled,
}: HierarchyRoomItemProps) {
  const mx = useMatrixClient();
  const roomIsSpace = room.room_type === RoomType.Space;
  const avatarUrl = room.avatar_url
    ? mxcUrlToHttp(mx, room.avatar_url, useAuthentication, 48, 48, 'crop')
    : undefined;

  return (
    <Box
      as="label"
      gap="300"
      alignItems="Center"
      style={{
        padding: config.space.S200,
        borderRadius: config.radii.R300,
        cursor: disabled ? 'not-allowed' : 'pointer',
        opacity: disabled ? 0.6 : 1,
      }}
    >
      <Checkbox
        size="300"
        variant="Primary"
        checked={selected}
        onClick={disabled ? undefined : onToggle}
        disabled={disabled}
      />
      <Avatar size="200">
        <RoomAvatar
          roomId={room.room_id}
          src={avatarUrl ?? undefined}
          alt={room.name || room.room_id}
          renderFallback={() => (
            <Text as="span" size="T200">
              {nameInitials(room.name || room.room_id)}
            </Text>
          )}
        />
      </Avatar>
      <Box direction="Column" grow="Yes" style={{ minWidth: 0 }}>
        <Text size="T300" truncate>
          {room.name || room.room_id}
        </Text>
        {room.topic && (
          <Text size="T200" priority="400" truncate>
            {room.topic}
          </Text>
        )}
      </Box>
      {roomIsSpace && (
        <Badge variant="Secondary" fill="Soft" outlined>
          <Text size="L400">Space</Text>
        </Badge>
      )}
    </Box>
  );
}

type JoinSpacePromptProps = {
  roomIdOrAlias: string;
  viaServers?: string[];
  onDone: (roomId: string) => void;
  onCancel: () => void;
};

type JoinPhase = 'initial' | 'joining-space' | 'selecting-rooms' | 'joining-rooms';

export function JoinSpacePrompt({
  roomIdOrAlias,
  viaServers,
  onDone,
  onCancel,
}: JoinSpacePromptProps) {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();

  const [phase, setPhase] = useState<JoinPhase>('initial');
  const [spaceId, setSpaceId] = useState<string | null>(null);
  const [hierarchyRooms, setHierarchyRooms] = useState<IHierarchyRoom[]>([]);
  const [selectedRoomIds, setSelectedRoomIds] = useState<Set<string>>(new Set());
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [progress, setProgress] = useState<{ current: number; total: number } | null>(null);
  const [canSkipLoading, setCanSkipLoading] = useState(false);

  /**
   * Skips to the space (used when loading takes too long)
   */
  const handleSkipToSpace = () => {
    if (spaceId) {
      onDone(spaceId);
    }
  };

  /**
   * Joins the space first
   */
  const handleJoinSpace = async () => {
    setPhase('joining-space');
    setError(null);
    setCanSkipLoading(false);

    let joinedSpaceId: string;

    try {
      const spaceRoom = await mx.joinRoom(roomIdOrAlias, { viaServers });
      joinedSpaceId = spaceRoom.roomId;
      setSpaceId(joinedSpaceId);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to join space');
      setPhase('initial');
      return;
    }

    // Space is joined - allow skipping from here
    setCanSkipLoading(true);

    // Now fetch the hierarchy with a timeout
    setLoading(true);

    const fetchWithTimeout = async (): Promise<IHierarchyRoom[]> => {
      const allRooms: IHierarchyRoom[] = [];
      let nextBatch: string | undefined;
      let pageCount = 0;

      const timeoutPromise = new Promise<never>((_, reject) => {
        setTimeout(() => reject(new Error('Timeout')), HIERARCHY_TIMEOUT_MS);
      });

      while (pageCount < MAX_PAGES) {
        // eslint-disable-next-line no-await-in-loop
        const result = await Promise.race([
          mx.getRoomHierarchy(joinedSpaceId, PER_PAGE_COUNT, undefined, false, nextBatch),
          timeoutPromise,
        ]);

        allRooms.push(...result.rooms);
        nextBatch = result.next_batch;
        pageCount += 1;

        if (!nextBatch) break;
      }

      return allRooms;
    };

    try {
      const allRooms = await fetchWithTimeout();

      // Filter out items we can't join (excluding the space itself)
      const joinableRooms = allRooms.filter((room) => {
        // Skip the space itself
        if (room.room_id === joinedSpaceId) return false;
        // Check if already joined
        const existingRoom = mx.getRoom(room.room_id);
        if (existingRoom?.getMyMembership() === Membership.Join) {
          return false;
        }
        return canAutoJoin(room);
      });

      setHierarchyRooms(joinableRooms);
      // Select all by default
      setSelectedRoomIds(new Set(joinableRooms.map((r) => r.room_id)));
    } catch {
      // Hierarchy fetch failed or timed out - show empty selection
      setHierarchyRooms([]);
      setSelectedRoomIds(new Set());
    } finally {
      setLoading(false);
      setPhase('selecting-rooms');
    }
  };

  /**
   * Joins selected rooms
   */
  const handleJoinSelectedRooms = async () => {
    if (!spaceId) return;

    setPhase('joining-rooms');
    setError(null);

    const spaceServer = getServerFromRoomId(spaceId);

    // Get selected rooms
    const roomsToJoin = hierarchyRooms.filter((room) => selectedRoomIds.has(room.room_id));

    // Separate spaces and rooms - join spaces first
    const spacesToJoin = roomsToJoin.filter((r) => r.room_type === RoomType.Space);
    const regularRoomsToJoin = roomsToJoin.filter((r) => r.room_type !== RoomType.Space);

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

    if (total > 0) {
      setProgress({ current, total });
    }

    const joinItem = async (item: IHierarchyRoom, isSpace: boolean): Promise<void> => {
      try {
        const itemServer = getServerFromRoomId(item.room_id);
        const itemViaServers = [itemServer, spaceServer].filter(Boolean);

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

        // Set notification preferences for rooms (not spaces)
        if (!isSpace) {
          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;
        setProgress({ current, total });
      }
    };

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

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

    setProgress(null);
    onDone(spaceId);
  };

  /**
   * Skip room selection and just finish
   */
  const handleSkip = () => {
    if (spaceId) {
      onDone(spaceId);
    }
  };

  const toggleRoom = (roomId: string) => {
    setSelectedRoomIds((prev) => {
      const newSet = new Set(prev);
      if (newSet.has(roomId)) {
        newSet.delete(roomId);
      } else {
        newSet.add(roomId);
      }
      return newSet;
    });
  };

  const selectAll = () => {
    setSelectedRoomIds(new Set(hierarchyRooms.map((r) => r.room_id)));
  };

  const selectNone = () => {
    setSelectedRoomIds(new Set());
  };

  const isJoining = phase === 'joining-space' || phase === 'joining-rooms';
  const spaces = useMemo(
    () => hierarchyRooms.filter((r) => r.room_type === RoomType.Space),
    [hierarchyRooms]
  );
  const rooms = useMemo(
    () => hierarchyRooms.filter((r) => r.room_type !== RoomType.Space),
    [hierarchyRooms]
  );

  const getButtonText = () => {
    if (phase === 'initial') return 'Join Space';
    if (phase === 'joining-space') return loading ? 'Loading Rooms...' : 'Joining Space...';
    if (phase === 'selecting-rooms') {
      const selectedCount = selectedRoomIds.size;
      return selectedCount > 0 ? `Join ${selectedCount} Rooms` : 'Done';
    }
    if (progress) return `Joining: ${progress.current}/${progress.total}`;
    return 'Joining...';
  };

  const handlePrimaryAction = () => {
    if (phase === 'initial') {
      handleJoinSpace();
    } else if (phase === 'selecting-rooms') {
      if (selectedRoomIds.size > 0) {
        handleJoinSelectedRooms();
      } else {
        handleSkip();
      }
    }
  };

  const canCancel = phase === 'initial' || phase === 'selecting-rooms' || canSkipLoading;

  const getDeactivateHandler = () => {
    if (!canCancel) return undefined;
    if (canSkipLoading) return handleSkipToSpace;
    return onCancel;
  };

  const getCloseHandler = () => {
    if (phase === 'selecting-rooms') return handleSkip;
    if (canSkipLoading) return handleSkipToSpace;
    return onCancel;
  };

  return (
    <Overlay open backdrop={<OverlayBackdrop />}>
      <OverlayCenter>
        <FocusTrap
          focusTrapOptions={{
            initialFocus: false,
            onDeactivate: getDeactivateHandler(),
            clickOutsideDeactivates: canCancel,
            escapeDeactivates: canCancel ? stopPropagation : false,
          }}
        >
          <Dialog variant="Surface" style={{ maxWidth: '500px', width: '90vw' }}>
            <Header
              style={{
                padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
                borderBottomWidth: config.borderWidth.B300,
              }}
              variant="Surface"
              size="500"
            >
              <Box grow="Yes">
                <Text size="H4">
                  {phase === 'selecting-rooms' ? 'Select Rooms to Join' : 'Join Space'}
                </Text>
              </Box>
              <IconButton
                size="300"
                onClick={getCloseHandler()}
                radii="300"
                disabled={phase === 'joining-space' && !canSkipLoading}
              >
                <Icon src={Icons.Cross} />
              </IconButton>
            </Header>
            <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
              {phase === 'initial' && (
                <Text priority="400" size="T300">
                  Join this space to see and select which rooms to join.
                </Text>
              )}

              {phase === 'joining-space' && (
                <Box direction="Column" gap="200" alignItems="Center">
                  <Box justifyContent="Center" alignItems="Center" gap="200">
                    <Spinner size="200" />
                    <Text size="T300">
                      {loading ? 'Loading space contents...' : 'Joining space...'}
                    </Text>
                  </Box>
                  {canSkipLoading && (
                    <Button size="300" variant="Secondary" fill="Soft" onClick={handleSkipToSpace}>
                      <Text size="B300">Skip to Space</Text>
                    </Button>
                  )}
                </Box>
              )}

              {phase === 'selecting-rooms' && hierarchyRooms.length > 0 && (
                <>
                  <Box direction="Column" gap="200">
                    <Text priority="400" size="T300">
                      Select which rooms and spaces to join:
                    </Text>
                    <Box gap="200">
                      <Button size="300" variant="Secondary" fill="Soft" onClick={selectAll}>
                        <Text size="B300">Select All</Text>
                      </Button>
                      <Button size="300" variant="Secondary" fill="Soft" onClick={selectNone}>
                        <Text size="B300">Select None</Text>
                      </Button>
                    </Box>
                  </Box>

                  <Scroll
                    hideTrack
                    visibility="Hover"
                    style={{ maxHeight: '300px', flexGrow: 1 }}
                  >
                    <Box direction="Column" gap="200">
                      {spaces.length > 0 && (
                        <Box direction="Column" gap="100">
                          <Text size="L400" style={{ color: color.Secondary.Main }}>
                            Spaces ({spaces.length})
                          </Text>
                          {spaces.map((room) => (
                            <HierarchyRoomItem
                              key={room.room_id}
                              room={room}
                              selected={selectedRoomIds.has(room.room_id)}
                              onToggle={() => toggleRoom(room.room_id)}
                              useAuthentication={useAuthentication}
                              disabled={isJoining}
                            />
                          ))}
                        </Box>
                      )}

                      {rooms.length > 0 && (
                        <Box direction="Column" gap="100">
                          <Text size="L400" style={{ color: color.Secondary.Main }}>
                            Rooms ({rooms.length})
                          </Text>
                          {rooms.map((room) => (
                            <HierarchyRoomItem
                              key={room.room_id}
                              room={room}
                              selected={selectedRoomIds.has(room.room_id)}
                              onToggle={() => toggleRoom(room.room_id)}
                              useAuthentication={useAuthentication}
                              disabled={isJoining}
                            />
                          ))}
                        </Box>
                      )}
                    </Box>
                  </Scroll>
                </>
              )}

              {phase === 'selecting-rooms' && hierarchyRooms.length === 0 && (
                <Text priority="400" size="T300">
                  This space has no additional rooms to join.
                </Text>
              )}

              {phase === 'joining-rooms' && (
                <Box justifyContent="Center" alignItems="Center" gap="200">
                  <Spinner size="200" />
                  <Text size="T300">
                    {progress
                      ? `Joining rooms: ${progress.current}/${progress.total}`
                      : 'Joining rooms...'}
                  </Text>
                </Box>
              )}

              {error && (
                <Text style={{ color: color.Critical.Main }} size="T300">
                  {error}
                </Text>
              )}

              <Button
                type="submit"
                variant="Primary"
                onClick={handlePrimaryAction}
                disabled={isJoining}
                before={isJoining ? <Spinner fill="Solid" variant="Primary" size="200" /> : undefined}
                aria-disabled={isJoining}
              >
                <Text size="B400">{getButtonText()}</Text>
              </Button>
            </Box>
          </Dialog>
        </FocusTrap>
      </OverlayCenter>
    </Overlay>
  );
}
