import { useDatabase } from "@/app/providers/DatabaseProvider/useDatabase";
import { createContext, useEffect, useRef, useState } from "react";
import { usePublicKey } from "../AccountProvider/usePublicKey";
import { useFileStorage } from "@/app/hooks/useFileStorage";
import { decodeWithPassword, encodeWithPassword, generateMd5 } from "@/app/workers/crypto/crypto";
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
import { useSystemAccounts } from "../SystemAccountsProvider/useSystemAccounts";
import { AVATAR_PASSWORD_TO_ENCODE } from "@/app/constants";

export const AvatarContext = createContext({});

interface AvatarProviderProps {
    children: React.ReactNode;
}

export interface AvatarInformation {
    avatar: string;
    timestamp: number;
}

interface AvatarCacheEntry {
    publicKey: string;
    avatars: AvatarInformation[];
}

export function AvatarProvider(props : AvatarProviderProps) {
    const {runQuery, allQuery} = useDatabase();
    const publicKey = usePublicKey();
    const [deliveredAvatars, setDeliveredAvatars] = 
        useState<string[]>([]);
    const {readFile, writeFile} = useFileStorage();
    const loadCacheRunningRef = useRef<string[]>([]);
    const { error } = useConsoleLogger("AvatarProvider");
    const systemAccounts = useSystemAccounts();
    /**
     * Дополнительный кэширующий слой для декодированных аватарок,
     * чтобы не декодировать их каждый раз из базы данных.
     */
    const [decodedAvatarsCache, setDecodedAvatarsCache] = useState<AvatarCacheEntry[]>([]);

    useEffect(() => {
        loadSystemAvatars();
    }, []);

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

    const saveAvatar = async (fromPublicKey: string, path : string, decryptedContent : string) => {
        const timestamp = Date.now();
        await runQuery("INSERT INTO `avatar_cache` (public_key, avatar, timestamp) VALUES (?, ?, ?)", 
            [fromPublicKey, path, timestamp]);
        setDecodedAvatarsCache((prev) => {
            const existingEntry = prev.find(e => e.publicKey === fromPublicKey);
            if(existingEntry){
                return prev.map(e => {
                    if(e.publicKey === fromPublicKey){
                        return {
                            publicKey: fromPublicKey,
                            avatars: [{
                                avatar: decryptedContent,
                                timestamp,
                            }, ...e.avatars]
                        }
                    }
                    return e;
                });
            } else {
                return [...prev, {
                    publicKey: fromPublicKey,
                    avatars: [{
                        avatar: decryptedContent,
                        timestamp,
                    }]
                }];
            }
        });
    }

    const loadSystemAvatars = async () => {
        let avatarCacheEntrys : AvatarCacheEntry[] = [];
        for(let i = 0; i < systemAccounts.length; i++){
            let account = systemAccounts[i];
            avatarCacheEntrys.push({
                publicKey: account.publicKey,
                avatars: [{
                    avatar: account.avatar,
                    timestamp: Date.now(),
                }]
            });
        }
        setDecodedAvatarsCache((prev) => {
            return [...prev, ...avatarCacheEntrys];
        });
    }

    const syncAvatarDeliveryWithLocalDb = async () => {
        const result = await allQuery("SELECT * FROM `avatar_delivery` WHERE account = ?", [publicKey]);
        for(let i = 0; i < result.length; i++){
            let publicKey = result[i].public_key;
            setDeliveredAvatars((prev) => [...prev, publicKey]);
        }
    }

    const loadAvatarsFromCacheByPublicKey = async (publicKey : string, allDecode : boolean = true) => {
        if(loadCacheRunningRef.current.indexOf(publicKey) !== -1){
            return;
        }
        loadCacheRunningRef.current.push(publicKey);
        const result = await allQuery("SELECT * FROM `avatar_cache` WHERE public_key = ? ORDER BY timestamp DESC", [publicKey]);
        if(result.length == 0){
            loadCacheRunningRef.current = loadCacheRunningRef.current.filter(pk => pk !== publicKey);
            return;
        }
        if(
            decodedAvatarsCache.find(e => e.publicKey === publicKey) &&
            (decodedAvatarsCache.find(e => e.publicKey === publicKey)?.avatars.length == result.length || !allDecode)
        ){
            loadCacheRunningRef.current = loadCacheRunningRef.current.filter(pk => pk !== publicKey);
            return;
        }
        let avatars : AvatarInformation[] = [];
        for(let i = 0; i < result.length; i++){
            let file = await readFile(result[i].avatar);
            if(!file){
                error("Avatar file not found: " + result[i].avatar);
                await runQuery("DELETE FROM `avatar_cache` WHERE avatar = ?", [result[i].avatar]);
                continue;
            }
            let decodedAvatar = "";
            try{
                decodedAvatar = await decodeWithPassword(AVATAR_PASSWORD_TO_ENCODE, 
                    Buffer.from(file, 'binary').toString()
                );
            }catch(e){
                error("Failed to decode avatar from file: " + result[i].avatar);
                await runQuery("DELETE FROM `avatar_cache` WHERE avatar = ?", [result[i].avatar]);
                continue;
            }
            avatars.push({
                avatar: decodedAvatar,
                timestamp: result[i].timestamp,
            });
            if(!allDecode){
                break;
            }
        }
        setDecodedAvatarsCache((prev) => {
            const existingEntry = prev.find(e => e.publicKey === publicKey);
            if(existingEntry){
                let nextState = prev.map(e => {
                    if(e.publicKey === publicKey){
                        return {
                            publicKey: publicKey,
                            avatars: avatars
                        }
                    }
                    return e;
                });
                return [...nextState]; 
            } else {
                return [...prev, {
                    publicKey: publicKey,
                    avatars: avatars
                }];
            }
        });
        loadCacheRunningRef.current = loadCacheRunningRef.current.filter(pk => pk !== publicKey);
    }

    /**
     * Change the avatar for a specific entity
     * @param base64Image The base64 encoded image
     * @param entity The entity to change the avatar for (groupId or publicKey)
     */
    const changeAvatar = async (base64Image : string, entity : string) => {
        const timestamp = Date.now();
        const avatarPath = `a/${await generateMd5(base64Image + entity)}`;
        const encodedForStorage = await encodeWithPassword(AVATAR_PASSWORD_TO_ENCODE, base64Image);
        await writeFile(avatarPath, Buffer.from(encodedForStorage, 'binary'));
        await runQuery("INSERT INTO `avatar_cache` (public_key, avatar, timestamp) VALUES (?, ?, ?)", 
            [entity, avatarPath, timestamp]);

        setDecodedAvatarsCache((prev) => {
            const existingEntry = prev.find(e => e.publicKey === entity);
            if(existingEntry){
                let nextState = prev.map(e => {
                    if(e.publicKey === entity){
                        return {
                            publicKey: entity,
                            avatars: [{
                                avatar: base64Image,
                                timestamp,
                            }, ...e.avatars]
                        }
                    }
                    return e;
                });
                return [...nextState]; 
            } else {
                return [...prev, {
                    publicKey: entity,
                    avatars: [{
                        avatar: base64Image,
                        timestamp,
                    }]
                }];
            }
        });
    }

    return (
        <AvatarContext.Provider value={{
            deliveredAvatars,
            saveAvatar,
            loadAvatarsFromCacheByPublicKey,
            changeAvatar,
            decodedAvatarsCache
        }}>
            {props.children}
        </AvatarContext.Provider>
    )
}