/**
 * WebSocket Manager
 * Handles real-time communication with the VNA backend
 */

export class WebSocketManager {
    constructor(config, notifications) {
        this.config = config;
        this.notifications = notifications;
        this.ws = null;
        this.isConnected = false;
        this.isConnecting = false;
        this.reconnectAttempts = 0;
        this.reconnectTimer = null;
        this.pingTimer = null;
        this.lastPing = null;
        this.eventListeners = new Map();

        this.messageQueue = [];
        this.maxQueueSize = 100;

        this.stats = {
            messagesReceived: 0,
            messagesPerSecond: 0,
            lastMessageTime: null,
            connectionTime: null,
            bytesReceived: 0
        };

        this.messageRateCounter = [];
        this.rateWindowMs = 1000;
    }

    async connect() {
        if (this.isConnected || this.isConnecting) {
            console.log('WebSocket already connected/connecting');
            return;
        }

        try {
            this.isConnecting = true;
            this.emit('connecting');

            console.log(` Connecting to WebSocket: ${this.config.url}`);

            this.ws = new WebSocket(this.config.url);
            this.setupWebSocketEvents();

            await this.waitForConnection(5000);
        } catch (error) {
            this.isConnecting = false;
            console.error('WebSocket connection failed:', error);
            this.handleConnectionError(error);
            throw error;
        }
    }

    waitForConnection(timeoutMs) {
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => reject(new Error('WebSocket connection timeout')), timeoutMs);

            const check = () => {
                if (this.isConnected) {
                    clearTimeout(timeout);
                    resolve();
                } else if (this.ws?.readyState === WebSocket.CLOSED || this.ws?.readyState === WebSocket.CLOSING) {
                    clearTimeout(timeout);
                    reject(new Error('WebSocket connection failed'));
                }
            };

            check();
            const interval = setInterval(() => {
                check();
                if (this.isConnected) clearInterval(interval);
            }, 100);

            const wrap = fn => (...args) => { clearInterval(interval); fn(...args); };
            resolve = wrap(resolve);
            reject = wrap(reject);
        });
    }

    setupWebSocketEvents() {
        if (!this.ws) return;

        this.ws.onopen = (event) => {
            console.log('WebSocket connected');
            this.isConnected = true;
            this.isConnecting = false;
            this.reconnectAttempts = 0;
            this.stats.connectionTime = new Date();

            this.startPingPong();
            this.processPendingMessages();
            this.emit('connected', event);

            this.notifications?.show?.({
                type: 'success',
                title: 'Подключено',
                message: 'Установлено соединение в реальном времени'
            });
        };

        this.ws.onmessage = (event) => {
            try {
                this.handleMessage(event.data);
            } catch (error) {
                console.error('Error processing WebSocket message:', error);
                this.notifications?.show?.({
                    type: 'error',
                    title: 'Ошибка сообщения',
                    message: 'Не удалось обработать полученные данные'
                });
            }
        };

        this.ws.onerror = (error) => {
            console.error('WebSocket error:', error);
            this.handleConnectionError(error);
        };

        this.ws.onclose = (event) => {
            console.log('WebSocket closed:', event.code, event.reason);
            this.handleDisconnection(event);
        };
    }

    handleMessage(data) {
        this.stats.messagesReceived++;
        this.stats.lastMessageTime = new Date();
        this.stats.bytesReceived += (typeof data === 'string' ? data.length : 0);
        this.updateMessageRate();

        try {
            if (typeof data !== 'string') {
                console.warn('Received non-text message, ignoring');
                return;
            }
            if (data === 'ping') { this.handlePing(); return; }
            if (data === 'pong') { this.handlePong(); return; }

            let payload;
            try {
                payload = JSON.parse(data);
            } catch (jsonError) {
                console.error('Invalid JSON format:', data);
                console.error('JSON parse error:', jsonError);
                this.notifications?.show?.({
                    type: 'error',
                    title: 'Ошибка разбора JSON',
                    message: `Не удалось разобрать JSON: ${data.substring(0, 200)}${data.length > 200 ? '...' : ''}`
                });
                return;
            }

            // Public "data" event for everything, plus more specific ones:
            this.emit('data', payload);

            switch (payload.type) {
                case 'processor_result':
                    this.emit('processor_result', payload);
                    break;
                case 'processor_history':
                    this.emit('processor_history', payload);
                    break;
                case 'processor_state':
                    this.emit('processor_state', payload);
                    break;
                case 'error':
                    console.error('Server error:', payload);
                    console.error('Error details:', {
                        message: payload.message,
                        code: payload.code,
                        details: payload.details,
                        timestamp: payload.timestamp,
                        source: payload.source
                    });
                    this.notifications?.show?.({
                        type: 'error',
                        title: 'Ошибка сервера',
                        message: `${payload.message}${payload.details ? ` — ${payload.details}` : ''}${payload.source ? ` (${payload.source})` : ''}`
                    });
                    break;
                default:
                    console.warn('Unknown payload type:', payload.type);
            }
        } catch (e) {
            console.error('Failed to parse WebSocket JSON:', e);
            console.log('Raw message:', data);
            this.notifications?.show?.({
                type: 'error',
                title: 'Ошибка обработки сообщения',
                message: `Не удалось обработать сообщение: ${data.substring(0, 200)}${data.length > 200 ? '...' : ''}`
            });
        }
    }

    updateMessageRate() {
        const now = Date.now();
        this.messageRateCounter.push(now);
        this.messageRateCounter = this.messageRateCounter.filter(t => now - t < this.rateWindowMs);
        this.stats.messagesPerSecond = this.messageRateCounter.length;
    }

    handlePing() {
        if (this.ws?.readyState === WebSocket.OPEN) this.ws.send('pong');
    }
    handlePong() {
        if (this.lastPing) {
            console.log(` WebSocket latency: ${Date.now() - this.lastPing}ms`);
            this.lastPing = null;
        }
    }
    startPingPong() {
        this.pingTimer = setInterval(() => {
            if (this.ws?.readyState === WebSocket.OPEN) {
                this.lastPing = Date.now();
                this.ws.send('ping');
            }
        }, 30000);
    }

    handleConnectionError() {
        if (!this.isConnected && this.isConnecting) {
            // Only show error notification during connection attempts, not after disconnection
            this.notifications?.show?.({
                type: 'error',
                title: 'Сбой подключения',
                message: 'Не удалось установить соединение в реальном времени'
            });
        }
    }

    handleDisconnection(event) {
        const wasConnected = this.isConnected;
        this.isConnected = false;
        this.isConnecting = false;

        if (this.pingTimer) { clearInterval(this.pingTimer); this.pingTimer = null; }

        this.emit('disconnected', event);

        if (wasConnected) {
            this.notifications?.show?.({
                type: 'warning',
                title: 'Отключено',
                message: 'Соединение потеряно. Пытаемся переподключиться...'
            });
            this.scheduleReconnect();
        }
    }

    scheduleReconnect() {
        if (this.reconnectTimer) return;
        const delay = Math.min(this.config.reconnectInterval * Math.pow(2, this.reconnectAttempts), 30000);
        console.log(` Scheduling reconnect in ${delay}ms (attempt ${this.reconnectAttempts + 1})`);
        this.reconnectTimer = setTimeout(() => {
            this.reconnectTimer = null;
            this.reconnect();
        }, delay);
    }

    async reconnect() {
        if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
            console.error('Max reconnection attempts reached');
            this.notifications?.show?.({
                type: 'error',
                title: 'Сбой подключения',
                message: 'Не удалось переподключиться после нескольких попыток'
            });
            return;
        }
        this.reconnectAttempts++;
        if (this.ws) { this.ws.close(); this.ws = null; }
        try { await this.connect(); }
        catch (error) { console.error(` Reconnection attempt ${this.reconnectAttempts} failed:`, error); this.scheduleReconnect(); }
    }

    send(data) {
        if (!this.isConnected || !this.ws) {
            if (this.messageQueue.length < this.maxQueueSize) {
                this.messageQueue.push(data);
                console.log('Message queued (not connected)');
            } else {
                console.warn('Message queue full, dropping message');
            }
            return false;
        }
        try {
            const msg = (typeof data === 'string') ? data : JSON.stringify(data);
            this.ws.send(msg);
            return true;
        } catch (e) {
            console.error('Failed to send message:', e);
            return false;
        }
    }

    processPendingMessages() {
        if (this.messageQueue.length === 0) return;
        console.log(` Processing ${this.messageQueue.length} queued messages`);
        const messages = [...this.messageQueue];
        this.messageQueue = [];
        messages.forEach(m => this.send(m));
    }

    // === PUBLIC API FOR BACKEND COMPATIBILITY ===

    /** Request recalculation with config update (EXISTS ON BACKEND) */
    recalculate(processorId, configUpdates = undefined) {
        return this.send({
            type: 'recalculate',
            processor_id: processorId,
            ...(configUpdates ? { config_updates: configUpdates } : {})
        });
    }

    /** Get processor results history (EXISTS ON BACKEND) */
    getHistory(processorId, limit = 10) {
        return this.send({
            type: 'get_history',
            processor_id: processorId,
            limit
        });
    }

    /** Get full processor state including sweep history (EXISTS ON BACKEND) */
    getProcessorState(processorId) {
        return this.send({
            type: 'get_processor_state',
            processor_id: processorId
        });
    }

    // === Events ===
    on(event, callback) {
        if (!this.eventListeners.has(event)) this.eventListeners.set(event, []);
        this.eventListeners.get(event).push(callback);
    }
    off(event, callback) {
        if (!this.eventListeners.has(event)) return;
        const arr = this.eventListeners.get(event);
        const i = arr.indexOf(callback);
        if (i > -1) arr.splice(i, 1);
    }
    emit(event, data) {
        if (!this.eventListeners.has(event)) return;
        this.eventListeners.get(event).forEach(cb => {
            try { cb(data); } catch (e) { console.error(` Error in event listener for ${event}:`, e); }
        });
    }

    disconnect() {
        console.log('Disconnecting WebSocket');
        if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; }
        if (this.pingTimer) { clearInterval(this.pingTimer); this.pingTimer = null; }
        if (this.ws) { this.ws.close(1000, 'Manual disconnect'); this.ws = null; }
        this.isConnected = false;
        this.isConnecting = false;
        this.reconnectAttempts = 0;
    }

    getStats() {
        return {
            ...this.stats,
            isConnected: this.isConnected,
            isConnecting: this.isConnecting,
            reconnectAttempts: this.reconnectAttempts,
            queuedMessages: this.messageQueue.length
        };
    }

    destroy() {
        this.disconnect();
        this.eventListeners.clear();
        this.messageQueue = [];
    }
}
