import 'dart:convert';
import 'dart:typed_data';

import 'package:crypton/crypton.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:marianum_mobile/push/push_decryptor.dart';
import 'package:pointycastle/export.dart' as pc;

/// Encrypts [plain] with [publicKey] using OAEP (SHA-1), mirroring Nextcloud's
/// default padding.
String _encryptOaep(RSAPublicKey publicKey, String plain) {
  final cipher = pc.OAEPEncoding(pc.RSAEngine())
    ..init(
      true,
      pc.PublicKeyParameter<pc.RSAPublicKey>(publicKey.asPointyCastle),
    );
  final out = cipher.process(Uint8List.fromList(utf8.encode(plain)));
  return base64.encode(out);
}

/// Signs the encrypted bytes with the server private key (SHA512withRSA).
String _sign(RSAPrivateKey serverPrivate, String subjectBase64) {
  final signature = serverPrivate.createSHA512Signature(
    Uint8List.fromList(base64.decode(subjectBase64)),
  );
  return base64.encode(signature);
}

void main() {
  final device = RSAKeypair.fromRandom();
  final server = RSAKeypair.fromRandom();

  const subjectJson =
      '{"app":"spreed","subject":"Max: Hallo","type":"chat","id":"abc123","nid":42}';

  group('PushDecryptor', () {
    test('decrypts an OAEP-encrypted subject', () {
      final encrypted = _encryptOaep(device.publicKey, subjectJson);
      final decryptor = PushDecryptor(
        devicePrivateKey: device.privateKey,
        serverPublicKey: server.publicKey,
      );

      expect(
        decryptor.verify(encrypted, _sign(server.privateKey, encrypted)),
        isTrue,
      );

      final subject = decryptor.decrypt(encrypted);
      expect(subject, isNotNull);
      expect(subject!.app, 'spreed');
      expect(subject.isTalk, isTrue);
      expect(subject.id, 'abc123');
      expect(subject.nid, 42);
    });

    test('decrypts a PKCS1-encrypted subject (fallback)', () {
      // crypton's encrypt uses PKCS#1 v1.5.
      final encrypted = device.publicKey.encrypt(subjectJson);
      final subject = PushDecryptor(
        devicePrivateKey: device.privateKey,
      ).decrypt(encrypted);
      expect(subject, isNotNull);
      expect(subject!.subject, 'Max: Hallo');
    });

    test('rejects a signature made with the wrong key', () {
      final encrypted = _encryptOaep(device.publicKey, subjectJson);
      final wrong = RSAKeypair.fromRandom();
      final decryptor = PushDecryptor(
        devicePrivateKey: device.privateKey,
        serverPublicKey: server.publicKey,
      );
      expect(
        decryptor.verify(encrypted, _sign(wrong.privateKey, encrypted)),
        isFalse,
      );
    });

    test('parses delete-multiple subjects', () {
      const deleteJson = '{"delete-multiple":true,"nids":[1,2,3]}';
      final encrypted = _encryptOaep(device.publicKey, deleteJson);
      final subject = PushDecryptor(
        devicePrivateKey: device.privateKey,
      ).decrypt(encrypted);
      expect(subject, isNotNull);
      expect(subject!.deleteMultiple, isTrue);
      expect(subject.isAnyDelete, isTrue);
      expect(subject.nids, [1, 2, 3]);
    });
  });
}
