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

import 'package:pointycastle/export.dart';

import '/utils/encryption/aes_helper.dart';
import '/utils/encryption/crypto_utils.dart';
import '/utils/storage/database.dart';

Friend findFriendByFriendId(List<Friend> friends, String id) {
  for (var friend in friends) {
    if (friend.friendId == id) {
      return friend;
    }
  }
  // Or return `null`.
  throw ArgumentError.value(id, 'id', 'No element with that id');
}

Future<Friend> getFriendByFriendId(String userId) async {
  final db = await getDatabaseConnection();

  final List<Map<String, dynamic>> maps = await db.query(
      'friends',
      where: 'friend_id = ?',
      whereArgs: [userId],
  );

  if (maps.length != 1) {
    throw ArgumentError('Invalid user id');
  }

  return Friend(
    id: maps[0]['id'],
    userId: maps[0]['user_id'],
    friendId: maps[0]['friend_id'],
    friendSymmetricKey: maps[0]['symmetric_key'],
    publicKey: CryptoUtils.rsaPublicKeyFromPem(maps[0]['asymmetric_public_key']),
    acceptedAt: maps[0]['accepted_at'] != null ? DateTime.parse(maps[0]['accepted_at']) : null,
    username: maps[0]['username'],
  );
}


Future<List<Friend>> getFriends({bool? accepted}) async {
  final db = await getDatabaseConnection();

  String? where;

  if (accepted == true) {
    where = 'accepted_at IS NOT NULL';
  }

  if (accepted == false) {
    where = 'accepted_at IS NULL';
  }

  final List<Map<String, dynamic>> maps = await db.query(
    'friends',
    where: where,
  );

  return List.generate(maps.length, (i) {
    return Friend(
        id: maps[i]['id'],
        userId: maps[i]['user_id'],
        friendId: maps[i]['friend_id'],
        friendSymmetricKey: maps[i]['symmetric_key'],
        publicKey: CryptoUtils.rsaPublicKeyFromPem(maps[i]['asymmetric_public_key']),
        acceptedAt: maps[i]['accepted_at'] != null ? DateTime.parse(maps[i]['accepted_at']) : null,
        username: maps[i]['username'],
    );
  });
}

class Friend{
  String id;
  String userId;
  String username;
  String friendId;
  String friendSymmetricKey;
  RSAPublicKey publicKey;
  DateTime? acceptedAt;
  bool? selected;
  Friend({
    required this.id,
    required this.userId,
    required this.username,
    required this.friendId,
    required this.friendSymmetricKey,
    required this.publicKey,
    required this.acceptedAt,
    this.selected,
  });

  factory Friend.fromJson(Map<String, dynamic> json, RSAPrivateKey privKey) {
    Uint8List idDecrypted = CryptoUtils.rsaDecrypt(
        base64.decode(json['friend_id']),
        privKey,
    );

    Uint8List username = CryptoUtils.rsaDecrypt(
        base64.decode(json['friend_username']),
        privKey,
    );

    Uint8List symmetricKeyDecrypted = CryptoUtils.rsaDecrypt(
        base64.decode(json['symmetric_key']),
        privKey,
    );

    String publicKeyString = AesHelper.aesDecrypt(
      symmetricKeyDecrypted,
      base64.decode(json['asymmetric_public_key'])
    );

    RSAPublicKey publicKey = CryptoUtils.rsaPublicKeyFromPem(publicKeyString);

    return Friend(
        id: json['id'],
        userId: json['user_id'],
        username: String.fromCharCodes(username),
        friendId: String.fromCharCodes(idDecrypted),
        friendSymmetricKey: base64.encode(symmetricKeyDecrypted),
        publicKey: publicKey,
        acceptedAt: json['accepted_at']['Valid'] ?
          DateTime.parse(json['accepted_at']['Time']) :
          null,
    );
  }

  Map<String, dynamic> payloadJson() {

    Uint8List friendIdEncrypted = CryptoUtils.rsaEncrypt(
      Uint8List.fromList(friendId.codeUnits),
      publicKey,
    );

    Uint8List usernameEncrypted = CryptoUtils.rsaEncrypt(
      Uint8List.fromList(username.codeUnits),
      publicKey,
    );

    Uint8List symmetricKeyEncrypted = CryptoUtils.rsaEncrypt(
      Uint8List.fromList(
        base64.decode(friendSymmetricKey),
      ),
      publicKey,
    );

    var publicKeyEncrypted = AesHelper.aesEncrypt(
      base64.decode(friendSymmetricKey),
      Uint8List.fromList(CryptoUtils.encodeRSAPublicKeyToPem(publicKey).codeUnits),
    );

    return {
      'id': id,
      'user_id': userId,
      'friend_id': base64.encode(friendIdEncrypted),
      'friend_username': base64.encode(usernameEncrypted),
      'symmetric_key': base64.encode(symmetricKeyEncrypted),
      'asymmetric_public_key': publicKeyEncrypted,
      'accepted_at': null,
    };
  }

  String publicKeyPem() {
    return CryptoUtils.encodeRSAPublicKeyToPem(publicKey);
  }

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'user_id': userId,
      'username': username,
      'friend_id': friendId,
      'symmetric_key': base64.encode(friendSymmetricKey.codeUnits),
      'asymmetric_public_key': publicKeyPem(),
      'accepted_at': acceptedAt?.toIso8601String(),
    };
  }

  @override
  String toString() {
    return '''


        id: $id
        userId: $userId
        username: $username
        friendId: $friendId
        accepted_at: $acceptedAt''';
  }
}
