import { Q } from '@nozbe/watermelondb';
import { database } from 'src/watermelondb';
import Chat from 'src/watermelondb/models/Chat';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';

dayjs.extend(utc);

type ServerChat = {
  uid: number | null;
  group_chat_token: string | null;
  name: string;
  avatar: string | null;
  short: string;
  sent_by: number;
  updated: Date | string;
  status: 1 | 2 | 3 | 4;
  unread_count: number;
  last_message_id: number;
  pin: 0 | 1;
  pin_order: number;
  archive: 0 | 1;
  archive_order: number;
  attachement_name: string;
  encrypted: 0 | 1;
  muted: 0 | 1;
  user_type: 'normal' | 'not_exist' | 'blocked';
  can_send_message: 0 | 1;
  is_admin: 0 | 1;
  announcement: 0 | 1;
};

export async function upsertChats(chats: ServerChat[]) {
  if (!chats?.length) return;

  const chatCollection = database.get<Chat>('chats');
  const seen = new Set<string>();

  const makeKey = (c: ServerChat) => (c.uid ? `u:${c.uid}` : `g:${c.group_chat_token}`);

  await database.write(async () => {
    const batch: any[] = [];

    for (const c of chats) {
      const key = makeKey(c);

      if (seen.has(key)) {
        continue;
      }

      seen.add(key);

      const existingNow = await chatCollection
        .query(c.uid ? Q.where('chat_uid', c.uid) : Q.where('group_chat_token', c.group_chat_token))
        .fetch();

      if (existingNow.length) {
        batch.push(
          existingNow[0].prepareUpdate((rec) => {
            rec.chatKey = makeChatKey({ chatUid: c.uid, groupChatToken: c.group_chat_token });
            rec.name = c.name;
            rec.avatar = c.avatar ?? null;
            rec.short = c.short;
            rec.sentBy = c.sent_by;
            rec.updated = dayjs.utc(c.updated, 'YYYY-MM-DD HH:mm:ss', true).valueOf();
            rec.status = c.status;
            rec.unreadCount = c.unread_count;
            rec.lastMessageId = c.last_message_id;
            rec.pin = c.pin;
            rec.pinOrder = c.pin_order;
            rec.archive = c.archive;
            rec.archiveOrder = c.archive_order;
            rec.attachmentName = c.attachement_name;
            rec.encrypted = c.encrypted;
            rec.muted = c.muted;
            rec.userType = c.user_type ?? null;
            rec.canSendMessages = c.can_send_message ?? 1;
            rec.isAdmin = c.is_admin ?? 1;
            rec.announcement = c.announcement ?? 0;
            rec.removed = false;
          })
        );
      } else {
        batch.push(
          chatCollection.prepareCreate((rec) => {
            rec.chatKey = makeChatKey({ chatUid: c.uid, groupChatToken: c.group_chat_token });
            rec.chatUid = c.uid ?? null;
            rec.groupChatToken = c.group_chat_token ?? null;

            rec.name = c.name;
            rec.avatar = c.avatar ?? null;
            rec.short = c.short;
            rec.sentBy = c.sent_by;
            rec.updated = dayjs.utc(c.updated, 'YYYY-MM-DD HH:mm:ss', true).valueOf();
            rec.status = c.status;
            rec.unreadCount = c.unread_count;
            rec.lastMessageId = c.last_message_id;
            rec.pin = c.pin;
            rec.pinOrder = c.pin_order;
            rec.archive = c.archive;
            rec.archiveOrder = c.archive_order;
            rec.attachmentName = c.attachement_name;
            rec.encrypted = c.encrypted;
            rec.muted = c.muted;
            rec.userType = c.user_type;

            rec.canSendMessages = c.can_send_message ?? 1;
            rec.isAdmin = c.is_admin ?? 1;
            rec.announcement = c.announcement ?? 0;
            rec.removed = false;
          })
        );
      }
    }

    await database.batch(batch);
    console.log('upsertChats complete');
  });
}

export function makeChatKey(input: { chatUid?: number | null; groupChatToken?: string | null }) {
  if (input.chatUid) return `u:${input.chatUid}`;
  if (input.groupChatToken) return `g:${input.groupChatToken}`;
  throw new Error('Chat must have uid or group token');
}

export async function backfillChatKeys() {
  const col = database.get<Chat>('chats');
  const chats = await col.query().fetch();

  await database.write(async () => {
    for (const c of chats) {
      if (!c.chatKey) {
        const key = makeChatKey({
          chatUid: c.chatUid,
          groupChatToken: c.groupChatToken
        });

        await c.update((r) => {
          r.chatKey = key;
        });
      }
    }
  });
}

export async function dedupeChats() {
  const chatCollection = database.get<Chat>('chats');
  const chats = await chatCollection.query().fetch();

  const map = new Map<string, Chat[]>();

  for (const c of chats) {
    if (!c.chatKey) continue;

    if (!map.has(c.chatKey)) map.set(c.chatKey, []);
    map.get(c.chatKey)!.push(c);
  }

  await database.write(async () => {
    for (const [, list] of map) {
      if (list.length <= 1) continue;

      list.sort((a, b) => {
        if (a.isDirty && !b.isDirty) return -1;
        if (!a.isDirty && b.isDirty) return 1;
        return b.updated - a.updated;
      });

      const [, ...duplicates] = list;

      for (const d of duplicates) {
        await d.destroyPermanently();
      }
    }
  });
}

export async function clearLocalDatabaseOnLogout() {
  await database.write(async () => {
    await database.unsafeResetDatabase();
  });
}
