import { useDatabase } from "@/app/providers/DatabaseProvider/useDatabase";
import { usePrivatePlain } from "../AccountProvider/usePrivatePlain";
import { decodeWithPassword, encodeWithPassword } from "@/app/workers/crypto/crypto";
import { generateRandomKey } from "@/app/utils/utils";
import { useDialogsList } from "../DialogListProvider/useDialogsList";
import { usePublicKey } from "../AccountProvider/usePublicKey";
import { DeliveredMessageState } from "./DialogProvider";
import { useSender } from "../ProtocolProvider/useSender";
import { useState } from "react";
import { PacketCreateGroup } from "../ProtocolProvider/protocol/packets/packet.create.group";
import { useProtocol } from "../ProtocolProvider/useProtocol";
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
import { GroupStatus, PacketGroupJoin } from "../ProtocolProvider/protocol/packets/packet.group.join";
import { useGroupInviteStatus } from "./useGroupInviteStatus";
import { useNavigate } from "react-router-dom";
import { useUpdateGroupInformation } from "../InformationProvider/useUpdateGroupInformation";
import { PacketGroupLeave } from "../ProtocolProvider/protocol/packets/packet.group.leave";
import { PacketGroupBan } from "../ProtocolProvider/protocol/packets/packet.group.ban";

export function useGroups() : {
    /**
     * Получаем ключ шифрования из базы данных по ид группы
     * @param groupId ид группы
     * @returns ключ шифрования
     */
    getGroupKey: (groupId: string) => Promise<string>;
    /**
     * Получает строку для приглашения в группу
     * @param groupId ид группы
     * @param title заголовок
     * @param encryptKey ключ шифрования
     * @param description описание
     * @returns строка, которая нужна для приглашения в группу
     */
    constructGroupString: (groupId: string, title: string, encryptKey: string, description?: string) => Promise<string>;
    /**
     * Функция, обратная constructGroupString, парсит строку приглашения в группу
     * @param groupString строка приглашения в группу
     * @returns объект с информацией о группе или null, если строка некорректна
     */
    parseGroupString: (groupString: string) => Promise<{
        groupId: string;
        title: string;
        encryptKey: string;
        description: string;
    } | null>;
    /**
     * Проверяет, является ли диалог группой
     * @param dialog ид диалога
     * @returns вернет true, если это группа и false если это пользователь
     */
    hasGroup: (dialog: string) => boolean;
    /**
     * Возвращает подготовленный для роута groupId
     * @param groupId подготавливает groupId для роута
     * @returns заменяет символы которые может не обрабатывать роутер 
     */
    prepareForRoute: (groupId: string) => string;
    /**
     * Создает группу
     * @param title заголовок
     * @param description описание
     * @returns 
     */
    createGroup: (title: string, description: string) => Promise<void>;
    /**
     * Зайдет в группу по строке приглашения
     * @param groupString строка приглашение
     * @returns 
     */
    joinGroup: (groupString: string) => Promise<void>;
    /**
     * Покидает группу
     * @param groupId ид группы
     * @returns 
     */
    leaveGroup: (groupId: string) => Promise<void>;
    /**
     * 
     * @param str 
     * @returns 
     */
    normalize: (str: string) => string;
    banUserOnGroup: (userPublicKey: string, groupId: string) => void;
    getPrefix: () => string;
    loading: boolean;
} {
    const {allQuery, runQuery} = useDatabase();
    const privatePlain = usePrivatePlain();
    const {updateDialog} = useDialogsList();
    const publicKey = usePublicKey();
    const [loading, setLoading] = useState<boolean>(false);
    const send = useSender();
    const {protocol} = useProtocol();
    const {info} = useConsoleLogger('useGroups');
    const {setInviteStatusByGroupId} = useGroupInviteStatus('');
    const navigate = useNavigate();
    const updateGroupInformation = useUpdateGroupInformation();

    const constructGroupString = async (groupId: string, title: string, encryptKey: string, description?: string) => {
        let groupString = `${groupId}:${title}:${encryptKey}`;
        if (description && description.trim().length > 0) {
            groupString += `:${description}`;
        }
        let encodedPayload = await encodeWithPassword('rosetta_group', groupString);
        return `#group:${encodedPayload}`;
    }

    const hasGroup = (dialog: string) => {
        return dialog.startsWith('#group:');
    }

    const getPrefix = () => {
        return '#group:';
    }

    const parseGroupString = async (groupString: string) => {
        try{
            if (!groupString.startsWith('#group:')) {
                return null;
            }
            let encodedPayload = groupString.substring(7);
            let decodedPayload = await decodeWithPassword('rosetta_group', encodedPayload);
            let parts = decodedPayload.split(':');
            return {
                groupId: parts[0],
                title: parts[1],
                encryptKey: parts[2],
                description: parts[3] || ''
            }
        }catch(e) {
            return null;
        }
    }    

    const getGroupKey = async (groupId: string) => {
        const query = `SELECT key FROM groups WHERE group_id = ? AND account = ? LIMIT 1`;
        const result = await allQuery(query, [normalize(groupId), publicKey]);
        if(result.length > 0) {
            let key = result[0].key;
            return await decodeWithPassword(privatePlain, key);
        }
        return "";
    };

    const prepareForRoute = (groupId: string) => {
        return `#group:${groupId}`.replace('#', '%23');
    }

    const normalize = (str: string) => {
        return str.replace('#group:', '').trim();
    }

    const createGroup = async (title: string, description: string) => {
        if(title.trim().length === 0){
            return;
        }
        setLoading(true);
        const packet = new PacketCreateGroup();
        send(packet);
        protocol.waitPacketOnce(0x11, async (packet : PacketCreateGroup) => {
            const groupId = packet.getGroupId();
            info(`Creating group with id ${groupId}`);
            const encryptKey = generateRandomKey(64);
            /**
             * После создания группы в нее необходимо зайти, в соотвествии с новым протоколом
             */
            joinGroup(await constructGroupString(groupId, title, encryptKey, description));
        });            
    }

    const banUserOnGroup = (userPublicKey: string, groupId: string) => {
        const packet = new PacketGroupBan();
        packet.setGroupId(groupId);
        packet.setPublicKey(userPublicKey);
        send(packet);
    }

    const joinGroup = async (groupString: string) => {
        const parsed = await parseGroupString(groupString);
        if (!parsed) {
            return;
        }
        const encryptKey = parsed.encryptKey;
        const groupId = parsed.groupId;
        const title = parsed.title;
        const description = parsed.description;
        const encodedGroupString = await encodeWithPassword(privatePlain, groupString);

        const packet = new PacketGroupJoin();
        packet.setGroupId(parsed.groupId);
        packet.setGroupString(encodedGroupString);
        send(packet);
        setLoading(true);

        protocol.waitPacketOnce(0x14, async (packet: PacketGroupJoin) => {
            console.info(`Received group join response for group ${parsed.groupId}`);
            const groupStatus = packet.getGroupStatus();
            if(groupStatus != GroupStatus.JOINED){
                info(`Cannot join group ${parsed.groupId}, banned`);
                setInviteStatusByGroupId(parsed.groupId, groupStatus);
                setLoading(false);
                return;
            }
            const secureKey = await encodeWithPassword(privatePlain, encryptKey);
            let content = await encodeWithPassword(encryptKey, `$a=Group joined`);
            let plainMessage = await encodeWithPassword(privatePlain, `$a=Group joined`);
            await runQuery(`
                    INSERT INTO groups (account, group_id, title, description, key) VALUES (?, ?, ?, ?, ?)
                `, [publicKey, groupId, title, description, secureKey]);
            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, "#group:" + groupId, content, Date.now(), 1, "", 1, plainMessage, publicKey, generateRandomKey(16),
                    DeliveredMessageState.DELIVERED
                , '[]']);
            updateDialog("#group:" + groupId); 
            setInviteStatusByGroupId(groupId, GroupStatus.JOINED);
            setLoading(false);     
            updateGroupInformation({
                groupId: groupId,
                title: title,
                description: description
            });   
            navigate(`/main/chat/${prepareForRoute(groupId)}`);    
        });
    }

    const leaveGroup = async (groupId: string) => {
        const packet = new PacketGroupLeave();
        packet.setGroupId(groupId);
        send(packet);
        setLoading(true);
        protocol.waitPacketOnce(0x15, async (packet: PacketGroupLeave) => {
            if(packet.getGroupId() != groupId){
                return;
            }
            await runQuery(`
                DELETE FROM groups WHERE group_id = ? AND account = ?
            `, [groupId, publicKey]);
            await runQuery(`
                DELETE FROM messages WHERE to_public_key = ? AND account = ?
            `, ["#group:" + normalize(groupId), publicKey]);
            updateDialog("#group:" + normalize(groupId));
            setLoading(false);
            navigate(`/main`);
        });
    }

    return {
        getGroupKey,
        constructGroupString,
        parseGroupString,
        hasGroup,
        prepareForRoute,
        createGroup,
        joinGroup,
        leaveGroup,
        getPrefix,
        banUserOnGroup,
        normalize,
        loading
    }
}