import { createContext, useEffect, useRef, useState } from "react";
import { PacketRequestUpdate } from "../ProtocolProvider/protocol/packets/packet.requestupdate";
import { useSender } from "../ProtocolProvider/useSender";
import { usePacket } from "../ProtocolProvider/usePacket";
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
import { useFileStorage } from "@/app/hooks/useFileStorage";
import { APP_VERSION } from "@/app/version";
import { useCore } from "@/app/hooks/useCore";

export interface UpdateProviderProps {
    children: React.ReactNode;
}

export enum UpdateStatus {
    DOWNLOADING,
    COMPILE,
    READY_FOR_RESTART,
    NO_UPDATES,
    KERNEL_UPDATE_NEED,
    APP_UPDATE_AVAILABLE
}

/**
 * Ответ от сервера обновлений на запрос наличия обновлений. Содержит информацию о том, какие обновления есть, и ссылки на них
 */
export interface UpdateServerResponse {
    version: string;
    platform: string;
    arch: string;
    kernel_update_required: boolean;
    service_pack_url: string;
    kernel_url: string;
}

export interface UpdateContextValue {
    updateStatus: UpdateStatus;
    downloadProgress: number;
    kernelUpdateUrl: string;
    checkForUpdates: () => void;
    downloadLastApplicationUpdate: () => void;
    restartAppForUpdateApply: () => void;
    updateServer: string | null;
}

export const UpdateProviderContext = createContext<UpdateContextValue | null>(null);

/**
 * Провайдер для управления обновлениями приложения. При инициализации 
 * запрашвиает сервер обновлений с основного сервера и получает актуальные версии с сервера обновлений
 */
export function UpdateProvider(props: UpdateProviderProps) {
    const send = useSender();
    const {info, error} = useConsoleLogger('UpdateProvider');
    const updateServerRef = useRef<string | null>(null);
    const [updateStatus, setUpdateStatus] = useState<UpdateStatus>(UpdateStatus.NO_UPDATES);
    const [downloadProgress, setDownloadProgress] = useState<number>(0);
    const [kernelUpdateUrl, setKernelUpdateUrl] = useState<string>("");
    const [appUpdateUrl, setAppUpdateUrl] = useState<string>("");
    const [appActualVersion, setAppActualVersion] = useState<string>("");
    const {writeFile} = useFileStorage();
    const {getCoreVersion, getArch, getPlatform} = useCore();

    useEffect(() => {
        let packet = new PacketRequestUpdate();
        /**
         * Клиент хочет получить сервер обновлений с основного сервера
         */
        packet.setUpdateServer("");
        send(packet);
    }, []);

    usePacket(0xA, (packet : PacketRequestUpdate) => {
        updateServerRef.current = packet.getUpdateServer();
        info(`Update server ${updateServerRef.current}`);
        checkForUpdates();
    }, []);

    const checkForUpdates = async () => {
        const coreVersion = await getCoreVersion();
        const arch = await getArch();
        const platform = await getPlatform();
        if(updateServerRef.current == null){
            /**
             * SDU еще не определен
             */
            return;
        }
        /**
         * Запрашиваем обновления с SDU сервера
         */
        let response = await fetch
            (`${updateServerRef.current}/updates/get?app=${APP_VERSION}&kernel=${coreVersion}&arch=${arch}&platform=${platform}`).catch((e) => {
                error("Failed to check for updates: " + e.message);
            });
        if(!response || response.status != 200){
            error("Failed to check for updates, SDU unavailable");
            return;
        }
        let updateInfo : UpdateServerResponse = await response.json();
        console.info("Update info: ", updateInfo);
        if(updateInfo.kernel_update_required){
            /**
             * Чтобы дальше получать обновления приложения нужно сначала обновить ядро
             */
            setUpdateStatus(UpdateStatus.KERNEL_UPDATE_NEED);
            setKernelUpdateUrl(updateInfo.kernel_url);
            info("Kernel update needed for next application updates");
            return;
        }
        if(updateInfo.service_pack_url != null){
            /**
             * Доступно обновление приложения, которое можно скачать и установить
             */
            setUpdateStatus(UpdateStatus.APP_UPDATE_AVAILABLE);
            setAppUpdateUrl(updateInfo.service_pack_url);
            setAppActualVersion(updateInfo.version);
            info("Application update available");
            return;
        }
    }

    const downloadLastApplicationUpdate = async () => {
        if(updateStatus != UpdateStatus.APP_UPDATE_AVAILABLE){
            return;
        }
        setUpdateStatus(UpdateStatus.DOWNLOADING);
        const xhr = new XMLHttpRequest();
        xhr.open("GET", updateServerRef.current + appUpdateUrl, true);
        xhr.responseType = "blob";
        
        xhr.onprogress = (event) => {
            if (event.lengthComputable) {
                const percentComplete = (event.loaded / event.total) * 100;
                setDownloadProgress(Math.round(percentComplete));
            }
        };

        xhr.onload = async () => {
            if (xhr.status === 200) {
                setUpdateStatus(UpdateStatus.COMPILE);
                const blob : Blob = xhr.response;
                let bundleName = `bundle ${appActualVersion}.zip`;
                await writeFile(bundleName, Buffer.from(await blob.arrayBuffer()));
                info("Update downloaded, starting compiler...");
                await window.electron.ipcRenderer.invoke('update:installServiceUpdate', bundleName);
                info("Update compiled successfully.");
                setTimeout(() => {
                    setUpdateStatus(UpdateStatus.READY_FOR_RESTART);
                }, 10000);
            }
        }
        
        xhr.onerror = () => {
            error("Error downloading update");
            setUpdateStatus(UpdateStatus.APP_UPDATE_AVAILABLE);
        }

        xhr.send();
    }

    const restartAppForUpdateApply = () => {
        if(updateStatus != UpdateStatus.READY_FOR_RESTART){
            return;
        }
        window.electron.ipcRenderer.invoke('update:restartApp');
    }    

    return (
        <UpdateProviderContext.Provider value={{
            updateStatus,
            downloadProgress,
            kernelUpdateUrl,
            checkForUpdates,
            downloadLastApplicationUpdate, 
            restartAppForUpdateApply,
            updateServer: updateServerRef.current 
        }}>
            {props.children}
        </UpdateProviderContext.Provider>
    );
}