import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
import { toast } from 'sonner';

export type PrinterType = 'bluetooth' | 'usb' | null;
export type PaperSize = '58' | '80';

interface PrinterState {
    device: any;
    type: PrinterType;
    isConnected: boolean;
    name: string | null;
    paperSize: PaperSize;
}

interface PrinterContextType extends PrinterState {
    connectBluetooth: () => Promise<void>;
    connectUsb: () => Promise<void>;
    disconnect: () => Promise<void>;
    sendData: (data: Uint8Array) => Promise<void>;
    setPaperSize: (size: PaperSize) => void;
}

const PrinterContext = createContext<PrinterContextType | undefined>(undefined);

async function findWriteCharacteristic(services: any[]) {
    for (const service of services) {
        const characteristics = await service.getCharacteristics();

        for (const char of characteristics) {
            if (char.properties.write || char.properties.writeWithoutResponse) {
                return char;
            }
        }
    }

    return null;
}

export function PrinterProvider({ children }: { children: ReactNode }) {
    const [state, setState] = useState<PrinterState>({
        device: null,
        type: null,
        isConnected: false,
        name: null,
        paperSize: (localStorage.getItem('printer_paper_size') as PaperSize) || '58',
    });

    const [characteristic, setCharacteristic] = useState<any>(null);

    // Save paper size to local storage
    useEffect(() => {
        localStorage.setItem('printer_paper_size', state.paperSize);
    }, [state.paperSize]);

    // USB disconnect listener
    useEffect(() => {
        const nav = navigator as any;
        if (!nav.usb) return;

        const handleUsbDisconnect = (event: any) => {
            // Check if the disconnected device matches our current device
            // We use vendorId and productId as a fallback if reference check fails
            const currentDevice = state.device;
            if (state.type === 'usb' && currentDevice && 
                (event.device === currentDevice || 
                 (event.device.vendorId === currentDevice.vendorId && 
                  event.device.productId === currentDevice.productId))) {
                
                setState(prev => ({ ...prev, device: null, type: null, isConnected: false, name: null }));
                toast.error('Printer USB terputus');
            }
        };

        nav.usb.addEventListener('disconnect', handleUsbDisconnect);
        return () => nav.usb.removeEventListener('disconnect', handleUsbDisconnect);
    }, [state.type, state.device]);

    // Bluetooth disconnect listener
    useEffect(() => {
        if (state.type === 'bluetooth' && state.device) {
            const device = state.device;
            const handleDisconnect = () => {
                setState(prev => ({ ...prev, device: null, type: null, isConnected: false, name: null }));
                setCharacteristic(null);
                toast.error('Printer Bluetooth terputus');
            };

            device.addEventListener('gattserverdisconnected', handleDisconnect);
            return () => device.removeEventListener('gattserverdisconnected', handleDisconnect);
        }
    }, [state.type, state.device]);

    // Auto-reconnect on mount
    useEffect(() => {
        const autoReconnect = async () => {
            const nav = navigator as any;
            const lastType = localStorage.getItem('last_printer_type');
            
            if (lastType === 'bluetooth' && nav.bluetooth?.getDevices) {
                try {
                    const devices = await nav.bluetooth.getDevices();
                    if (devices.length > 0) {
                        const device = devices[0];
                        const server = await device.gatt?.connect();
                        const services = await server.getPrimaryServices();
                        const writeChar = await findWriteCharacteristic(services);
                        if (writeChar) {
                            setCharacteristic(writeChar);
                            setState(prev => ({
                                ...prev,
                                device,
                                type: 'bluetooth',
                                isConnected: true,
                                name: device.name || 'Bluetooth Printer',
                            }));
                        }
                    }
                } catch (e) {
                    console.error('Auto-reconnect bluetooth failed:', e);
                }
            } else if (lastType === 'usb' && nav.usb?.getDevices) {
                try {
                    const devices = await nav.usb.getDevices();
                    if (devices.length > 0) {
                        const device = devices[0];
                        await device.open();
                        if (device.configuration === null) await device.selectConfiguration(1);
                        const iface = device.configuration.interfaces[0];
                        await device.claimInterface(iface.interfaceNumber);
                        
                        setState(prev => ({
                            ...prev,
                            device,
                            type: 'usb',
                            isConnected: true,
                            name: device.productName || 'USB Printer',
                        }));
                    }
                } catch (e) {
                    console.error('Auto-reconnect usb failed:', e);
                }
            }
        };

        autoReconnect();
    }, []);

    const connectBluetooth = useCallback(async () => {
        try {
            const nav = navigator as any;

            if (!nav.bluetooth) {
                toast.error('Bluetooth tidak didukung di browser ini');

                return;
            }

            const device = await nav.bluetooth.requestDevice({
                acceptAllDevices: true,
                optionalServices: [
                    '000018f0-0000-1000-8000-00805f9b34fb', 
                    '49535343-fe7d-41aa-83b1-d10935904914', 
                    '0000ff00-0000-1000-8000-00805f9b34fb',
                    '00001101-0000-1000-8000-00805f9b34fb' 
                ]
            });

            const server = await device.gatt?.connect();
            const services = await server.getPrimaryServices();
            
            const writeChar = await findWriteCharacteristic(services);

            if (!writeChar) {
                throw new Error('Tidak dapat menemukan characteristic untuk menulis data');
            }

            setCharacteristic(writeChar);
            setState(prev => ({
                ...prev,
                device,
                type: 'bluetooth',
                isConnected: true,
                name: device.name || 'Bluetooth Printer',
            }));

            localStorage.setItem('last_printer_type', 'bluetooth');
            toast.success(`Terhubung ke ${device.name || 'Printer Bluetooth'}`);

        } catch (error: any) {
            console.error('Bluetooth connection error:', error);

            if (error.name !== 'NotFoundError') {
                toast.error(error.message || 'Gagal menghubungkan printer bluetooth');
            }
        }
    }, []);

    const connectUsb = useCallback(async () => {
        try {
            const nav = navigator as any;

            if (!nav.usb) {
                toast.error('USB tidak didukung di browser ini');

                return;
            }

            const device = await nav.usb.requestDevice({
                filters: []
            });

            await device.open();

            if (device.configuration === null) {
                await device.selectConfiguration(1);
            }
            
            const iface = device.configuration.interfaces[0];
            await device.claimInterface(iface.interfaceNumber);
            
            setState(prev => ({
                ...prev,
                device,
                type: 'usb',
                isConnected: true,
                name: device.productName || 'USB Printer',
            }));

            localStorage.setItem('last_printer_type', 'usb');
            toast.success(`Terhubung ke ${device.productName || 'Printer USB'}`);

        } catch (error: any) {
            console.error('USB connection error:', error);

            if (error.name !== 'NotFoundError') {
                toast.error(error.message || 'Gagal menghubungkan printer USB');
            }
        }
    }, []);

    const setPaperSize = useCallback((size: PaperSize) => {
        setState(prev => ({ ...prev, paperSize: size }));
    }, []);

    const sendData = useCallback(async (data: Uint8Array) => {
        if (!state.isConnected || !state.device) {
            toast.error('Printer belum terhubung');

            return;
        }

        try {
            if (state.type === 'bluetooth') {
                const device = state.device;
                let activeChar = characteristic;
                
                // Try to reconnect if GATT is disconnected
                if (!device.gatt?.connected) {
                    toast.loading('Menghubungkan kembali...', { id: 'printer-reconnect' });
                    try {
                        const server = await device.gatt?.connect();
                        const services = await server.getPrimaryServices();
                        const writeChar = await findWriteCharacteristic(services);

                        if (writeChar) {
                            setCharacteristic(writeChar);
                            activeChar = writeChar;
                            toast.success('Printer terhubung kembali', { id: 'printer-reconnect' });
                        } else {
                            throw new Error('Gagal menemukan characteristic');
                        }
                    } catch (e) {
                        toast.error('Printer terputus', { id: 'printer-reconnect' });
                        setState(prev => ({ ...prev, device: null, type: null, isConnected: false, name: null }));
                        setCharacteristic(null);

                        return;
                    }
                }

                if (!activeChar) {
                    throw new Error('Characteristic tidak ditemukan');
                }

                // Bluetooth printers often have small buffers and limited MTU.
                // 20 bytes is the standard BLE MTU.
                const chunkSize = 20; 

                for (let i = 0; i < data.length; i += chunkSize) {
                    const chunk = data.slice(i, i + chunkSize);

                    // Prefer writeValue (with response) for better reliability
                    // it acts as a primitive flow control
                    await activeChar.writeValue(chunk);
                    
                    // Add a tiny delay between chunks if the data is large to avoid buffer overflow
                    // especially for cheaper printers
                    if (i % 100 === 0 && i > 0) {
                        await new Promise(resolve => setTimeout(resolve, 50));
                    }
                }
            } else if (state.type === 'usb') {
                const device = state.device as any;
                
                try {
                    // Check if device is still open
                    if (!device.opened) {
                        await device.open();
                    }

                    const endpoint = device.configuration.interfaces[0].alternates[0].endpoints.find((e: any) => e.direction === 'out' && e.type === 'bulk');

                    if (!endpoint) {
                        throw new Error('Tidak dapat menemukan USB endpoint');
                    }
                    
                    await device.transferOut(endpoint.endpointNumber, data);
                } catch (e) {
                    console.error('USB print error:', e);
                    setState(prev => ({ ...prev, device: null, type: null, isConnected: false, name: null }));
                    toast.error('Printer USB terputus');

                    return;
                }
            }
        } catch (error: any) {
            console.error('Print error:', error);
            toast.error('Gagal mengirim data ke printer: ' + (error.message || 'Error tidak diketahui'));
        }
    }, [state.isConnected, state.device, state.type, characteristic]);

    const disconnect = useCallback(async () => {
        if (state.type === 'bluetooth' && state.device) {
            if (state.device.gatt?.connected) {
                state.device.gatt.disconnect();
            }
        } else if (state.type === 'usb' && state.device) {
            try {
                await state.device.close();
            } catch (e) {
                console.error('Error closing USB device:', e);
            }
        }

        localStorage.removeItem('last_printer_type');
        setState(prev => ({ ...prev, device: null, type: null, isConnected: false, name: null }));
        setCharacteristic(null);
        toast.info('Printer terputus');
    }, [state.device, state.type]);

    return (
        <PrinterContext.Provider value={{
            ...state,
            connectBluetooth,
            connectUsb,
            disconnect,
            sendData,
            setPaperSize,
        }}>
            {children}
        </PrinterContext.Provider>
    );
}

export function usePrinterContext() {
    const context = useContext(PrinterContext);
    if (context === undefined) {
        throw new Error('usePrinterContext must be used within a PrinterProvider');
    }
    return context;
}
