// API Client for communicating with the backend

class ApiClient {
    constructor() {
        // Auto-detect server URL based on current location
        const currentHost = window.location.hostname;
        const currentPort = window.location.port;
        
        // If accessing from localhost, use localhost:3001
        // If accessing from another device, use the same hostname but port 3001
        if (currentHost === 'localhost' || currentHost === '127.0.0.1') {
            this.baseUrl = 'http://localhost:3001';
        } else {
            // Use the same hostname but port 3001
            this.baseUrl = `http://${currentHost}:3001`;
        }
        
        logger.debug('API Client initialized with base URL:', this.baseUrl);
    }

    async request(path, { method = 'GET', headers = {}, body = undefined, query = undefined, isForm = false } = {}) {
        const url = new URL(`${this.baseUrl}${path}`);
        if (query && typeof query === 'object') {
            Object.entries(query).forEach(([k, v]) => {
                if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
            });
        }
        const finalHeaders = { 'Accept': 'application/json', ...headers };
        const options = { method, headers: finalHeaders };
        if (body !== undefined) {
            if (isForm) {
                options.body = body;
            } else {
                options.headers['Content-Type'] = options.headers['Content-Type'] || 'application/json';
                options.body = typeof body === 'string' ? body : JSON.stringify(body);
            }
        }
        const response = await fetch(url.toString(), options);
        let data;
        const text = await response.text();
        try {
            data = text ? JSON.parse(text) : null;
        } catch (_) {
            data = text; // Non-JSON payload
        }
        if (!response.ok) {
            const message = (data && data.message) || `HTTP ${response.status}: ${response.statusText}`;
            throw new Error(message);
        }
        return data;
    }

    async getClusterMembers() {
        return this.request('/api/cluster/members', { method: 'GET' });
    }

    async getClusterMembersFromNode(ip) {
        return this.request(`/api/cluster/members`, { 
            method: 'GET',
            query: { ip: ip }
        });
    }

    async getDiscoveryInfo() {
        return this.request('/api/discovery/nodes', { method: 'GET' });
    }

    async selectRandomPrimaryNode() {
        return this.request('/api/discovery/random-primary', {
            method: 'POST',
            body: { timestamp: new Date().toISOString() }
        });
    }

    async getNodeStatus(ip) {
        return this.request(`/api/node/status/${encodeURIComponent(ip)}`, { method: 'GET' });
    }

    async getTasksStatus(ip) {
        return this.request('/api/tasks/status', { method: 'GET', query: ip ? { ip } : undefined });
    }

    async getEndpoints(ip) {
        return this.request('/api/node/endpoints', { method: 'GET', query: ip ? { ip } : undefined });
    }

    async callEndpoint({ ip, method, uri, params }) {
        return this.request('/api/proxy-call', {
            method: 'POST',
            body: { ip, method, uri, params }
        });
    }

    async uploadFirmware(file, nodeIp) {
        const formData = new FormData();
        formData.append('file', file);
        const data = await this.request(`/api/node/update`, {
            method: 'POST',
            query: { ip: nodeIp },
            body: formData,
            isForm: true,
            headers: {},
        });
        // Some endpoints may return HTTP 200 with success=false on logical failure
        if (data && data.success === false) {
            const message = data.message || 'Firmware upload failed';
            throw new Error(message);
        }
        return data;
    }

    async getMonitoringResources(ip) {
        return this.request('/api/proxy-call', {
            method: 'POST',
            body: { 
                ip: ip, 
                method: 'GET', 
                uri: '/api/monitoring/resources',
                params: []
            }
        });
    }

    async getNodeLabels(ip) {
        return this.request(`/api/node/status/${encodeURIComponent(ip)}`, { method: 'GET' });
    }

    async setNodeLabels(ip, labels) {
        return this.request('/api/proxy-call', {
            method: 'POST',
            body: { 
                ip: ip, 
                method: 'POST', 
                uri: '/api/node/config',
                params: [{ name: 'labels', value: JSON.stringify(labels) }]
            }
        });
    }

    // Registry API methods - now proxied through gateway
    async getRegistryHealth() {
        return this.request('/api/registry/health', { method: 'GET' });
    }

    async uploadFirmwareToRegistry(metadata, firmwareFile) {
        const formData = new FormData();
        formData.append('metadata', JSON.stringify(metadata));
        formData.append('firmware', firmwareFile);
        
        return this.request('/api/registry/firmware', {
            method: 'POST',
            body: formData,
            isForm: true,
            headers: {}
        });
    }

    async updateFirmwareMetadata(name, version, metadata) {
        return this.request(`/api/registry/firmware/${encodeURIComponent(name)}/${encodeURIComponent(version)}`, {
            method: 'PUT',
            body: metadata
        });
    }

    async listFirmwareFromRegistry(name = null, version = null) {
        const query = {};
        if (name) query.name = name;
        if (version) query.version = version;
        
        const queryString = Object.keys(query).length ? '?' + new URLSearchParams(query).toString() : '';
        return this.request(`/api/registry/firmware${queryString}`, { method: 'GET' });
    }

    async downloadFirmwareFromRegistry(name, version) {
        const response = await fetch(`${this.baseURL}/api/registry/firmware/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
        
        if (!response.ok) {
            const errorText = await response.text();
            throw new Error(`Registry download failed: ${errorText}`);
        }
        
        return response.blob();
    }

    async deleteFirmwareFromRegistry(name, version) {
        return this.request(`/api/registry/firmware/${encodeURIComponent(name)}/${encodeURIComponent(version)}`, {
            method: 'DELETE'
        });
    }

    // Rollout API methods
    async getClusterNodeVersions() {
        return this.request('/api/cluster/node/versions', { method: 'GET' });
    }

    async startRollout(rolloutData) {
        return this.request('/api/rollout', {
            method: 'POST',
            body: rolloutData
        });
    }
}

// Global API client instance
window.apiClient = new ApiClient();

// WebSocket Client for real-time updates
class WebSocketClient {
    constructor() {
        this.ws = null;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;
        this.reconnectDelay = 1000; // Start with 1 second
        this.listeners = new Map();
        this.isConnected = false;

        // Auto-detect WebSocket URL based on current location
        const currentHost = window.location.hostname;
        const currentPort = window.location.port;

        // Use ws:// for HTTP and wss:// for HTTPS
        const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';

        if (currentHost === 'localhost' || currentHost === '127.0.0.1') {
            this.wsUrl = `${wsProtocol}//localhost:3001/ws`;
        } else {
            this.wsUrl = `${wsProtocol}//${currentHost}:3001/ws`;
        }

        logger.debug('WebSocket Client initialized with URL:', this.wsUrl);
        this.connect();
    }

    connect() {
        try {
            this.ws = new WebSocket(this.wsUrl);
            this.setupEventListeners();
        } catch (error) {
            logger.error('Failed to create WebSocket connection:', error);
            this.scheduleReconnect();
        }
    }

    setupEventListeners() {
        this.ws.onopen = () => {
            logger.debug('WebSocket connected');
            this.isConnected = true;
            this.reconnectAttempts = 0;
            this.reconnectDelay = 1000;

            // Notify listeners of connection
            this.emit('connected');
        };

        this.ws.onmessage = (event) => {
            try {
                const data = JSON.parse(event.data);
                logger.debug('WebSocket message received:', data);
                logger.debug('WebSocket message type:', data.type);
                this.emit('message', data);
                this.handleMessage(data);
            } catch (error) {
                logger.error('Failed to parse WebSocket message:', error);
            }
        };

        this.ws.onclose = (event) => {
            logger.debug('WebSocket disconnected:', event.code, event.reason);
            this.isConnected = false;
            this.emit('disconnected');

            if (event.code !== 1000) { // Not a normal closure
                this.scheduleReconnect();
            }
        };

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

    handleMessage(data) {
        switch (data.type) {
            case 'cluster_update':
                this.emit('clusterUpdate', data);
                break;
            case 'node_discovery':
                this.emit('nodeDiscovery', data);
                break;
            case 'firmware_upload_status':
                this.emit('firmwareUploadStatus', data);
                break;
            case 'rollout_progress':
                this.emit('rolloutProgress', data);
                break;
            default:
                logger.debug('Unknown WebSocket message type:', data.type);
        }
    }

    scheduleReconnect() {
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
            logger.error('Max reconnection attempts reached');
            this.emit('maxReconnectAttemptsReached');
            return;
        }

        this.reconnectAttempts++;
        const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1); // Exponential backoff

        logger.debug(`Scheduling WebSocket reconnect attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);

        setTimeout(() => {
            this.connect();
        }, delay);
    }

    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
    }

    off(event, callback) {
        if (this.listeners.has(event)) {
            const callbacks = this.listeners.get(event);
            const index = callbacks.indexOf(callback);
            if (index > -1) {
                callbacks.splice(index, 1);
            }
        }
    }

    emit(event, ...args) {
        if (this.listeners.has(event)) {
            this.listeners.get(event).forEach(callback => {
                try {
                    callback(...args);
                } catch (error) {
                    logger.error('Error in WebSocket event listener:', error);
                }
            });
        }
    }

    send(data) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(data));
        } else {
            logger.warn('WebSocket not connected, cannot send data');
        }
    }

    disconnect() {
        if (this.ws) {
            this.ws.close(1000, 'Client disconnect');
        }
    }

    getConnectionStatus() {
        return {
            connected: this.isConnected,
            reconnectAttempts: this.reconnectAttempts,
            maxReconnectAttempts: this.maxReconnectAttempts,
            url: this.wsUrl
        };
    }
}

// Global WebSocket client instance
window.wsClient = new WebSocketClient(); 