/**
 * Home Assistant API Integration
 * Handles communication with Home Assistant via WebSocket API
 * Supports both custom panel mode (using hass object) and iframe mode (WebSocket)
 */

class HomeAssistantAPI {
    constructor() {
        this.connection = null;
        this.messageId = 1;
        this.pendingRequests = new Map();
        this.stateUpdateCallbacks = [];
        this.hass = null; // For custom panel mode
        this.usingHassObject = false; // Track which mode we're in
    }
    
    /**
     * Set hass object (custom panel mode)
     */
    setHassObject(hass) {
        this.hass = hass;
        this.usingHassObject = true;
    }
    
    async connect() {
        // If we already have a hass object, we're in custom panel mode
        if (this.hass && this.usingHassObject) {
            console.log('Using existing hass connection from custom panel');
            return Promise.resolve();
        }
        
        try {
            // Get auth token - works in both browser and mobile app
            const authToken = await this.getAuthToken();
            
            // Connect to Home Assistant WebSocket API
            // Use relative path for mobile app compatibility
            const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
            const host = window.location.host;
            const wsUrl = `${protocol}//${host}/api/websocket`;
            
            this.connection = new WebSocket(wsUrl);
            
            return new Promise((resolve, reject) => {
                this.connection.onopen = () => {
                    console.log('WebSocket connected');
                };
                
                this.connection.onmessage = (event) => {
                    const message = JSON.parse(event.data);
                    this.handleMessage(message, authToken, resolve, reject);
                };
                
                this.connection.onerror = (error) => {
                    console.error('WebSocket error:', error);
                    reject(error);
                };
                
                this.connection.onclose = () => {
                    console.log('WebSocket disconnected');
                };
            });
        } catch (error) {
            console.error('Failed to connect:', error);
            throw error;
        }
    }
    
    handleMessage(message, authToken, resolveConnection, rejectConnection) {
        if (message.type === 'auth_required') {
            // Send authentication
            this.send({
                type: 'auth',
                access_token: authToken
            });
        } else if (message.type === 'auth_ok') {
            console.log('Authenticated successfully');
            resolveConnection();
        } else if (message.type === 'auth_invalid') {
            console.error('Authentication failed');
            rejectConnection(new Error('Authentication failed'));
        } else if (message.type === 'result') {
            // Handle response to our request
            const pending = this.pendingRequests.get(message.id);
            if (pending) {
                if (message.success) {
                    pending.resolve(message.result);
                } else {
                    pending.reject(message.error);
                }
                this.pendingRequests.delete(message.id);
            }
        } else if (message.type === 'event') {
            // Handle state change events
            if (message.event && message.event.event_type === 'state_changed') {
                this.notifyStateUpdate(message.event.data);
            }
        }
    }
    
    async getAuthToken() {
        // Method 1: Check if running in Home Assistant panel context (works in mobile app)
        if (window.hassConnection) {
            try {
                const auth = await window.hassConnection;
                if (auth && auth.auth && auth.auth.data && auth.auth.data.access_token) {
                    return auth.auth.data.access_token;
                }
            } catch (e) {
                console.warn('Could not get token from hassConnection:', e);
            }
        }
        
        // Method 2: Try to get from parent window context
        if (window.parent && window.parent.hassConnection && window.parent !== window) {
            try {
                const auth = await window.parent.hassConnection;
                if (auth && auth.auth && auth.auth.data && auth.auth.data.access_token) {
                    return auth.auth.data.access_token;
                }
            } catch (e) {
                console.warn('Could not get token from parent.hassConnection:', e);
            }
        }
        
        // Method 3: Fallback to localStorage (browser only)
        try {
            const token = localStorage.getItem('hassTokens');
            if (token) {
                const tokens = JSON.parse(token);
                if (tokens.access_token) {
                    return tokens.access_token;
                }
            }
        } catch (e) {
            console.warn('Could not get token from localStorage:', e);
        }
        
        throw new Error('No authentication token found. Please ensure the panel is loaded within Home Assistant.');
    }
    
    send(message) {
        if (this.connection && this.connection.readyState === WebSocket.OPEN) {
            this.connection.send(JSON.stringify(message));
        } else {
            throw new Error('WebSocket not connected');
        }
    }
    
    async sendRequest(message) {
        // If using hass object (custom panel mode), use hass.callWS
        if (this.hass && this.usingHassObject) {
            try {
                return await this.hass.callWS(message);
            } catch (error) {
                console.error('Error calling hass.callWS:', error);
                throw error;
            }
        }
        
        // Fallback to WebSocket mode
        const id = this.messageId++;
        const request = { id, ...message };
        
        return new Promise((resolve, reject) => {
            this.pendingRequests.set(id, { resolve, reject });
            this.send(request);
            
            // Timeout after 30 seconds
            setTimeout(() => {
                if (this.pendingRequests.has(id)) {
                    this.pendingRequests.delete(id);
                    reject(new Error('Request timeout'));
                }
            }, 30000);
        });
    }
    
    async getStates() {
        // Custom panel mode
        if (this.hass && this.usingHassObject) {
            return Object.values(this.hass.states);
        }
        // WebSocket mode
        return await this.sendRequest({ type: 'get_states' });
    }
    
    async getConfig() {
        // Custom panel mode
        if (this.hass && this.usingHassObject) {
            return this.hass.config;
        }
        // WebSocket mode
        return await this.sendRequest({ type: 'get_config' });
    }
    
    async getClimateEntities() {
        const states = await this.getStates();
        return states.filter(state => 
            state.entity_id.startsWith('climate.') && 
            !state.entity_id.startsWith('climate.climate_scheduler_')
        );
    }
    
    async callService(domain, service, serviceData, returnResponse = false) {
        // Custom panel mode
        if (this.hass && this.usingHassObject) {
            // Use callWS for return_response to avoid triggering haptic feedback
            if (returnResponse) {
                const result = await this.hass.callWS({
                    type: 'call_service',
                    domain,
                    service,
                    service_data: serviceData,
                    return_response: true
                });
                return result?.response;
            } else {
                // Use callService for non-return-response calls
                return await this.hass.callService(domain, service, serviceData);
            }
        }
        
        // WebSocket mode
        const requestData = {
            type: 'call_service',
            domain,
            service,
            service_data: serviceData
        };
        
        if (returnResponse) {
            requestData.return_response = true;
        }
        
        return await this.sendRequest(requestData);
    }
    
    async setLogLevel(level = 'debug') {
        return await this.callService('logger', 'set_level', {
            'custom_components.climate_scheduler': level
        });
    }
    
    async getSchedule(entityId, day = null) {
        try {
            const serviceData = {
                schedule_id: entityId
            };
            
            if (day) {
                serviceData.day = day;
            }
            
            // Call our custom service to get schedule with return_response
            const result = await this.callService('climate_scheduler', 'get_schedule', 
                serviceData, true);  // Pass true to enable return_response
            return result;
        } catch (error) {
            console.error('Failed to get schedule:', error);
            return null;
        }
    }
    
    async setSchedule(entityId, nodes, day = null, scheduleMode = null) {
        const serviceData = {
            schedule_id: entityId,
            nodes: nodes
        };
        
        if (day) {
            serviceData.day = day;
        }
        if (scheduleMode) {
            serviceData.schedule_mode = scheduleMode;
        }
        
        return await this.callService('climate_scheduler', 'set_schedule', serviceData);
    }
    
    async enableSchedule(entityId) {
        return await this.callService('climate_scheduler', 'enable_schedule', {
            schedule_id: entityId
        });
    }
    
    async disableSchedule(entityId) {
        return await this.callService('climate_scheduler', 'disable_schedule', {
            schedule_id: entityId
        });
    }
    
    async advanceSchedule(entityId) {
        return await this.callService('climate_scheduler', 'advance_schedule', {
            schedule_id: entityId
        });
    }
    
    async advanceGroup(groupName) {
        return await this.callService('climate_scheduler', 'advance_group', {
            schedule_id: groupName
        });
    }
    
    async cancelAdvance(entityId) {
        return await this.callService('climate_scheduler', 'cancel_advance', {
            schedule_id: entityId
        });
    }
    
    async getAdvanceStatus(entityId) {
        try {
            const result = await this.callService('climate_scheduler', 'get_advance_status', {
                schedule_id: entityId
            }, true);
            // Normalize across modes:
            // - hass.callWS path returns `result.response`
            // - websocket path may return `{response: ...}`
            return result?.response ?? result;
        } catch (error) {
            console.error('Failed to get advance status:', error);
            return { is_active: false, history: [] };
        }
    }
    
    async clearAdvanceHistory(entityId) {
        return await this.callService('climate_scheduler', 'clear_advance_history', {
            schedule_id: entityId
        });
    }
    
    async testFireEvent(groupName, node, day) {
        return await this.callService('climate_scheduler', 'test_fire_event', {
            schedule_id: groupName,
            node: JSON.stringify(node),
            day: day
        });
    }

    
    async getOverrideStatus(entityId) {
        try {
            const result = await this.callService('climate_scheduler', 'get_override_status', {
                schedule_id: entityId
            }, true);
            return result;
        } catch (error) {
            console.error('Failed to get override status:', error);
            return { has_override: false };
        }
    }
    
    async clearSchedule(entityId) {
        return await this.callService('climate_scheduler', 'clear_schedule', {
            schedule_id: entityId
        });
    }
    
    async setIgnored(entityId, ignored) {
        return await this.callService('climate_scheduler', 'set_ignored', {
            schedule_id: entityId,
            ignored: ignored
        });
    }
    
    // Group management methods
    async createGroup(groupName) {
        return await this.callService('climate_scheduler', 'create_group', {
            schedule_id: groupName
        });
    }
    
    async deleteGroup(groupName) {
        return await this.callService('climate_scheduler', 'delete_group', {
            schedule_id: groupName
        });
    }
    
    async renameGroup(oldName, newName) {
        return await this.callService('climate_scheduler', 'rename_group', {
            old_name: oldName,
            new_name: newName
        });
    }
    
    async addToGroup(groupName, entityId) {
        return await this.callService('climate_scheduler', 'add_to_group', {
            schedule_id: groupName,
            entity_id: entityId
        });
    }
    
    async removeFromGroup(groupName, entityId) {
        return await this.callService('climate_scheduler', 'remove_from_group', {
            schedule_id: groupName,
            entity_id: entityId
        });
    }
    
    async getGroups() {
        try {
            const result = await this.callService('climate_scheduler', 'get_groups', {}, true);
            return result;
        } catch (error) {
            console.error('Failed to get groups:', error);
            return { groups: {} };
        }
    }
    
    async setGroupSchedule(groupName, nodes, day = null, scheduleMode = null, profileName = null) {
        const callStartTime = performance.now();
        console.debug('[HA-API] setGroupSchedule called', {
            timestamp: new Date().toISOString(),
            groupName,
            nodeCount: nodes?.length,
            day,
            scheduleMode,
            profileName,
            usingHassObject: this.usingHassObject
        });
        
        // Guard: ensure a valid groupName is provided before calling HA service.
        if (!groupName) {
            const msg = 'setGroupSchedule called without a valid groupName';
            console.error('[HA-API]', msg, groupName, nodes, day, scheduleMode);
            // Throw so callers can handle the error rather than sending null to HA
            throw new Error(msg);
        }

        const serviceData = {
            schedule_id: groupName,
            nodes: nodes
        };
        
        if (day) {
            serviceData.day = day;
        }
        if (scheduleMode) {
            serviceData.schedule_mode = scheduleMode;
        }
        if (profileName) {
            serviceData.profile_name = profileName;
        }
        
        console.debug('[HA-API] Calling climate_scheduler.set_group_schedule', {
            serviceData: { ...serviceData, nodes: `[${nodes?.length} nodes]` },
            connectionMode: this.usingHassObject ? 'hass-object' : 'websocket'
        });
        
        try {
            const result = await this.callService('climate_scheduler', 'set_group_schedule', serviceData);
            console.debug('[HA-API] set_group_schedule succeeded', {
                duration: (performance.now() - callStartTime).toFixed(2) + 'ms',
                result
            });
            return result;
        } catch (error) {
            console.error('[HA-API] setGroupSchedule failed:', {
                error,
                errorCode: error?.code,
                errorMessage: error?.message,
                translationKey: error?.translation_key,
                translationPlaceholders: error?.translation_placeholders,
                groupName,
                duration: (performance.now() - callStartTime).toFixed(2) + 'ms',
                connectionMode: this.usingHassObject ? 'hass-object' : 'websocket'
            });
            throw error;
        }
    }
    
    async enableGroup(groupName) {
        return await this.callService('climate_scheduler', 'enable_group', {
            schedule_id: groupName
        });
    }
    
    async disableGroup(groupName) {
        return await this.callService('climate_scheduler', 'disable_group', {
            schedule_id: groupName
        });
    }
    
    async getHistory(entityId, startTime, endTime) {
        try {
            // Format times as ISO strings
            const start = startTime.toISOString();
            const end = endTime ? endTime.toISOString() : new Date().toISOString();
            
            // Use recorder history API
            const result = await this.sendRequest({
                type: 'history/history_during_period',
                start_time: start,
                end_time: end,
                entity_ids: [entityId],
                minimal_response: false,
                no_attributes: false
            });
            
            return result;
        } catch (error) {
            console.error('Failed to get history:', error);
            return null;
        }
    }
    
    async subscribeToStateChanges() {
        // Custom panel mode - hass object handles state updates automatically
        if (this.hass && this.usingHassObject) {
            // Set up listener for hass state changes
            // The panel will call updateHassConnection when hass changes
            console.log('Using hass object state updates');
            return Promise.resolve();
        }
        
        // WebSocket mode
        return await this.sendRequest({
            type: 'subscribe_events',
            event_type: 'state_changed'
        });
    }
    
    async getSettings() {
        try {
            const result = await this.callService('climate_scheduler', 'get_settings', {}, true);

            // Normalize across execution modes:
            // - In custom panel mode, callService(..., true) returns the service response directly.
            // - In raw websocket mode, callService may return { response: <service_response> }.
            const payload = result?.response ?? result ?? {};

            // Service response includes version metadata, but app.js expects the raw settings dict.
            // If the response shape is { settings: {...}, version: {...} }, return settings.
            if (
                payload &&
                typeof payload === 'object' &&
                payload.version &&
                payload.settings &&
                typeof payload.settings === 'object'
            ) {
                return payload.settings;
            }

            return payload;
        } catch (error) {
            console.error('Failed to get settings:', error);
            return {};
        }
    }
    
    async saveSettings(settings) {
        try {
            await this.callService('climate_scheduler', 'save_settings', { settings: JSON.stringify(settings) });
            return true;
        } catch (error) {
            console.error('Failed to save settings:', error);
            throw error;
        }
    }
    
    async cleanupDerivativeSensors(confirmDeleteAll = false) {
        try {
            const result = await this.callService('climate_scheduler', 'cleanup_derivative_sensors', {
                confirm_delete_all: confirmDeleteAll
            }, true);
            return result?.response || result || {};
        } catch (error) {
            console.error('Failed to cleanup derivative sensors:', error);
            throw error;
        }
    }
    
    async cleanupOrphanedClimateEntities(deleteEntities = false) {
        try {
            const result = await this.callService('climate_scheduler', 'cleanup_orphaned_climate_entities', {
                delete: deleteEntities
            }, true);
            return result?.response || result || {};
        } catch (error) {
            console.error('Failed to cleanup orphaned climate entities:', error);
            throw error;
        }
    }

    async cleanupUnmonitoredStorage(deleteEntries = false) {
        try {
            const result = await this.callService('climate_scheduler', 'cleanup_unmonitored_storage', {
                delete: deleteEntries
            }, true);
            return result?.response || result || {};
        } catch (error) {
            console.error('Failed to cleanup unmonitored storage:', error);
            throw error;
        }
    }
    
    // Profile management methods
    async createProfile(scheduleId, profileName) {
        return await this.callService('climate_scheduler', 'create_profile', {
            schedule_id: scheduleId,
            profile_name: profileName
        });
    }
    
    async deleteProfile(scheduleId, profileName) {
        return await this.callService('climate_scheduler', 'delete_profile', {
            schedule_id: scheduleId,
            profile_name: profileName
        });
    }
    
    async renameProfile(scheduleId, oldName, newName) {
        return await this.callService('climate_scheduler', 'rename_profile', {
            schedule_id: scheduleId,
            old_name: oldName,
            new_name: newName
        });
    }
    
    async setActiveProfile(scheduleId, profileName) {
        return await this.callService('climate_scheduler', 'set_active_profile', {
            schedule_id: scheduleId,
            profile_name: profileName
        });
    }
    
    async getProfiles(scheduleId) {
        try {
            const result = await this.callService('climate_scheduler', 'get_profiles', {
                schedule_id: scheduleId
            }, true);
            return result;
        } catch (error) {
            console.error('Failed to get profiles:', error);
            return { profiles: {}, active_profile: null };
        }
    }
    
    async runDiagnostics() {
        try {
            const result = await this.callService('climate_scheduler', 'diagnostics', {}, true);
            return result;
        } catch (error) {
            console.error('Failed to run diagnostics:', error);
            throw error;
        }
    }
    
    onStateUpdate(callback) {
        this.stateUpdateCallbacks.push(callback);
    }
    
    notifyStateUpdate(data) {
        this.stateUpdateCallbacks.forEach(callback => {
            try {
                callback(data);
            } catch (error) {
                console.error('Error in state update callback:', error);
            }
        });
    }
    
    async subscribeToEvents(eventType, callback) {
        if (this.usingHassObject && this.hass) {
            // Use hass connection for event subscription
            const conn = this.hass.connection;
            if (conn && conn.subscribeEvents) {
                return await conn.subscribeEvents(callback, eventType);
            }
        }
        console.warn('Event subscription not available');
        return null;
    }}