/*
Copyright (C) 2024 - 2025 3NSoft Inc.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { appChatsSrv, appDeliverySrv } from '@main/services/services-provider';
import { ChatsStore, useChatsStore } from '@main/store/chats.store';
import { ContactsStore, useContactsStore } from '@main/store/contacts.store';
import { includesAddress, toCanonicalAddress } from '@shared/address-utils';
import { storeToRefs } from 'pinia';
import { AppDeliveryService, ChatIncomingMessage, ChatMessageView, ChatSystemMessageData, SystemMessageHandlerParams } from '~/index';
import { getAttachmentFilesInfo } from '@main/helpers/chats.helper';
import { Nullable } from '@v1nt1248/3nclient-lib';
import { handleAddChatMembers } from './system-message-handlers/handle-add-chat-members';
import { handleRemoveChatMembers } from './system-message-handlers/handle-remove-chat-members';
import { handleDeleteChatMembers } from './system-message-handlers/handle-delete-chat-members';
import { handleUpdateMessageStatus } from './system-message-handlers/handle-update-message-status';
import { handleDeleteChatMessage } from './system-message-handlers/handle-delete-chat-message';
import { handleUpdateChatName } from './system-message-handlers/handle-update-chat-name';
import { sendSystemMessage } from './send-system-message';
// import { createChat } from './create-chat';
import { makeServiceCaller } from '@shared/ipc/ipc-service-caller';
import { getDeliveryErrors } from '@v1nt1248/3nclient-lib/utils';
import { size } from 'lodash';
import { useAppStore } from '../app.store';
import { ChatStore, useChatStore } from '../chat.store';

export type HandleChatIncomingMessage = (
  msg: ChatIncomingMessage
) => Promise<void>;

function makeIncomingMessageHandler(
  chatsStore: ChatsStore, chatStore: ChatStore, contactsStore: ContactsStore,
  ownAddr: string
): HandleChatIncomingMessage {

  const { fetchChatList, createChat } = chatsStore;
  const { chatList } = storeToRefs(chatsStore);
  const {
    pushMsgIntoCurrentChatMessages, fetchChat,
    getWritableCurrentChatMessageView 
  } = chatStore;
  const { currentChatId, currentChatMessages } = storeToRefs(chatStore);
  const { fetchContacts } = contactsStore;

  return async msg => {

    const { jsonBody, msgId } = msg;
    const { chatMessageType, chatId, members = [] } = jsonBody;

    const membersFromDb = chatList.value[chatId]?.members;
    const realMembers = membersFromDb || members;
    if (
      chatMessageType === 'system' ||
      (chatMessageType === 'regular' && includesAddress(realMembers, ownAddr))
    ) {

      await fetchContacts();

      const {
        jsonBody,
        msgId,
        attachments,
        plainTxtBody,
        htmlTxtBody,
        sender,
      } = msg;
      const {
        chatId,
        chatMessageType = 'regular',
        chatMessageId,
        members,
        admins = [],
        chatName,
        initialMessageId,
        chatSystemData = {} as ChatSystemMessageData,
      } = jsonBody;
      const isChatPresent = Object.keys(chatList.value).includes(chatId);
    
      if (!isChatPresent) {
        const name = isGroupChat(members, ownAddr)
          ? chatName || 'Group chat'
          : chatName || sender;
        await createChat({ chatId, members, admins, name });
      }
    
      switch (chatMessageType) {
        case 'system':
          const { event, value, displayable = false } = chatSystemData;
          const params: SystemMessageHandlerParams = {
            chatId, msgId, sender, chatMessageId, value, displayable,
          };
          switch (event) {
            case 'add:members':
              return await handleAddChatMembers(
                fetchChatList, pushMsgIntoCurrentChatMessages, params
              );
            case 'remove:members':
              return await handleRemoveChatMembers(
                fetchChatList, pushMsgIntoCurrentChatMessages, params
              );
            case 'delete:members':
              return await handleDeleteChatMembers(
                fetchChatList, pushMsgIntoCurrentChatMessages, params
              );
            case 'update:status':
              return await handleUpdateMessageStatus(
                getWritableCurrentChatMessageView, params
              );
            case 'delete:message':
              return await handleDeleteChatMessage(
                fetchChatList, currentChatId, fetchChat, params
              );
            case 'update:chatName':
              return await handleUpdateChatName(
                fetchChatList, pushMsgIntoCurrentChatMessages, params
              );
            default:
              return await w3n.log(
                'error', `No handler found to handle chat system event ${event}`
              );
          }
        default:
          const msgView: ChatMessageView<'incoming'> = {
            msgId,
            attachments: await getAttachmentFilesInfo({
              incomingAttachments: attachments
            }),
            messageType: 'incoming',
            sender,
            body: plainTxtBody! || htmlTxtBody! || '',
            chatId,
            chatMessageType,
            chatMessageId,
            initialMessageId: initialMessageId || null,
            timestamp: Date.now(),
            status: 'received',
          };
    
          await appChatsSrv.upsertMessage(msgView);
          pushMsgIntoCurrentChatMessages(msgView);
          await fetchChatList();
          sendSystemMessage({
            chatId,
            chatMessageId,
            recipients: [sender],
            event: 'update:status',
            value: 'received',
          });
          if (!attachments) {
            appDeliverySrv.removeMessageFromInbox([msgId]);
          }
          break;
      }

    } else {
      appDeliverySrv.removeMessageFromInbox([msgId]);
    }

  };
}

function isGroupChat(members: string[], ownAddr: string): boolean {
  const all = members.map(toCanonicalAddress);
  const canonOwnAddr = toCanonicalAddress(ownAddr);
  const other = all.filter(addr => (addr !== canonOwnAddr));
  return (other.length > 1);
}

export async function startMessagesProcessing(): Promise<() => void> {

  const chatsStore = useChatsStore();
  const chatStore = useChatStore();
  const { getWritableCurrentChatMessageView } = chatStore;
  const contactsStore = useContactsStore();
  const { user: ownAddr } = useAppStore();

  const deliverySrvConnection = await w3n.rpc!.thisApp!('ChatDeliveryService');
  const deliverSrv = makeServiceCaller(
    deliverySrvConnection,
    [],
    ['watchIncomingMessages', 'watchOutgoingMessages'],
  ) as AppDeliveryService;

  const stopIncomingMsgObservation = deliverSrv.watchIncomingMessages({
    next: makeIncomingMessageHandler(
      chatsStore, chatStore, contactsStore, ownAddr
    ),
    error: (e) => console.error(e),
    complete: () => deliverySrvConnection.close(),
  });

  const stopOutgoingMsgObservation = deliverSrv.watchOutgoingMessages({
    next: (val) => {
      const { id, progress } = val;
      const { allDone, recipients } = progress;
      if (allDone) {
        appDeliverySrv.removeMessageFromDeliveryList([id]);
        const errors = getDeliveryErrors(progress);
        const { localMeta = {} } = progress;
        const { path } = localMeta;

        if (!path.includes('system')) {
          // TODO it's necessary to change when we will add new delivery status (not only 'sent' or 'error')
          const status = (
            (size(errors) === 0) || (size(recipients) > size(errors))
          ) ? 'sent' : 'error';

          handleUpdateMessageStatus(
            getWritableCurrentChatMessageView, { msgId: id, value: status }
          );
        }
      }
    },
    error: (e) => console.error(e),
    complete: () => deliverySrvConnection.close(),
  });

  return () => {
    stopIncomingMsgObservation();
    stopOutgoingMsgObservation();
  };
}
