import 'dart:typed_data';

import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:mutex/mutex.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/signal/consts.signal.dart';
import 'package:twonly/src/services/signal/prekeys.signal.dart';
import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/utils/log.dart';

/// This caused some troubles, so protection the encryption...
final lockingSignalEncryption = Mutex();

Future<CiphertextMessage?> signalEncryptMessage(
  int target,
  Uint8List plaintextContent,
) async {
  return lockingSignalEncryption.protect<CiphertextMessage?>(() async {
    try {
      final signalStore = (await getSignalStore())!;
      final address = SignalProtocolAddress(target.toString(), defaultDeviceId);

      final session = SessionCipher.fromStore(signalStore, address);

      final preKey = await getPreKeyByContactId(target);
      final signedPreKey = await getSignedPreKeyByContactId(target);

      if (signedPreKey != null) {
        final sessionBuilder = SessionBuilder.fromSignalStore(
          signalStore,
          address,
        );

        ECPublicKey? tempPrePublicKey;

        if (preKey != null) {
          tempPrePublicKey = Curve.decodePoint(
            DjbECPublicKey(
              Uint8List.fromList(preKey.preKey),
            ).serialize(),
            1,
          );
        }

        final tempSignedPreKeyPublic = Curve.decodePoint(
          DjbECPublicKey(Uint8List.fromList(signedPreKey.signedPreKey))
              .serialize(),
          1,
        );

        final tempSignedPreKeySignature = Uint8List.fromList(
          signedPreKey.signedPreKeySignature,
        );

        final tempIdentityKey = await signalStore.getIdentity(address);
        if (tempIdentityKey != null) {
          final preKeyBundle = PreKeyBundle(
            target,
            defaultDeviceId,
            preKey?.preKeyId,
            tempPrePublicKey,
            signedPreKey.signedPreKeyId,
            tempSignedPreKeyPublic,
            tempSignedPreKeySignature,
            tempIdentityKey,
          );

          try {
            await sessionBuilder.processPreKeyBundle(preKeyBundle);
          } catch (e) {
            Log.error('could not process pre key bundle: $e');
          }
        } else {
          Log.error('did not get the identity of the remote address');
        }
      }
      return await session.encrypt(plaintextContent);
    } catch (e) {
      Log.error(e.toString());
      return null;
    }
  });
}

Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
    signalDecryptMessage(
  int source,
  Uint8List encryptedContentRaw,
  int type,
) async {
  try {
    final session = SessionCipher.fromStore(
      (await getSignalStore())!,
      SignalProtocolAddress(source.toString(), defaultDeviceId),
    );

    Uint8List plaintext;

    switch (type) {
      case CiphertextMessage.prekeyType:
        plaintext = await session.decrypt(
          PreKeySignalMessage(encryptedContentRaw),
        );
      case CiphertextMessage.whisperType:
        plaintext = await session.decryptFromSignal(
          SignalMessage.fromSerialized(encryptedContentRaw),
        );
      default:
        Log.error('Unknown Message Decryption Type: $type');
        return (null, PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN);
    }

    return (EncryptedContent.fromBuffer(plaintext), null);
  } on InvalidKeyIdException catch (e) {
    Log.warn(e);
    return (null, PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN);
  } catch (e) {
    Log.error(e);
    return (null, PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN);
  }
}
