import { useContext } from "react";
import { useDatabase } from "../DatabaseProvider/useDatabase";
import { chacha20Encrypt, encodeWithPassword, encrypt, generateMd5} from "../../workers/crypto/crypto";
import { AttachmentMeta, DeliveredMessageState, DialogContext, Message } from "./DialogProvider";
import { Attachment, AttachmentType, PacketMessage } from "@/app/providers/ProtocolProvider/protocol/packets/packet.message";
import { usePublicKey } from "../AccountProvider/usePublicKey";
import { usePrivatePlain } from "../AccountProvider/usePrivatePlain";
import { usePrivateKeyHash } from "../AccountProvider/usePrivateKeyHash";
import { useSender } from "../ProtocolProvider/useSender";
import { generateRandomKey } from "@/app/utils/utils";
import { useFileStorage } from "@/app/hooks/useFileStorage";
import { useDialogsList } from "../DialogListProvider/useDialogsList";
import { useProtocolState } from "../ProtocolProvider/useProtocolState";
import { ProtocolState } from "../ProtocolProvider/ProtocolProvider";
import { useGroups } from "./useGroups";
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
import { usePrepareAttachment } from "../AttachmentProvider/usePrepareAttachment";

export function useDialog() : {
    messages: Message[];
    sendMessage: (message: string, attachemnts : Attachment[]) => Promise<void>;
    deleteMessages: () => Promise<void>;
    loadMessagesToTop: (count?: number) => Promise<void>;
    deleteMessageById: (messageId: string) => Promise<void>;
    loading: boolean;
    deleteSelectedMessages: (messageIds: string[]) => Promise<void>;
    dialog: string;
    loadMessagesToMessageId: (messageId: string) => Promise<void>;
    updateAttachmentsInMessagesByAttachmentId: (attachmentId: string, blob: string) => Promise<void>;
} {
    const {runQuery} = useDatabase();
    const send = useSender();
    const context = useContext(DialogContext);
    if(!context) {
        throw new Error("useDialog must be used within a DialogProvider");
    }
    const {loading, 
        messages,
        clearDialogCache, 
        setMessages, 
        dialog, loadMessagesToTop, loadMessagesToMessageId} = context;
    const {updateDialog} = useDialogsList();
    const publicKey = usePublicKey();
    const privateKey = usePrivateKeyHash();
    const privatePlain = usePrivatePlain();
    const {writeFile} = useFileStorage();
    const [protocolState] = useProtocolState();
    const {hasGroup, getGroupKey} = useGroups();
    const {warn} = useConsoleLogger('useDialog');
    const {prepareAttachmentsToSend} = usePrepareAttachment();

    /**
     * Отправка сообщения в диалог
     * @param message Сообщение
     * @param attachemnts Вложения
     */
    const sendMessage = async (message: string, attachemnts : Attachment[]) => {
        const messageId = generateRandomKey(16);

        let cahchaEncrypted = {ciphertext: "", key: "", nonce: ""} as any;
        let key = Buffer.from("");
        let encryptedKey = "";
        let plainMessage = "";
        let content = "";

        if(!hasGroup(dialog)){
            cahchaEncrypted = (await chacha20Encrypt(message.trim()) as any);
            key = Buffer.concat([
                Buffer.from(cahchaEncrypted.key, "hex"),
                Buffer.from(cahchaEncrypted.nonce, "hex")]);
            encryptedKey = await encrypt(key.toString('binary'), dialog);
            plainMessage = await encodeWithPassword(privatePlain, message.trim());
            content = cahchaEncrypted.ciphertext;
        }else{
            /**
             * Это группа, там шифрование устроено иначе
             * для групп используется один общий ключ, который
             * есть только у участников группы, сам ключ при этом никак
             * не отправляется по сети (ведь ID у группы общий и у каждого
             * и так есть этот ключ)
             */
            const groupKey = await getGroupKey(dialog);
            if(!groupKey){
                warn("Group key not found for dialog " + dialog);
                return;
            }
            content = await encodeWithPassword(groupKey, message.trim());
            plainMessage = await encodeWithPassword(privatePlain, message.trim());
            encryptedKey = ""; // В группах не нужен зашифрованный ключ
            key = Buffer.from(groupKey);
        }

        /**
         * Нужно зашифровать ключ еще и нашим ключом,
         * чтобы в последствии мы могли расшифровать этот ключ у своих 
         * же сообщений (смотреть problem_sync.md)
         */
        const aesChachaKey = await encodeWithPassword(privatePlain, key.toString('binary'));
        setMessages((prev : Message[]) => ([...prev, {
            from_public_key: publicKey,
            to_public_key: dialog,
            content: content,
            timestamp: Date.now(),
            readed: publicKey == dialog ? 1 : 0,
            chacha_key: key.toString('hex'),
            from_me: 1,
            plain_message: message,
            delivered: publicKey == dialog ? DeliveredMessageState.DELIVERED : DeliveredMessageState.WAITING,
            message_id: messageId,
            attachments: attachemnts
        }]));


        let attachmentsMeta : AttachmentMeta[] = [];
        for(let i = 0; i < attachemnts.length; i++) {
            const attachment = attachemnts[i];
            attachmentsMeta.push({
                id: attachment.id,
                type: attachment.type,
                preview: attachment.preview,
                transport: attachment.transport
            });
            if(attachment.type == AttachmentType.FILE){
                /**
                 * Обычно вложения дублируются на диск. Так происходит со всем.
                 * Кроме файлов. Если дублировать файл весом в 2гб на диск отправка будет
                 * занимать очень много времени.
                 * К тому же, это приведет к созданию ненужной копии у отправителя
                 */
                continue;
            }
            writeFile(`m/${await generateMd5(attachment.id + publicKey)}`, Buffer.from(await encodeWithPassword(privatePlain, attachment.blob)).toString('binary'));
        }

        await runQuery(`
            INSERT INTO messages 
                (from_public_key, to_public_key, content, timestamp, read, chacha_key, from_me, plain_message, account, message_id, delivered, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            `, [publicKey, dialog, content, Date.now(), publicKey == dialog ? 1 : 0, aesChachaKey, 1, plainMessage, publicKey, messageId, publicKey == dialog ? DeliveredMessageState.DELIVERED : (
                protocolState != ProtocolState.CONNECTED ? DeliveredMessageState.ERROR : DeliveredMessageState.WAITING
            ), JSON.stringify(attachmentsMeta)]);
        updateDialog(dialog);
        if(publicKey == "" 
            || dialog == "" 
            || publicKey == dialog) {
            return;
        }

        console.info("Sending key for message ", key.toString('hex'));
        let preparedToNetworkSendAttachements : Attachment[] = await prepareAttachmentsToSend(messageId, dialog, key.toString('hex'), attachemnts);
        if(attachemnts.length <= 0 && message.trim() == ""){
            runQuery("UPDATE messages SET delivered = ? WHERE message_id = ?", [DeliveredMessageState.ERROR, messageId]);
            updateDialog(dialog);
            return;
        }
        const packet = new PacketMessage();
        packet.setFromPublicKey(publicKey);
        packet.setToPublicKey(dialog);
        packet.setContent(content);
        packet.setChachaKey(encryptedKey);
        packet.setPrivateKey(privateKey);
        packet.setMessageId(messageId);
        packet.setTimestamp(Date.now());  
        packet.setAttachments(preparedToNetworkSendAttachements); 
        packet.setAesChachaKey(aesChachaKey);
        send(packet);
    }

    const deleteMessages = async () => {
        if(!hasGroup(dialog)){
            await runQuery(`
                DELETE FROM messages WHERE ((from_public_key = ? AND to_public_key = ?) OR (from_public_key = ? AND to_public_key = ?)) AND account = ?
            `, [dialog, publicKey, publicKey, dialog, publicKey]);
        }else{
            await runQuery(`
                DELETE FROM messages WHERE to_public_key = ? AND account = ?
            `, [dialog, publicKey]);
        }
        setMessages([]);
        updateDialog(dialog);
        clearDialogCache();
    }

    const deleteMessageById = async (messageId: string) => {
        await runQuery(`
            DELETE FROM messages WHERE message_id = ? AND account = ?
        `, [messageId, publicKey]);
        setMessages((prev) => prev.filter((msg) => msg.message_id !== messageId));
        updateDialog(dialog);
    }

    const deleteSelectedMessages = async (messageIds: string[]) => {
        if(messageIds.length == 0){
            return;
        }
        /**
         * Old messages support, ignore empty IDs
         * @since 0.1.7 all messages have IDs
         */
        let idsNotEmpty = messageIds.filter(v => v.trim() != "");
        if(idsNotEmpty.length == 0){
            return;
        }

        const placeholders = idsNotEmpty.map(() => '?').join(',');
        await runQuery(`
            DELETE FROM messages WHERE message_id IN (` +placeholders+ `) AND account = ?
        `, [...idsNotEmpty, publicKey]);
        setMessages((prev) => prev.filter((msg) => !messageIds.includes(msg.message_id)));
        updateDialog(dialog);
    }

    const updateAttachmentsInMessagesByAttachmentId = async (attachmentId: string, blob: string) => {
        setMessages((prevMessages) => {
            return prevMessages.map((msg) => {
                let updated = false;
                const updatedAttachments = msg.attachments.map((attachment) => {
                    if (attachment.id === attachmentId) {
                        updated = true;
                        return {
                            ...attachment,
                            blob: blob
                        };
                    }
                    return attachment;
                });
                if (updated) {
                    return {
                        ...msg,
                        attachments: updatedAttachments,
                    };
                }
                return msg;
            });
        });
    }

    return {
        messages,
        sendMessage, updateAttachmentsInMessagesByAttachmentId, deleteMessages, loadMessagesToTop, loadMessagesToMessageId, deleteMessageById, loading, deleteSelectedMessages,
        dialog,
    };
}