import { useCallback, useEffect, useMemo, useState } from 'react';
import { Room } from 'matrix-js-sdk';
import { CryptoApi } from 'matrix-js-sdk/lib/crypto-api';
import { verifiedDevice } from '../utils/matrix-crypto';
import { useAlive } from './useAlive';
import { fulfilledPromiseSettledResult } from '../utils/common';
import { useMatrixClient } from './useMatrixClient';
import { useDeviceListChange } from './useDeviceList';

export enum VerificationStatus {
  Unknown,
  Unverified,
  Verified,
  Unsupported,
}

export const useDeviceVerificationDetect = (
  crypto: CryptoApi | undefined,
  userId: string,
  deviceId: string | undefined,
  callback: (status: VerificationStatus) => void,
): void => {
  const mx = useMatrixClient();

  const updateStatus = useCallback(async () => {
    if (crypto && deviceId) {
      const data = await verifiedDevice(crypto, userId, deviceId);
      if (data === null) {
        callback(VerificationStatus.Unsupported);
        return;
      }
      callback(data ? VerificationStatus.Verified : VerificationStatus.Unverified);
      return;
    }
    callback(VerificationStatus.Unknown);
  }, [crypto, deviceId, userId, callback]);

  useEffect(() => {
    updateStatus();
  }, [mx, updateStatus, userId]);

  useDeviceListChange(
    useCallback(
      (userIds) => {
        if (userIds.includes(userId)) {
          updateStatus();
        }
      },
      [userId, updateStatus],
    ),
  );
};

export const useDeviceVerificationStatus = (
  crypto: CryptoApi | undefined,
  userId: string,
  deviceId: string | undefined,
): VerificationStatus => {
  const [verificationStatus, setVerificationStatus] = useState(VerificationStatus.Unknown);

  useDeviceVerificationDetect(crypto, userId, deviceId, setVerificationStatus);

  return verificationStatus;
};

export const useUnverifiedDeviceCount = (
  crypto: CryptoApi | undefined,
  userId: string,
  devices: string[],
): number | undefined => {
  const [unverifiedCount, setUnverifiedCount] = useState<number>();
  const alive = useAlive();

  const updateCount = useCallback(async () => {
    let count = 0;
    if (crypto) {
      const promises = devices.map((deviceId) => verifiedDevice(crypto, userId, deviceId));
      const result = await Promise.allSettled(promises);
      const settledResult = fulfilledPromiseSettledResult(result);
      settledResult.forEach((status) => {
        if (status === false) {
          count += 1;
        }
      });
    }
    if (alive()) {
      setUnverifiedCount(count);
    }
  }, [crypto, userId, devices, alive]);

  useDeviceListChange(
    useCallback(
      (userIds) => {
        if (userIds.includes(userId)) {
          updateCount();
        }
      },
      [userId, updateCount],
    ),
  );

  useEffect(() => {
    updateCount();
  }, [updateCount]);

  return unverifiedCount;
};

export const useRoomUnverifiedDeviceCount = (
  crypto: CryptoApi | undefined,
  room: Room,
): number | undefined => {
  const [unverifiedCount, setUnverifiedCount] = useState<number>();
  const alive = useAlive();

  const memberIds = useMemo(
    () => room.getJoinedMembers().map((m) => m.userId),
    // room.roomId guards against room changes; getJoinedMemberCount() ensures
    // the list refreshes when members join/leave so new devices get checked.
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [room.roomId, room.getJoinedMemberCount()],
  );

  const updateCount = useCallback(async () => {
    if (!crypto) return;

    const deviceMap = await crypto.getUserDeviceInfo(memberIds);
    let count = 0;

    const allChecks: Promise<boolean | null>[] = [];

    deviceMap.forEach((devices, userId) => {
      devices.forEach((_device, deviceId) => {
        allChecks.push(verifiedDevice(crypto, userId, deviceId));
      });
    });

    const results = await Promise.allSettled(allChecks);
    const settled = fulfilledPromiseSettledResult(results);
    settled.forEach((status) => {
      if (status === false) {
        count += 1;
      }
    });

    if (alive()) {
      setUnverifiedCount(count);
    }
  }, [crypto, memberIds, alive]);

  useDeviceListChange(
    useCallback(
      (userIds) => {
        const affected = userIds.some((uid) => memberIds.includes(uid));
        if (affected) {
          updateCount();
        }
      },
      [memberIds, updateCount],
    ),
  );

  useEffect(() => {
    updateCount();
  }, [updateCount]);

  return unverifiedCount;
};
