import { createContext, useCallback, useContext, useEffect, useState } from "react";
import { useDatabase } from "@/app/providers/DatabaseProvider/useDatabase";
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
import { usePublicKey } from "../AccountProvider/usePublicKey";
import { DIALOG_DROP_TO_REQUESTS_IF_NO_MESSAGES_FROM_ME_COUNT } from "@/app/constants";
import { useQueue } from "@/app/hooks/useQueue";
import { useSystemAccounts } from "../SystemAccountsProvider/useSystemAccounts";
import { DialogStateContext } from "../DialogStateProvider.tsx/DialogStateProvider";


interface DialogListContextValue {
    dialogs: DialogRow[];
    setDialogs: (dialogs: DialogRow[]) => void;
    updateDialog: (dialog_id: string) => void;
    loadingDialogs: number; //if > 0 then loading
}

export const DialogListContext = createContext<DialogListContextValue|null>(null);

interface DialogListProviderProps {
    children: React.ReactNode;
}

export interface DialogRow {
    dialog_id: string;
    last_message_id: string;
    last_timestamp: number; //timestamp update
    last_message_timestamp: number; //message timestamp
    is_request: boolean;
    /**
     * Закреплен ли диалог
     */
    pinned?: boolean;
}

export function DialogListProvider(props : DialogListProviderProps) {
    const [dialogs, setDialogs] = useState<DialogRow[]>([]);
    const {info} = useConsoleLogger('DialogListProvider');
    const { allQuery, getQuery, runQuery } = useDatabase();
    const publicKey = usePublicKey();
    const { inProcess, addToQueue, removeFromQueue, queue } = useQueue<string>();
    const systemAccounts = useSystemAccounts();
    const { pinned } = useContext(DialogStateContext)!;
    const [loadingDialogs, setLoadingDialogs] = useState<number>(0);

    useEffect(() => {
        initialLoadingFromDatabase();
    }, [publicKey]);

    useEffect(() => {
        //console.info(pinned);
        setDialogs((prevDialogs) => {
            const newDialogs = prevDialogs.map(d => {
                return {
                    ...d,
                    pinned: pinned.includes(d.dialog_id)
                }
            });
            return newDialogs.sort((a, b) => 
                a.pinned === b.pinned ? 
                    b.last_message_timestamp - a.last_message_timestamp 
                    : (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));
        });
    }, [pinned]);

    const initialLoadingFromDatabase = async () => {
        if(publicKey == ''){
            return;
        }
        const rows = await allQuery(`SELECT * FROM dialogs WHERE account = ? ORDER BY last_timestamp DESC`, [publicKey]);
        const loadedDialogs: DialogRow[] = rows.map((row: any) => {
            /**
             * Если диалог системный (бот, канал и т.д.), то он не может быть запросом
             */
            const isRequest = row.is_request 
            && !systemAccounts.some(acc => acc.publicKey === row.dialog_id);

            return {
                dialog_id: row.dialog_id,
                last_message_id: row.last_message_id,
                last_timestamp: Date.now(),
                is_request: isRequest,
                last_message_timestamp: row.last_timestamp,
                pinned: pinned.includes(row.dialog_id),
            }
        });
        const prepartedDialogsForBatching = loadedDialogs.sort((a, b) => a.pinned === b.pinned ? b.last_message_timestamp - a.last_message_timestamp : (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));
        
        if(prepartedDialogsForBatching.length > 0){
            /**
             * Показываем индикатор загрузки, потому что
             * есть баг с отрисовкой большого количества диалогов сразу
             * TODO: найти причину бага и исправить
             * UI блокируется на время отрисовки большого количества диалогов
             * не надо так)))
             */
            setLoadingDialogs(prepartedDialogsForBatching.length);
            setTimeout(() => {
                setLoadingDialogs(0);
                setDialogs(prepartedDialogsForBatching);
            }, 100);
        }
        
        info(`Loaded ${loadedDialogs.length} dialogs from database.`);
    }

    /**
     * Обновляет информацию о диалоге в общем списке диалогов
     * @param dialog_id id диалога в формате public_key или #group:group_id
     * @returns 
     */
    const updateDialog = useCallback(async (dialog_id: string) => {
        if(inProcess(dialog_id)){
            info(`Dialog ${dialog_id} is already being processed, skipping update.`);
            setTimeout(() => {
                /**
                 * Попытка обновить диалог еще раз, чтобы избежать гонки
                 */
                updateDialog(dialog_id);
            }, 100);
            return;
        }
        addToQueue(dialog_id);
        let last_message : any = null;
        if(dialog_id.startsWith('#group:')){
            last_message = await getQuery(`SELECT * FROM messages 
                WHERE to_public_key = ? 
                AND account = ? 
                ORDER BY timestamp 
                DESC LIMIT 1`, [dialog_id, publicKey]);
        }else{
            last_message = await getQuery(`SELECT * FROM messages 
                WHERE ((from_public_key = ? AND to_public_key = ?) OR (from_public_key = ? AND to_public_key = ?))
                AND account = ? 
                ORDER BY timestamp 
                DESC LIMIT 1`, [dialog_id, publicKey, publicKey, dialog_id, publicKey]);
        }
        let dialogsWithId = await getQuery(`SELECT COUNT(*) as count FROM dialogs WHERE dialog_id = ? AND account = ?`, [dialog_id, publicKey]);

        if(!last_message && dialogsWithId.count > 0){
            setDialogs((prevDialogs) => {
                const filteredDialogs = prevDialogs.filter(d => d.dialog_id !== dialog_id);
                return filteredDialogs.sort((a, b) => 
                a.pinned === b.pinned ? 
                    b.last_message_timestamp - a.last_message_timestamp 
                    : (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));;
            });
            info(`Delete dialog ${dialog_id} as it has no messages.`);
            await runQuery(`DELETE FROM dialogs WHERE dialog_id = ? AND account = ?`, [dialog_id, publicKey]);
            removeFromQueue(dialog_id);
            return;
        }

        if(!last_message){
            info(`No messages found for dialog ${dialog_id}, skipping update.`);
            removeFromQueue(dialog_id);
            return;
        }

        let messagesFromMeCount = await getQuery(`SELECT COUNT(*) as count FROM messages 
            WHERE (
                (from_public_key = ? AND to_public_key = ?) 
                OR 
                (from_public_key = ? AND to_public_key = ?)
            )
            AND from_public_key = ? 
            AND account = ? ORDER BY timestamp DESC LIMIT ?`, 
            [dialog_id, publicKey, publicKey, dialog_id, publicKey, publicKey, DIALOG_DROP_TO_REQUESTS_IF_NO_MESSAGES_FROM_ME_COUNT]);

        const updatedDialog: DialogRow = {
            dialog_id: dialog_id,
            last_message_id: last_message.message_id,
            last_timestamp: Date.now(),
            is_request: messagesFromMeCount.count <= 0 && !systemAccounts.some(acc => acc.publicKey === dialog_id),
            last_message_timestamp: last_message.timestamp,
            pinned: pinned.includes(dialog_id)
        };


        // Обновляем состояние диалога в базе
        if(dialogsWithId.count > 0){
            await runQuery(`UPDATE dialogs SET last_message_id = ?, last_timestamp = ?, is_request = ? WHERE dialog_id = ? AND account = ?`, [updatedDialog.last_message_id, updatedDialog.last_message_timestamp, updatedDialog.is_request, dialog_id, publicKey]);
        }else{
            await runQuery(`INSERT INTO dialogs (dialog_id, last_message_id, last_timestamp, is_request, account) VALUES (?, ?, ?, ?, ?)`, [dialog_id, updatedDialog.last_message_id, updatedDialog.last_message_timestamp, updatedDialog.is_request, publicKey]);
        }

        setDialogs((prevDialogs) => {
            const filteredDialogs = prevDialogs.filter(d => d.dialog_id !== dialog_id);
            const newDialogs = [updatedDialog, ...filteredDialogs];
            return newDialogs.sort((a, b) => 
                a.pinned === b.pinned ? 
                    b.last_message_timestamp - a.last_message_timestamp 
                    : (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));
        });
        removeFromQueue(dialog_id);
        info(`Dialog ${dialog_id} updated.`);
    },  [publicKey, queue, pinned, systemAccounts]);


    return (
        <DialogListContext.Provider value={{
            dialogs,
            setDialogs,
            updateDialog,
            loadingDialogs
        }}>
            {props.children}
        </DialogListContext.Provider>
    )
}