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

import 'package:crypton/crypton.dart';
import 'package:pointycastle/export.dart' as pc;

import 'push_subject.dart';

/// Verifies and decrypts the encrypted `subject` of a Nextcloud push-v2
/// notification. Pure crypto, no plugin/platform access, so it runs safely
/// inside the FCM background isolate.
///
/// - Signature: `SHA512withRSA` over the *encrypted* subject bytes, verified
///   with the per-user server public key returned at registration.
/// - Encryption: the subject is encrypted with the device public key, so the
///   device decrypts with its private key. Nextcloud 32 defaults to
///   OAEP (SHA-1/MGF1-SHA-1); older instances use PKCS#1 v1.5. We try OAEP
///   first and fall back to PKCS#1.
class PushDecryptor {
  final RSAPrivateKey devicePrivateKey;

  /// Per-user server public key (the `publicKey` from the NC registration
  /// response). When null, signature verification is skipped.
  final RSAPublicKey? serverPublicKey;

  const PushDecryptor({required this.devicePrivateKey, this.serverPublicKey});

  /// Returns true when [signatureBase64] is a valid server signature over the
  /// encrypted subject. Returns true when no server key is configured (the
  /// proxy already verified the signature before forwarding).
  bool verify(String subjectBase64, String signatureBase64) {
    final key = serverPublicKey;
    if (key == null) return true;
    try {
      final signed = Uint8List.fromList(base64.decode(subjectBase64));
      final signature = Uint8List.fromList(base64.decode(signatureBase64));
      return key.verifySHA512Signature(signed, signature);
    } on Object {
      return false;
    }
  }

  /// Decrypts the base64 subject into a [PushSubject], or returns null when
  /// neither padding scheme yields valid JSON.
  PushSubject? decrypt(String subjectBase64) {
    final encrypted = Uint8List.fromList(base64.decode(subjectBase64));
    final plain = _decryptOaep(encrypted) ?? _decryptPkcs1(encrypted);
    if (plain == null) return null;
    try {
      final json = jsonDecode(plain) as Map<String, dynamic>;
      return PushSubject.fromJson(json);
    } on Object {
      return null;
    }
  }

  String? _decryptOaep(Uint8List data) =>
      _tryDecrypt(pc.OAEPEncoding(pc.RSAEngine()), data);

  String? _decryptPkcs1(Uint8List data) =>
      _tryDecrypt(pc.PKCS1Encoding(pc.RSAEngine()), data);

  String? _tryDecrypt(pc.AsymmetricBlockCipher cipher, Uint8List data) {
    try {
      cipher.init(
        false,
        pc.PrivateKeyParameter<pc.RSAPrivateKey>(
          devicePrivateKey.asPointyCastle,
        ),
      );
      final out = cipher.process(data);
      return utf8.decode(out);
    } on Object {
      return null;
    }
  }
}
