/*
 *   Famedly Matrix SDK
 *   Copyright (C) 2020 Famedly GmbH
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as
 *   published by the Free Software Foundation, either version 3 of the
 *   License, or (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see .
 */
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
import '../fake_client.dart';
import '../fake_database.dart';
EventUpdate getLastSentEvent(KeyVerification req) {
  final entry = FakeMatrixApi.calledEndpoints.entries
      .firstWhere((p) => p.key.contains('/send/'));
  final type = entry.key.split('/')[6];
  final content = json.decode(entry.value.first);
  return EventUpdate(
    content: {
      'event_id': req.transactionId,
      'type': type,
      'content': content,
      'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
      'sender': req.client.userID,
    },
    type: EventUpdateType.timeline,
    roomID: req.room!.id,
  );
}
void main() async {
  /// All Tests related to the ChatTime
  group('Key Verification', tags: 'olm', () {
    Logs().level = Level.error;
    // key @othertest:fakeServer.notExisting
    const otherPickledOlmAccount =
        'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA';
    late Client client1;
    late Client client2;
    setUpAll(() async {
      await olm.init();
      olm.get_library_version();
    });
    setUp(() async {
      client1 = await getClient();
      client2 = Client(
        'othertestclient',
        httpClient: FakeMatrixApi.currentApi!,
        databaseBuilder: getDatabase,
      );
      await client2.checkHomeserver(
        Uri.parse('https://fakeserver.notexisting'),
        checkWellKnown: false,
      );
      await client2.init(
        newToken: 'abc',
        newUserID: '@othertest:fakeServer.notExisting',
        newHomeserver: client2.homeserver,
        newDeviceName: 'Text Matrix Client',
        newDeviceID: 'FOXDEVICE',
        newOlmAccount: otherPickledOlmAccount,
      );
      await Future.delayed(Duration(milliseconds: 10));
      client1.verificationMethods = {
        KeyVerificationMethod.emoji,
        KeyVerificationMethod.numbers,
        KeyVerificationMethod.qrScan,
        KeyVerificationMethod.qrShow,
        KeyVerificationMethod.reciprocate,
      };
      client2.verificationMethods = {
        KeyVerificationMethod.emoji,
        KeyVerificationMethod.numbers,
        KeyVerificationMethod.qrShow,
        KeyVerificationMethod.reciprocate,
      };
      // cancel sync to reduce background load and prevent sync overwriting which keys are tracked.
      await client1.abortSync();
      await client2.abortSync();
      // get client2 device keys to start verification
      await client1.updateUserDeviceKeys(additionalUsers: {client2.userID!});
      await client2.updateUserDeviceKeys(additionalUsers: {client1.userID!});
    });
    tearDown(() async {
      await client1.dispose(closeDatabase: true);
      await client2.dispose(closeDatabase: true);
    });
    test('Run emoji / number verification', () async {
      // for a full run we test in-room verification in a cleartext room
      // because then we can easily intercept the payloads and inject in the other client
      FakeMatrixApi.calledEndpoints.clear();
      // make sure our master key is *not* verified to not triger SSSS for now
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(false);
      final req1 =
          await client1.userDeviceKeys[client2.userID]!.startVerification(
        newDirectChatEnableEncryption: false,
      );
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
        ),
      );
      var evt = getLastSentEvent(req1);
      expect(req1.state, KeyVerificationState.waitingAccept);
      final comp = Completer();
      final sub = client2.onKeyVerificationRequest.stream.listen((req) {
        comp.complete(req);
      });
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      final req2 = await comp.future;
      await sub.cancel();
      expect(
        client2.encryption!.keyVerificationManager
            .getRequest(req2.transactionId!),
        req2,
      );
      // send ready
      FakeMatrixApi.calledEndpoints.clear();
      await req2.acceptVerification();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
        ),
      );
      evt = getLastSentEvent(req2);
      expect(req2.state, KeyVerificationState.askChoice);
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req1.possibleMethods, [EventTypes.Sas]);
      expect(req2.possibleMethods, [EventTypes.Sas]);
      expect(req1.state, KeyVerificationState.waitingAccept);
      // no need for start (continueVerification) because sas only mode override already sent it after ready
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start',
        ),
      );
      evt = getLastSentEvent(req1);
      // send accept
      FakeMatrixApi.calledEndpoints.clear();
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.accept',
        ),
      );
      evt = getLastSentEvent(req2);
      // send key
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
        ),
      );
      evt = getLastSentEvent(req1);
      // send key
      FakeMatrixApi.calledEndpoints.clear();
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
        ),
      );
      evt = getLastSentEvent(req2);
      // receive last key
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      // compare emoji
      expect(req1.state, KeyVerificationState.askSas);
      expect(req2.state, KeyVerificationState.askSas);
      expect(req1.sasTypes[0], 'emoji');
      expect(req1.sasTypes[1], 'decimal');
      expect(req2.sasTypes[0], 'emoji');
      expect(req2.sasTypes[1], 'decimal');
      // compare emoji
      final emoji1 = req1.sasEmojis;
      final emoji2 = req2.sasEmojis;
      for (var i = 0; i < 7; i++) {
        expect(emoji1[i].emoji, emoji2[i].emoji);
        expect(emoji1[i].name, emoji2[i].name);
      }
      // compare numbers
      final numbers1 = req1.sasNumbers;
      final numbers2 = req2.sasNumbers;
      for (var i = 0; i < 3; i++) {
        expect(numbers1[i], numbers2[i]);
      }
      // alright, they match
      // send mac
      FakeMatrixApi.calledEndpoints.clear();
      await req1.acceptSas();
      evt = getLastSentEvent(req1);
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac',
        ),
      );
      expect(req1.state, KeyVerificationState.waitingSas);
      // send mac
      FakeMatrixApi.calledEndpoints.clear();
      await req2.acceptSas();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac',
        ),
      );
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done',
        ),
      );
      evt = getLastSentEvent(req2);
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done',
        ),
      );
      expect(req1.state, KeyVerificationState.done);
      expect(req2.state, KeyVerificationState.done);
      expect(
        client1.userDeviceKeys[client2.userID]?.deviceKeys[client2.deviceID]
            ?.directVerified,
        true,
      );
      expect(
        client2.userDeviceKeys[client1.userID]?.deviceKeys[client1.deviceID]
            ?.directVerified,
        true,
      );
      await client1.encryption!.keyVerificationManager.cleanup();
      await client2.encryption!.keyVerificationManager.cleanup();
    });
    test('ask SSSS start', () async {
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(true);
      await client1.encryption!.ssss.clearCache();
      final req1 = await client1.userDeviceKeys[client2.userID]!
          .startVerification(newDirectChatEnableEncryption: false);
      expect(req1.state, KeyVerificationState.askSSSS);
      await req1.openSSSS(recoveryKey: ssssKey);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
        ),
      );
      expect(req1.state, KeyVerificationState.waitingAccept);
      await req1.cancel();
      await client1.encryption!.keyVerificationManager.cleanup();
    });
    test('ask SSSS end', () async {
      FakeMatrixApi.calledEndpoints.clear();
      // make sure our master key is *not* verified to not triger SSSS for now
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(false);
      // the other one has to have their master key verified to trigger asking for ssss
      client2.userDeviceKeys[client2.userID]!.masterKey!
          .setDirectVerified(true);
      final req1 = await client1.userDeviceKeys[client2.userID]!
          .startVerification(newDirectChatEnableEncryption: false);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
        ),
      );
      var evt = getLastSentEvent(req1);
      expect(req1.state, KeyVerificationState.waitingAccept);
      final comp = Completer();
      final sub = client2.onKeyVerificationRequest.stream.listen((req) {
        comp.complete(req);
      });
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      final req2 = await comp.future;
      await sub.cancel();
      // send ready
      FakeMatrixApi.calledEndpoints.clear();
      await req2.acceptVerification();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
        ),
      );
      evt = getLastSentEvent(req2);
      expect(req2.state, KeyVerificationState.askChoice);
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req1.possibleMethods, [EventTypes.Sas]);
      expect(req2.possibleMethods, [EventTypes.Sas]);
      expect(req1.state, KeyVerificationState.waitingAccept);
      // no need for start (continueVerification) because sas only mode override already sent it after ready
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start',
        ),
      );
      evt = getLastSentEvent(req1);
      // send accept
      FakeMatrixApi.calledEndpoints.clear();
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.accept',
        ),
      );
      evt = getLastSentEvent(req2);
      // send key
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
        ),
      );
      evt = getLastSentEvent(req1);
      // send key
      FakeMatrixApi.calledEndpoints.clear();
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
        ),
      );
      evt = getLastSentEvent(req2);
      // receive last key
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      // compare emoji
      expect(req1.state, KeyVerificationState.askSas);
      expect(req2.state, KeyVerificationState.askSas);
      // compare emoji
      final emoji1 = req1.sasEmojis;
      final emoji2 = req2.sasEmojis;
      for (var i = 0; i < 7; i++) {
        expect(emoji1[i].emoji, emoji2[i].emoji);
        expect(emoji1[i].name, emoji2[i].name);
      }
      // compare numbers
      final numbers1 = req1.sasNumbers;
      final numbers2 = req2.sasNumbers;
      for (var i = 0; i < 3; i++) {
        expect(numbers1[i], numbers2[i]);
      }
      // alright, they match
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(true);
      await client1.encryption!.ssss.clearCache();
      // send mac
      FakeMatrixApi.calledEndpoints.clear();
      await req1.acceptSas();
      evt = getLastSentEvent(req1);
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac',
        ),
      );
      expect(req1.state, KeyVerificationState.waitingSas);
      // send mac
      FakeMatrixApi.calledEndpoints.clear();
      await req2.acceptSas();
      evt = getLastSentEvent(req2);
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac',
        ),
      );
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done',
        ),
      );
      FakeMatrixApi.calledEndpoints.clear();
      expect(req1.state, KeyVerificationState.askSSSS);
      expect(req2.state, KeyVerificationState.done);
      await req1.openSSSS(recoveryKey: ssssKey);
      expect(req1.state, KeyVerificationState.done);
      // let any background box usage from ssss signing finish
      await Future.delayed(Duration(seconds: 1));
      await client1.encryption!.keyVerificationManager.cleanup();
      await client2.encryption!.keyVerificationManager.cleanup();
    });
    test('reject verification', () async {
      FakeMatrixApi.calledEndpoints.clear();
      // make sure our master key is *not* verified to not triger SSSS for now
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(false);
      final req1 = await client1.userDeviceKeys[client2.userID]!
          .startVerification(newDirectChatEnableEncryption: false);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
        ),
      );
      var evt = getLastSentEvent(req1);
      expect(req1.state, KeyVerificationState.waitingAccept);
      final comp = Completer();
      final sub = client2.onKeyVerificationRequest.stream.listen((req) {
        comp.complete(req);
      });
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      final req2 = await comp.future;
      await sub.cancel();
      FakeMatrixApi.calledEndpoints.clear();
      await req2.rejectVerification();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.cancel',
        ),
      );
      evt = getLastSentEvent(req2);
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req1.state, KeyVerificationState.error);
      expect(req2.state, KeyVerificationState.error);
      await client1.encryption!.keyVerificationManager.cleanup();
      await client2.encryption!.keyVerificationManager.cleanup();
    });
    test('reject sas', () async {
      FakeMatrixApi.calledEndpoints.clear();
      // make sure our master key is *not* verified to not triger SSSS for now
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(false);
      final req1 = await client1.userDeviceKeys[client2.userID]!
          .startVerification(newDirectChatEnableEncryption: false);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
        ),
      );
      var evt = getLastSentEvent(req1);
      expect(req1.state, KeyVerificationState.waitingAccept);
      final comp = Completer();
      final sub = client2.onKeyVerificationRequest.stream.listen((req) {
        comp.complete(req);
      });
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      final req2 = await comp.future;
      await sub.cancel();
      // send ready
      FakeMatrixApi.calledEndpoints.clear();
      await req2.acceptVerification();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
        ),
      );
      evt = getLastSentEvent(req2);
      expect(req2.state, KeyVerificationState.askChoice);
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req1.possibleMethods, [EventTypes.Sas]);
      expect(req2.possibleMethods, [EventTypes.Sas]);
      expect(req1.state, KeyVerificationState.waitingAccept);
      // no need for start (continueVerification) because sas only mode override already sent it after ready
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start',
        ),
      );
      evt = getLastSentEvent(req1);
      // send accept
      FakeMatrixApi.calledEndpoints.clear();
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.accept',
        ),
      );
      evt = getLastSentEvent(req2);
      // send key
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
        ),
      );
      evt = getLastSentEvent(req1);
      // send key
      FakeMatrixApi.calledEndpoints.clear();
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
        ),
      );
      evt = getLastSentEvent(req2);
      // receive last key
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      await req1.acceptSas();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac',
        ),
      );
      FakeMatrixApi.calledEndpoints.clear();
      await req2.rejectSas();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.cancel',
        ),
      );
      evt = getLastSentEvent(req2);
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req1.state, KeyVerificationState.error);
      expect(req2.state, KeyVerificationState.error);
      await client1.encryption!.keyVerificationManager.cleanup();
      await client2.encryption!.keyVerificationManager.cleanup();
    });
    test('other device accepted', () async {
      FakeMatrixApi.calledEndpoints.clear();
      // make sure our master key is *not* verified to not triger SSSS for now
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(false);
      final req1 = await client1.userDeviceKeys[client2.userID]!
          .startVerification(newDirectChatEnableEncryption: false);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
        ),
      );
      final evt = getLastSentEvent(req1);
      expect(req1.state, KeyVerificationState.waitingAccept);
      final comp = Completer();
      final sub = client2.onKeyVerificationRequest.stream.listen((req) {
        comp.complete(req);
      });
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      final req2 = await comp.future;
      await sub.cancel();
      await client2.encryption!.keyVerificationManager.handleEventUpdate(
        EventUpdate(
          content: {
            'event_id': req2.transactionId,
            'type': EventTypes.KeyVerificationReady,
            'content': {
              'methods': [EventTypes.Sas],
              'from_device': 'SOMEOTHERDEVICE',
              'm.relates_to': {
                'rel_type': 'm.reference',
                'event_id': req2.transactionId,
              },
            },
            'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
            'sender': client2.userID,
          },
          type: EventUpdateType.timeline,
          roomID: req2.room!.id,
        ),
      );
      expect(req2.state, KeyVerificationState.error);
      await req2.cancel();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.cancel',
        ),
      );
      await client1.encryption!.keyVerificationManager.cleanup();
      await client2.encryption!.keyVerificationManager.cleanup();
    });
    test('Run qr verification mode 0, ssss start', () async {
      expect(
        client1.userDeviceKeys[client2.userID]?.masterKey!.directVerified,
        false,
      );
      expect(
        client2.userDeviceKeys[client1.userID]?.masterKey!.directVerified,
        false,
      );
      // for a full run we test in-room verification in a cleartext room
      // because then we can easily intercept the payloads and inject in the other client
      FakeMatrixApi.calledEndpoints.clear();
      // make sure our master key is *not* verified to not triger SSSS for now
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(true);
      client2.userDeviceKeys[client2.userID]!.masterKey!
          .setDirectVerified(true);
      await client1.encryption!.ssss.clearCache();
      final req1 =
          await client1.userDeviceKeys[client2.userID]!.startVerification(
        newDirectChatEnableEncryption: false,
      );
      expect(req1.state, KeyVerificationState.askSSSS);
      await req1.openSSSS(recoveryKey: ssssKey);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
        ),
      );
      var evt = getLastSentEvent(req1);
      expect(req1.state, KeyVerificationState.waitingAccept);
      final comp = Completer();
      final sub = client2.onKeyVerificationRequest.stream.listen((req) {
        comp.complete(req);
      });
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      final req2 = await comp.future;
      await sub.cancel();
      expect(
        client2.encryption!.keyVerificationManager
            .getRequest(req2.transactionId!),
        req2,
      );
      // send ready
      FakeMatrixApi.calledEndpoints.clear();
      await req2.acceptVerification();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
        ),
      );
      evt = getLastSentEvent(req2);
      expect(req2.state, KeyVerificationState.askChoice);
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req1.state, KeyVerificationState.askChoice);
      expect(
        req1.possibleMethods,
        [EventTypes.Sas, EventTypes.Reciprocate, EventTypes.QRScan],
      );
      expect(
        req2.possibleMethods,
        [EventTypes.Sas, EventTypes.Reciprocate, EventTypes.QRShow],
      );
      // send start
      FakeMatrixApi.calledEndpoints.clear();
      await req1.continueVerification(
        EventTypes.Reciprocate,
        qrDataRawBytes: Uint8List.fromList(req2.qrCode?.qrDataRawBytes ?? []),
      );
      expect(req2.qrCode!.randomSharedSecret, req1.randomSharedSecretForQRCode);
      expect(req1.state, KeyVerificationState.showQRSuccess);
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start',
        ),
      );
      evt = getLastSentEvent(req1);
      // send done
      FakeMatrixApi.calledEndpoints.clear();
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req2.state, KeyVerificationState.confirmQRScan);
      await req2.acceptQRScanConfirmation();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done',
        ),
      );
      evt = getLastSentEvent(req2);
      FakeMatrixApi.calledEndpoints.clear();
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req1.state, KeyVerificationState.done);
      expect(req2.state, KeyVerificationState.done);
      expect(
        client1.userDeviceKeys[client2.userID]?.masterKey!.directVerified,
        true,
      );
      expect(
        client2.userDeviceKeys[client1.userID]?.masterKey!.directVerified,
        true,
      );
      await client1.encryption!.keyVerificationManager.cleanup();
      await client2.encryption!.keyVerificationManager.cleanup();
    });
    test('Run qr verification mode 0, but fail on masterKey unverified client1',
        () async {
      // for a full run we test in-room verification in a cleartext room
      // because then we can easily intercept the payloads and inject in the other client
      FakeMatrixApi.calledEndpoints.clear();
      // make sure our master key is *not* verified to not triger SSSS for now
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(false);
      final req1 =
          await client1.userDeviceKeys[client2.userID]!.startVerification(
        newDirectChatEnableEncryption: false,
      );
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
        ),
      );
      var evt = getLastSentEvent(req1);
      expect(req1.state, KeyVerificationState.waitingAccept);
      final comp = Completer();
      final sub = client2.onKeyVerificationRequest.stream.listen((req) {
        comp.complete(req);
      });
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      final req2 = await comp.future;
      await sub.cancel();
      expect(
        client2.encryption!.keyVerificationManager
            .getRequest(req2.transactionId!),
        req2,
      );
      // send ready
      FakeMatrixApi.calledEndpoints.clear();
      await req2.acceptVerification();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
        ),
      );
      evt = getLastSentEvent(req2);
      expect(req2.state, KeyVerificationState.askChoice);
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req1.possibleMethods, [EventTypes.Sas]);
      expect(req2.possibleMethods, [EventTypes.Sas]);
      expect(req1.state, KeyVerificationState.waitingAccept);
      // send start
      FakeMatrixApi.calledEndpoints.clear();
      // qrCode will be null here anyway because masterKey not signed
      await req1.continueVerification(
        EventTypes.Reciprocate,
        qrDataRawBytes: Uint8List.fromList(req2.qrCode?.qrDataRawBytes ?? []),
      );
      expect(req1.state, KeyVerificationState.error);
      await client1.encryption!.keyVerificationManager.cleanup();
      await client2.encryption!.keyVerificationManager.cleanup();
    });
    test('Run qr verification mode 0, but fail on masterKey unverified client2',
        () async {
      // for a full run we test in-room verification in a cleartext room
      // because then we can easily intercept the payloads and inject in the other client
      FakeMatrixApi.calledEndpoints.clear();
      // make sure our master key is *not* verified to not triger SSSS for now
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(false);
      final req1 =
          await client1.userDeviceKeys[client2.userID]!.startVerification(
        newDirectChatEnableEncryption: false,
      );
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
        ),
      );
      var evt = getLastSentEvent(req1);
      expect(req1.state, KeyVerificationState.waitingAccept);
      final comp = Completer();
      final sub = client2.onKeyVerificationRequest.stream.listen((req) {
        comp.complete(req);
      });
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      final req2 = await comp.future;
      await sub.cancel();
      expect(
        client2.encryption!.keyVerificationManager
            .getRequest(req2.transactionId!),
        req2,
      );
      // send ready
      FakeMatrixApi.calledEndpoints.clear();
      await req2.acceptVerification();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
        ),
      );
      evt = getLastSentEvent(req2);
      expect(req2.state, KeyVerificationState.askChoice);
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req1.possibleMethods, [EventTypes.Sas]);
      expect(req2.possibleMethods, [EventTypes.Sas]);
      expect(req1.state, KeyVerificationState.waitingAccept);
      FakeMatrixApi.calledEndpoints.clear();
      // qrCode will be null here anyway because masterKey not signed
      await req2.continueVerification(
        EventTypes.Reciprocate,
        qrDataRawBytes: Uint8List.fromList(req2.qrCode?.qrDataRawBytes ?? []),
      );
      expect(req2.state, KeyVerificationState.error);
      await client1.encryption!.keyVerificationManager.cleanup();
      await client2.encryption!.keyVerificationManager.cleanup();
    });
    test(
        'Run qr verification mode, but fail because no knownVerificationMethod',
        () async {
      client1.verificationMethods = {
        KeyVerificationMethod.emoji,
        KeyVerificationMethod.numbers,
      };
      client2.verificationMethods = {
        KeyVerificationMethod.emoji,
        KeyVerificationMethod.numbers,
      };
      // for a full run we test in-room verification in a cleartext room
      // because then we can easily intercept the payloads and inject in the other client
      FakeMatrixApi.calledEndpoints.clear();
      // make sure our master key is *not* verified to not triger SSSS for now
      client1.userDeviceKeys[client1.userID]!.masterKey!
          .setDirectVerified(false);
      final req1 =
          await client1.userDeviceKeys[client2.userID]!.startVerification(
        newDirectChatEnableEncryption: false,
      );
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
        ),
      );
      var evt = getLastSentEvent(req1);
      expect(req1.state, KeyVerificationState.waitingAccept);
      final comp = Completer();
      final sub = client2.onKeyVerificationRequest.stream.listen((req) {
        comp.complete(req);
      });
      await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
      final req2 = await comp.future;
      await sub.cancel();
      expect(
        client2.encryption!.keyVerificationManager
            .getRequest(req2.transactionId!),
        req2,
      );
      // send ready
      FakeMatrixApi.calledEndpoints.clear();
      await req2.acceptVerification();
      await FakeMatrixApi.firstWhere(
        (e) => e.startsWith(
          '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
        ),
      );
      evt = getLastSentEvent(req2);
      expect(req2.state, KeyVerificationState.askChoice);
      await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
      expect(req1.possibleMethods, [EventTypes.Sas]);
      expect(req2.possibleMethods, [EventTypes.Sas]);
      expect(req1.state, KeyVerificationState.waitingAccept);
      FakeMatrixApi.calledEndpoints.clear();
      // qrCode will be null here anyway because qr isn't supported
      await req1.continueVerification(
        EventTypes.Reciprocate,
        qrDataRawBytes: Uint8List.fromList(req2.qrCode?.qrDataRawBytes ?? []),
      );
      expect(req1.state, KeyVerificationState.error);
      await client1.encryption!.keyVerificationManager.cleanup();
      await client2.encryption!.keyVerificationManager.cleanup();
    });
  });
}