import 'dart:async';
import 'dart:convert';

import 'package:drift/drift.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/tables/groups.table.dart';
import 'package:twonly/src/database/twonly.db.dart' hide Message;
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/api/utils.api.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/utils/avatars.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';

Future<bool> handleNewContactRequest(int fromUserId) async {
  final contact = await twonlyDB.contactsDao
      .getContactByUserId(fromUserId)
      .getSingleOrNull();
  if (contact != null) {
    // Either the contact has accepted the fromUserId already: Then just blindly accept the request.
    // Or the user has also requested fromUserId. This means that both user have requested each other (while been
    // offline for example): In this case the contact can also be accepted blindly.
    if (contact.accepted || (!contact.requested && !contact.deletedByUser)) {
      if (!contact.accepted) {
        // User has also requested the fromUserId, so mark the user as accepted.
        await handleContactAccept(fromUserId);
      }

      await sendCipherText(
        contact.userId,
        EncryptedContent(
          contactRequest: EncryptedContent_ContactRequest(
            type: EncryptedContent_ContactRequest_Type.ACCEPT,
          ),
        ),
        blocking: false,
      );
      return true;
    }
  }
  // Request the username by the server so an attacker can not
  // forge the displayed username in the contact request
  final user = await apiService.getUserById(fromUserId);
  if (user == null) {
    return false;
  }
  await twonlyDB.contactsDao.insertOnConflictUpdate(
    ContactsCompanion(
      username: Value(utf8.decode(user.username)),
      userId: Value(fromUserId),
      requested: const Value(true),
      deletedByUser: const Value(false),
    ),
  );
  await setupNotificationWithUsers();

  return true;
}

Future<void> handleContactAccept(int fromUserId) async {
  final contact = await twonlyDB.contactsDao
      .getContactByUserId(fromUserId)
      .getSingleOrNull();
  if (contact == null) return;
  if (contact.requested || contact.deletedByUser) {
    Log.error('User has never send an request. So ignore the Accept.');
    return;
  }

  await twonlyDB.contactsDao.updateContact(
    fromUserId,
    const ContactsCompanion(
      requested: Value(false),
      accepted: Value(true),
      deletedByUser: Value(false),
    ),
  );
  await twonlyDB.groupsDao.createNewDirectChat(
    fromUserId,
    GroupsCompanion(
      groupName: Value(getContactDisplayName(contact)),
    ),
  );
}

Future<bool> handleContactRequest(
  int fromUserId,
  EncryptedContent_ContactRequest contactRequest,
  String receiptId,
) async {
  switch (contactRequest.type) {
    case EncryptedContent_ContactRequest_Type.REQUEST:
      Log.info('[$receiptId] Got a contact request from $fromUserId');
      return handleNewContactRequest(fromUserId);
    case EncryptedContent_ContactRequest_Type.ACCEPT:
      Log.info('[$receiptId] Got a contact accept from $fromUserId');
      await handleContactAccept(fromUserId);
    case EncryptedContent_ContactRequest_Type.REJECT:
      Log.info('[$receiptId] Got a contact reject from $fromUserId');
      await twonlyDB.contactsDao.updateContact(
        fromUserId,
        const ContactsCompanion(
          accepted: Value(false),
          requested: Value(false),
          deletedByUser: Value(true),
        ),
      );
  }
  return true;
}

Future<void> handleContactUpdate(
  int fromUserId,
  EncryptedContent_ContactUpdate contactUpdate,
  int? senderProfileCounter,
  String receiptId,
) async {
  switch (contactUpdate.type) {
    case EncryptedContent_ContactUpdate_Type.REQUEST:
      Log.info('[$receiptId] Got a contact update request from $fromUserId');
      await sendContactMyProfileData(fromUserId);

    case EncryptedContent_ContactUpdate_Type.UPDATE:
      Log.info('[$receiptId] Got a contact update $fromUserId');
      Uint8List? avatarSvgCompressed;
      if (contactUpdate.hasAvatarSvgCompressed()) {
        avatarSvgCompressed = Uint8List.fromList(
          contactUpdate.avatarSvgCompressed,
        );
      }
      if (contactUpdate.hasDisplayName() &&
          contactUpdate.hasUsername() &&
          senderProfileCounter != null) {
        final contact = await twonlyDB.contactsDao
            .getContactByUserId(fromUserId)
            .getSingleOrNull();

        if (contact != null) {
          final sharedGroups = await twonlyDB.groupsDao.getGroupsForMember(
            fromUserId,
          );

          if (contact.username != contactUpdate.username) {
            for (final group in sharedGroups) {
              await twonlyDB.groupsDao.insertGroupAction(
                GroupHistoriesCompanion(
                  groupId: Value(group.groupId),
                  type: const Value(GroupActionType.updatedContactUsername),
                  contactId: Value(fromUserId),
                  oldGroupName: Value(contact.username),
                  newGroupName: Value(contactUpdate.username),
                ),
              );
            }
          }

          if (contact.displayName != contactUpdate.displayName) {
            for (final group in sharedGroups) {
              await twonlyDB.groupsDao.insertGroupAction(
                GroupHistoriesCompanion(
                  groupId: Value(group.groupId),
                  type: const Value(GroupActionType.updatedContactDisplayName),
                  contactId: Value(fromUserId),
                  oldGroupName: Value(contact.displayName ?? contact.username),
                  newGroupName: Value(contactUpdate.displayName),
                ),
              );
            }
          }
        }

        await twonlyDB.contactsDao.updateContact(
          fromUserId,
          ContactsCompanion(
            avatarSvgCompressed: Value(
              avatarSvgCompressed,
            ),
            displayName: Value(contactUpdate.displayName),
            username: Value(contactUpdate.username),
            senderProfileCounter: Value(senderProfileCounter),
          ),
        );
        unawaited(createPushAvatars(forceForUserId: fromUserId));
      }
  }
}

Future<void> handleFlameSync(
  String groupId,
  EncryptedContent_FlameSync flameSync,
  String receiptId,
) async {
  Log.info('[$receiptId] Got a flameSync for group $groupId');

  final group = await twonlyDB.groupsDao.getGroup(groupId);
  if (group == null || group.lastFlameCounterChange == null) return;

  var updates = GroupsCompanion(
    alsoBestFriend: Value(flameSync.bestFriend),
  );
  if (isToday(group.lastFlameCounterChange!) &&
          isToday(fromTimestamp(flameSync.lastFlameCounterChange)) ||
      flameSync.forceUpdate) {
    if (flameSync.flameCounter > group.flameCounter) {
      updates = updates.copyWith(
        flameCounter: Value(flameSync.flameCounter.toInt()),
      );
    }
    if (flameSync.flameCounter > group.maxFlameCounter) {
      updates = updates.copyWith(
        maxFlameCounter: Value(flameSync.flameCounter.toInt()),
      );
    }
  }
  await twonlyDB.groupsDao.updateGroup(group.groupId, updates);
}

Future<int?> checkForProfileUpdate(
  int fromUserId,
  EncryptedContent content,
) async {
  int? senderProfileCounter;

  if (content.hasSenderProfileCounter()) {
    senderProfileCounter = content.senderProfileCounter.toInt();
    if (!content.hasContactUpdate()) {
      final contact = await twonlyDB.contactsDao
          .getContactByUserId(fromUserId)
          .getSingleOrNull();
      if (contact != null) {
        if (contact.senderProfileCounter < senderProfileCounter) {
          await sendCipherText(
            fromUserId,
            EncryptedContent(
              contactUpdate: EncryptedContent_ContactUpdate(
                type: EncryptedContent_ContactUpdate_Type.REQUEST,
              ),
            ),
            blocking: false,
          );
        }
      }
    }
  }

  return senderProfileCounter;
}
