/*
 *   Famedly Matrix SDK
 *   Copyright (C) 2019, 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/matrix.dart';
import 'fake_database.dart';
void main() {
  group('Matrix SDK Database Test', () {
    late DatabaseApi database;
    late int toDeviceQueueIndex;
    test('Setup', () async {
      database = await getMatrixSdkDatabase(null);
    });
    test('transaction', () async {
      var counter = 0;
      await database.transaction(() async {
        expect(counter++, 0);
        await database.transaction(() async {
          expect(counter++, 1);
          await Future.delayed(Duration(milliseconds: 50));
          expect(counter++, 2);
        });
        expect(counter++, 3);
      });
    });
    test('insertIntoToDeviceQueue', () async {
      toDeviceQueueIndex = await database.insertIntoToDeviceQueue(
        'm.test',
        'txnId',
        '{"foo":"bar"}',
      );
    });
    test('getToDeviceEventQueue', () async {
      final toDeviceQueue = await database.getToDeviceEventQueue();
      expect(toDeviceQueue.first.type, 'm.test');
    });
    test('deleteFromToDeviceQueue', () async {
      await database.deleteFromToDeviceQueue(toDeviceQueueIndex);
      final toDeviceQueue = await database.getToDeviceEventQueue();
      expect(toDeviceQueue.isEmpty, true);
    });
    test('storeFile', () async {
      await database.storeFile(
          Uri.parse('mxc://test'), Uint8List.fromList([0]), 0);
      final file = await database.getFile(Uri.parse('mxc://test'));
      expect(file != null, database.supportsFileStoring);
    });
    test('getFile', () async {
      await database.getFile(Uri.parse('mxc://test'));
    });
    test('deleteOldFiles', () async {
      await database.deleteOldFiles(1);
      final file = await database.getFile(Uri.parse('mxc://test'));
      expect(file == null, true);
    });
    test('storeRoomUpdate', () async {
      final roomUpdate = JoinedRoomUpdate.fromJson({
        'highlight_count': 0,
        'notification_count': 0,
        'limited_timeline': false,
        'membership': Membership.join,
      });
      final client = Client('testclient');
      await database.storeRoomUpdate('!testroom', roomUpdate, client);
      final rooms = await database.getRoomList(client);
      expect(rooms.single.id, '!testroom');
    });
    test('getRoomList', () async {
      final room =
          await database.getSingleRoom(Client('testclient'), '!testroom');
      expect(room?.id, '!testroom');
    });
    test('getRoomList', () async {
      final list = await database.getRoomList(Client('testclient'));
      expect(list.single.id, '!testroom');
    });
    test('setRoomPrevBatch', () async {
      final client = Client('testclient');
      await database.setRoomPrevBatch('1234', '!testroom', client);
      final rooms = await database.getRoomList(client);
      expect(rooms.single.prev_batch, '1234');
    });
    test('forgetRoom', () async {
      await database.forgetRoom('!testroom');
      final rooms = await database.getRoomList(Client('testclient'));
      expect(rooms.isEmpty, true);
    });
    test('getClient', () async {
      await database.getClient('name');
    });
    test('insertClient', () async {
      await database.insertClient(
        'name',
        'homeserverUrl',
        'token',
        'userId',
        'deviceId',
        'deviceName',
        'prevBatch',
        'olmAccount',
      );
      final client = await database.getClient('name');
      expect(client?['token'], 'token');
    });
    test('updateClient', () async {
      await database.updateClient(
        'homeserverUrl',
        'token_different',
        'userId',
        'deviceId',
        'deviceName',
        'prevBatch',
        'olmAccount',
      );
      final client = await database.getClient('name');
      expect(client?['token'], 'token_different');
    });
    test('updateClientKeys', () async {
      await database.updateClientKeys(
        'olmAccount2',
      );
      final client = await database.getClient('name');
      expect(client?['olm_account'], 'olmAccount2');
    });
    test('storeSyncFilterId', () async {
      await database.storeSyncFilterId(
        '1234',
      );
      final client = await database.getClient('name');
      expect(client?['sync_filter_id'], '1234');
    });
    test('getAccountData', () async {
      await database.getAccountData();
    });
    test('storeAccountData', () async {
      await database.storeAccountData('m.test', '{"foo":"bar"}');
      final events = await database.getAccountData();
      expect(events.values.single.type, 'm.test');
      await database.storeAccountData('m.abc+de', '{"foo":"bar"}');
      final events2 = await database.getAccountData();
      expect(events2.values.any((element) => element.type == 'm.abc+de'), true);
    });
    test('storeEventUpdate', () async {
      await database.storeEventUpdate(
          EventUpdate(
            roomID: '!testroom:example.com',
            type: EventUpdateType.timeline,
            content: {
              'type': EventTypes.Message,
              'content': {
                'body': '* edit 3',
                'msgtype': 'm.text',
                'm.new_content': {
                  'body': 'edit 3',
                  'msgtype': 'm.text',
                },
                'm.relates_to': {
                  'event_id': '\$source',
                  'rel_type': RelationshipTypes.edit,
                },
              },
              'event_id': '\$event:example.com',
              'sender': '@bob:example.org',
            },
          ),
          Client('testclient'));
    });
    test('getEventById', () async {
      final event = await database.getEventById('\$event:example.com',
          Room(id: '!testroom:example.com', client: Client('testclient')));
      expect(event?.type, EventTypes.Message);
    });
    test('getEventList', () async {
      final events = await database.getEventList(
        Room(id: '!testroom:example.com', client: Client('testclient')),
      );
      expect(events.single.type, EventTypes.Message);
    });
    test('getUser', () async {
      final user = await database.getUser('@bob:example.org',
          Room(id: '!testroom:example.com', client: Client('testclient')));
      expect(user, null);
    });
    test('getUsers', () async {
      final users = await database.getUsers(
          Room(id: '!testroom:example.com', client: Client('testclient')));
      expect(users.isEmpty, true);
    });
    test('removeEvent', () async {
      await database.removeEvent(
          '\$event:example.com', '!testroom:example.com');
      final event = await database.getEventById('\$event:example.com',
          Room(id: '!testroom:example.com', client: Client('testclient')));
      expect(event, null);
    });
    test('getAllInboundGroupSessions', () async {
      final result = await database.getAllInboundGroupSessions();
      expect(result.isEmpty, true);
    });
    test('getInboundGroupSession', () async {
      await database.getInboundGroupSession(
          '!testroom:example.com', 'sessionId');
    });
    test('getInboundGroupSessionsToUpload', () async {
      await database.getInboundGroupSessionsToUpload();
    });
    test('storeInboundGroupSession', () async {
      await database.storeInboundGroupSession(
        '!testroom:example.com',
        'sessionId',
        'pickle',
        '{"foo":"bar"}',
        '{}',
        '{}',
        'senderKey',
        '{}',
      );
      final session = await database.getInboundGroupSession(
        '!testroom:example.com',
        'sessionId',
      );
      expect(jsonDecode(session!.content)['foo'], 'bar');
    });
    test('markInboundGroupSessionAsUploaded', () async {
      await database.markInboundGroupSessionAsUploaded(
          '!testroom:example.com', 'sessionId');
    });
    test('markInboundGroupSessionsAsNeedingUpload', () async {
      await database.markInboundGroupSessionsAsNeedingUpload();
    });
    test('updateInboundGroupSessionAllowedAtIndex', () async {
      await database.updateInboundGroupSessionAllowedAtIndex(
        '{}',
        '!testroom:example.com',
        'sessionId',
      );
    });
    test('updateInboundGroupSessionIndexes', () async {
      await database.updateInboundGroupSessionIndexes(
        '{}',
        '!testroom:example.com',
        'sessionId',
      );
    });
    test('getSSSSCache', () async {
      final cache = await database.getSSSSCache('type');
      expect(cache, null);
    });
    test('storeSSSSCache', () async {
      await database.storeSSSSCache('type', 'keyId', 'ciphertext', '{}');
      final cache = (await database.getSSSSCache('type'))!;
      expect(cache.type, 'type');
      expect(cache.keyId, 'keyId');
      expect(cache.ciphertext, 'ciphertext');
      expect(cache.content, '{}');
    });
    test('getOlmSessions', () async {
      final olm = await database.getOlmSessions(
        'identityKey',
        'userId',
      );
      expect(olm.isEmpty, true);
    });
    test('getAllOlmSessions', () async {
      var sessions = await database.getAllOlmSessions();
      expect(sessions.isEmpty, true);
      await database.storeOlmSession(
        'identityKey',
        'sessionId',
        'pickle',
        0,
      );
      await database.storeOlmSession(
        'identityKey',
        'sessionId2',
        'pickle',
        0,
      );
      sessions = await database.getAllOlmSessions();
      expect(
        sessions,
        {
          'identityKey': {
            'sessionId': {
              'identity_key': 'identityKey',
              'pickle': 'pickle',
              'session_id': 'sessionId',
              'last_received': 0
            },
            'sessionId2': {
              'identity_key': 'identityKey',
              'pickle': 'pickle',
              'session_id': 'sessionId2',
              'last_received': 0
            }
          }
        },
      );
    });
    test('getOlmSessionsForDevices', () async {
      final olm = await database.getOlmSessionsForDevices(
        ['identityKeys'],
        'userId',
      );
      expect(olm.isEmpty, true);
    });
    test('storeOlmSession', () async {
      if (!(await olmEnabled())) return;
      await database.storeOlmSession(
        'identityKey',
        'sessionId',
        'pickle',
        0,
      );
      final olm = await database.getOlmSessions(
        'identityKey',
        'userId',
      );
      expect(olm.isNotEmpty, true);
    });
    test('getOutboundGroupSession', () async {
      final session = await database.getOutboundGroupSession(
        '!testroom:example.com',
        '@alice:example.com',
      );
      expect(session, null);
    });
    test('storeOutboundGroupSession', () async {
      if (!(await olmEnabled())) return;
      await database.storeOutboundGroupSession(
        '!testroom:example.com',
        'pickle',
        '{}',
        0,
      );
      final session = await database.getOutboundGroupSession(
        '!testroom:example.com',
        '@alice:example.com',
      );
      expect(session?.devices.isEmpty, true);
    });
    test('getLastSentMessageUserDeviceKey', () async {
      final list = await database.getLastSentMessageUserDeviceKey(
        'userId',
        'deviceId',
      );
      expect(list.isEmpty, true);
    });
    test('getUnimportantRoomEventStatesForRoom', () async {
      final events = await database.getUnimportantRoomEventStatesForRoom(
        ['events'],
        Room(id: '!mep', client: Client('testclient')),
      );
      expect(events.isEmpty, true);
    });
    test('getUserDeviceKeys', () async {
      await database.getUserDeviceKeys(Client('testclient'));
    });
    test('storeUserCrossSigningKey', () async {
      await database.storeUserCrossSigningKey(
        '@alice:example.com',
        'publicKey',
        '{}',
        false,
        false,
      );
    });
    test('setVerifiedUserCrossSigningKey', () async {
      await database.setVerifiedUserCrossSigningKey(
        true,
        '@alice:example.com',
        'publicKey',
      );
    });
    test('setBlockedUserCrossSigningKey', () async {
      await database.setBlockedUserCrossSigningKey(
        true,
        '@alice:example.com',
        'publicKey',
      );
    });
    test('removeUserCrossSigningKey', () async {
      await database.removeUserCrossSigningKey(
        '@alice:example.com',
        'publicKey',
      );
    });
    test('storeUserDeviceKeysInfo', () async {
      await database.storeUserDeviceKeysInfo(
        '@alice:example.com',
        true,
      );
    });
    test('storeUserDeviceKey', () async {
      await database.storeUserDeviceKey(
        '@alice:example.com',
        'deviceId',
        '{}',
        false,
        false,
        0,
      );
    });
    test('setVerifiedUserDeviceKey', () async {
      await database.setVerifiedUserDeviceKey(
        true,
        '@alice:example.com',
        'deviceId',
      );
    });
    test('setBlockedUserDeviceKey', () async {
      await database.setBlockedUserDeviceKey(
        true,
        '@alice:example.com',
        'deviceId',
      );
    });
    test('getStorePresences', () async {
      const userId = '@alice:example.com';
      final presence = CachedPresence(
        PresenceType.online,
        100,
        'test message',
        true,
        '@alice:example.com',
      );
      await database.storePresence(
        userId,
        presence,
      );
      final storedPresence = await database.getPresence(userId);
      expect(
        presence.toJson(),
        storedPresence?.toJson(),
      );
    });
    // Clearing up from here
    test('clearSSSSCache', () async {
      await database.clearSSSSCache();
    });
    test('clearCache', () async {
      await database.clearCache();
    });
    test('clear', () async {
      await database.clear();
    });
    test('Close', () async {
      await database.close();
    });
  });
}
Future olmEnabled() async {
  var olmEnabled = true;
  try {
    await olm.init();
    olm.get_library_version();
  } catch (e) {
    olmEnabled = false;
  }
  return olmEnabled;
}