// Pose Service for WiFi-DensePose UI

import { API_CONFIG } from '../config/api.config.js';
import { apiService } from './api.service.js';
import { wsService } from './websocket.service.js';

export class PoseService {
  constructor() {
    this.streamConnection = null;
    this.eventConnection = null;
    this.poseSubscribers = [];
    this.eventSubscribers = [];
    this.connectionState = 'disconnected';
    this.lastPoseData = null;
    this.performanceMetrics = {
      messageCount: 0,
      errorCount: 0,
      lastUpdateTime: null,
      averageLatency: 0,
      droppedFrames: 0
    };
    this.validationErrors = [];
    this.logger = this.createLogger();

    // Model inference mode tracking
    this.modelActive = false;

    // Configuration
    this.config = {
      enableValidation: true,
      enablePerformanceTracking: true,
      maxValidationErrors: 10,
      confidenceThreshold: 0.3,
      confidenceThresholdModelInference: 0.15,
      maxPersons: 10,
      timeoutMs: 5000
    };
  }

  createLogger() {
    return {
      debug: (...args) => console.debug('[POSE-DEBUG]', new Date().toISOString(), ...args),
      info: (...args) => console.info('[POSE-INFO]', new Date().toISOString(), ...args),
      warn: (...args) => console.warn('[POSE-WARN]', new Date().toISOString(), ...args),
      error: (...args) => console.error('[POSE-ERROR]', new Date().toISOString(), ...args)
    };
  }

  // Get current pose estimation
  async getCurrentPose(options = {}) {
    const params = {
      zone_ids: options.zoneIds?.join(','),
      confidence_threshold: options.confidenceThreshold,
      max_persons: options.maxPersons,
      include_keypoints: options.includeKeypoints,
      include_segmentation: options.includeSegmentation
    };

    // Remove undefined values
    Object.keys(params).forEach(key => 
      params[key] === undefined && delete params[key]
    );

    return apiService.get(API_CONFIG.ENDPOINTS.POSE.CURRENT, params);
  }

  // Analyze pose (requires auth)
  async analyzePose(request) {
    return apiService.post(API_CONFIG.ENDPOINTS.POSE.ANALYZE, request);
  }

  // Get zone occupancy
  async getZoneOccupancy(zoneId) {
    const endpoint = API_CONFIG.ENDPOINTS.POSE.ZONE_OCCUPANCY.replace('{zone_id}', zoneId);
    return apiService.get(endpoint);
  }

  // Get zones summary
  async getZonesSummary() {
    return apiService.get(API_CONFIG.ENDPOINTS.POSE.ZONES_SUMMARY);
  }

  // Get historical data (requires auth)
  async getHistoricalData(request) {
    return apiService.post(API_CONFIG.ENDPOINTS.POSE.HISTORICAL, request);
  }

  // Get recent activities
  async getActivities(options = {}) {
    const params = {
      zone_id: options.zoneId,
      limit: options.limit || 50
    };

    // Remove undefined values
    Object.keys(params).forEach(key => 
      params[key] === undefined && delete params[key]
    );

    return apiService.get(API_CONFIG.ENDPOINTS.POSE.ACTIVITIES, params);
  }

  // Calibrate system (requires auth)
  async calibrate() {
    return apiService.post(API_CONFIG.ENDPOINTS.POSE.CALIBRATE);
  }

  // Get calibration status (requires auth)
  async getCalibrationStatus() {
    return apiService.get(API_CONFIG.ENDPOINTS.POSE.CALIBRATION_STATUS);
  }

  // Get pose statistics
  async getStats(hours = 24) {
    return apiService.get(API_CONFIG.ENDPOINTS.POSE.STATS, { hours });
  }

  // Start pose stream
  async startPoseStream(options = {}) {
    if (this.streamConnection) {
      this.logger.warn('Pose stream already active', { connectionId: this.streamConnection });
      return this.streamConnection;
    }

    this.logger.info('Starting pose stream', { options });
    this.resetPerformanceMetrics();

    // Validate options
    const validationResult = this.validateStreamOptions(options);
    if (!validationResult.valid) {
      throw new Error(`Invalid stream options: ${validationResult.errors.join(', ')}`);
    }

    // Use a lower confidence threshold when model inference is active
    const defaultThreshold = this.modelActive
      ? this.config.confidenceThresholdModelInference
      : this.config.confidenceThreshold;

    const params = {
      zone_ids: options.zoneIds?.join(','),
      min_confidence: options.minConfidence || defaultThreshold,
      max_fps: options.maxFps || 30,
      token: options.token || apiService.authToken
    };

    // Remove undefined values
    Object.keys(params).forEach(key => 
      params[key] === undefined && delete params[key]
    );

    try {
      this.connectionState = 'connecting';
      this.notifyConnectionState('connecting');

      this.streamConnection = await wsService.connect(
        API_CONFIG.ENDPOINTS.STREAM.WS_POSE,
        params,
        {
          onOpen: (event) => {
            this.logger.info('Pose stream connected successfully');
            this.connectionState = 'connected';
            this.notifyConnectionState('connected');
            this.notifyPoseSubscribers({ type: 'connected', event });
          },
          onMessage: (data) => {
            this.handlePoseMessage(data);
          },
          onError: (error) => {
            this.logger.error('Pose stream error occurred', { error });
            this.connectionState = 'error';
            this.performanceMetrics.errorCount++;
            this.notifyConnectionState('error', error);
            this.notifyPoseSubscribers({ type: 'error', error });
          },
          onClose: (event) => {
            this.logger.info('Pose stream disconnected', { event });
            this.connectionState = 'disconnected';
            this.streamConnection = null;
            this.notifyConnectionState('disconnected', event);
            this.notifyPoseSubscribers({ type: 'disconnected', event });
          }
        }
      );

      // Set up connection state monitoring
      if (this.streamConnection) {
        this.setupConnectionStateMonitoring();
      }

      this.logger.info('Pose stream initiated', { connectionId: this.streamConnection });
      return this.streamConnection;
    } catch (error) {
      this.logger.error('Failed to start pose stream', { error: error.message });
      this.connectionState = 'failed';
      this.notifyConnectionState('failed', error);
      throw error;
    }
  }

  validateStreamOptions(options) {
    const errors = [];

    if (options.zoneIds && !Array.isArray(options.zoneIds)) {
      errors.push('zoneIds must be an array');
    }

    if (options.minConfidence !== undefined) {
      if (typeof options.minConfidence !== 'number' || options.minConfidence < 0 || options.minConfidence > 1) {
        errors.push('minConfidence must be a number between 0 and 1');
      }
    }

    if (options.maxFps !== undefined) {
      if (typeof options.maxFps !== 'number' || options.maxFps <= 0 || options.maxFps > 60) {
        errors.push('maxFps must be a number between 1 and 60');
      }
    }

    return {
      valid: errors.length === 0,
      errors
    };
  }

  setupConnectionStateMonitoring() {
    if (!this.streamConnection) return;

    // Monitor connection state changes
    wsService.onConnectionStateChange(this.streamConnection, (state, data) => {
      this.logger.debug('WebSocket connection state changed', { state, data });
      this.connectionState = state;
      this.notifyConnectionState(state, data);
    });
  }

  notifyConnectionState(state, data = null) {
    this.logger.debug('Connection state notification', { state, data });
    this.notifyPoseSubscribers({ 
      type: 'connection_state', 
      state, 
      data,
      metrics: this.getPerformanceMetrics() 
    });
  }

  // Stop pose stream
  stopPoseStream() {
    if (this.streamConnection) {
      wsService.disconnect(this.streamConnection);
      this.streamConnection = null;
    }
  }

  // Subscribe to pose updates
  subscribeToPoseUpdates(callback) {
    this.poseSubscribers.push(callback);
    
    // Return unsubscribe function
    return () => {
      const index = this.poseSubscribers.indexOf(callback);
      if (index > -1) {
        this.poseSubscribers.splice(index, 1);
      }
    };
  }

  // Handle pose stream messages
  handlePoseMessage(data) {
    const startTime = performance.now();
    this.performanceMetrics.messageCount++;
    
    this.logger.debug('Received pose message', { 
      type: data.type, 
      messageCount: this.performanceMetrics.messageCount 
    });
    
    try {
      // Validate message structure
      if (this.config.enableValidation) {
        const validationResult = this.validatePoseMessage(data);
        if (!validationResult.valid) {
          this.addValidationError(`Invalid message structure: ${validationResult.errors.join(', ')}`);
          return;
        }
      }

      const { type, payload, data: messageData, zone_id, timestamp } = data;
      
      // Handle both payload (old format) and data (new format) properties
      const actualData = payload || messageData;

      // Update performance metrics
      if (this.config.enablePerformanceTracking) {
        this.updatePerformanceMetrics(startTime, timestamp);
      }

      switch (type) {
        case 'connection_established':
          this.logger.info('WebSocket connection established');
          this.notifyPoseSubscribers({
            type: 'connected',
            data: { status: 'connected' }
          });
          break;

        case 'pose_data':
          this.logger.debug('Processing pose data', { zone_id, hasData: !!actualData });
          
          // Validate pose data
          if (this.config.enableValidation && actualData) {
            const poseValidation = this.validatePoseData(actualData);
            if (!poseValidation.valid) {
              this.addValidationError(`Invalid pose data: ${poseValidation.errors.join(', ')}`);
              return;
            }
          }
          
          // Convert zone-based WebSocket format to REST API format
          const convertedData = this.convertZoneDataToRestFormat(actualData, zone_id, data);
          this.lastPoseData = convertedData;
          
          this.logger.debug('Converted pose data', { 
            personsCount: convertedData.persons?.length || 0,
            zones: Object.keys(convertedData.zone_summary || {})
          });
          
          this.notifyPoseSubscribers({
            type: 'pose_update',
            data: convertedData
          });
          break;

        case 'historical_data':
          this.logger.debug('Historical data received');
          this.notifyPoseSubscribers({
            type: 'historical_update',
            data: actualData
          });
          break;

        case 'zone_statistics':
          this.logger.debug('Zone statistics received');
          this.notifyPoseSubscribers({
            type: 'zone_stats',
            data: actualData
          });
          break;

        case 'system_event':
          this.logger.debug('System event received');
          this.notifyPoseSubscribers({
            type: 'system_event',
            data: actualData
          });
          break;

        case 'pong':
          // Handle heartbeat response
          this.logger.debug('Heartbeat response received');
          break;

        default:
          this.logger.warn('Unknown pose message type', { type, data });
          this.notifyPoseSubscribers({
            type: 'unknown_message',
            data: { originalType: type, originalData: data }
          });
      }
    } catch (error) {
      this.logger.error('Error handling pose message', { error: error.message, data });
      this.performanceMetrics.errorCount++;
      this.addValidationError(`Message handling error: ${error.message}`);
      
      this.notifyPoseSubscribers({
        type: 'error',
        error: error,
        data: { originalMessage: data }
      });
    }
  }

  validatePoseMessage(message) {
    const errors = [];

    if (!message || typeof message !== 'object') {
      errors.push('Message must be an object');
      return { valid: false, errors };
    }

    if (!message.type || typeof message.type !== 'string') {
      errors.push('Message must have a valid type string');
    }

    return {
      valid: errors.length === 0,
      errors
    };
  }

  validatePoseData(poseData) {
    const errors = [];

    if (!poseData || typeof poseData !== 'object') {
      errors.push('Pose data must be an object');
      return { valid: false, errors };
    }

    if (poseData.pose && poseData.pose.persons) {
      const persons = poseData.pose.persons;
      if (!Array.isArray(persons)) {
        errors.push('Persons must be an array');
      } else if (persons.length > this.config.maxPersons) {
        errors.push(`Too many persons detected (${persons.length} > ${this.config.maxPersons})`);
      }

      // Validate person data
      persons.forEach((person, index) => {
        if (!person || typeof person !== 'object') {
          errors.push(`Person ${index} must be an object`);
        } else {
          if (person.confidence !== undefined && 
              (typeof person.confidence !== 'number' || person.confidence < 0 || person.confidence > 1)) {
            errors.push(`Person ${index} confidence must be between 0 and 1`);
          }
        }
      });
    }

    return {
      valid: errors.length === 0,
      errors
    };
  }

  updatePerformanceMetrics(startTime, messageTimestamp) {
    const processingTime = performance.now() - startTime;
    this.performanceMetrics.lastUpdateTime = Date.now();

    // Calculate latency if timestamp is provided
    if (messageTimestamp) {
      const messageTime = new Date(messageTimestamp).getTime();
      const currentTime = Date.now();
      const latency = currentTime - messageTime;
      
      // Update average latency (simple moving average)
      if (this.performanceMetrics.averageLatency === 0) {
        this.performanceMetrics.averageLatency = latency;
      } else {
        this.performanceMetrics.averageLatency = 
          (this.performanceMetrics.averageLatency * 0.9) + (latency * 0.1);
      }
    }
  }

  addValidationError(error) {
    this.validationErrors.push({
      error,
      timestamp: Date.now(),
      messageCount: this.performanceMetrics.messageCount
    });

    // Keep only recent errors
    if (this.validationErrors.length > this.config.maxValidationErrors) {
      this.validationErrors = this.validationErrors.slice(-this.config.maxValidationErrors);
    }

    this.logger.warn('Validation error', { error });
  }

  resetPerformanceMetrics() {
    this.performanceMetrics = {
      messageCount: 0,
      errorCount: 0,
      lastUpdateTime: null,
      averageLatency: 0,
      droppedFrames: 0
    };
    this.validationErrors = [];
    this.logger.debug('Performance metrics reset');
  }

  getPerformanceMetrics() {
    return {
      ...this.performanceMetrics,
      validationErrors: this.validationErrors.length,
      connectionState: this.connectionState
    };
  }

  // Convert zone-based WebSocket data to REST API format
  convertZoneDataToRestFormat(zoneData, zoneId, originalMessage) {
    console.log('🔧 Converting zone data:', { zoneData, zoneId, originalMessage });
    
    if (!zoneData || !zoneData.pose) {
      console.log('⚠️ No pose data in zone data, returning empty result');
      return {
        timestamp: originalMessage.timestamp || new Date().toISOString(),
        frame_id: `ws_frame_${Date.now()}`,
        persons: [],
        zone_summary: {},
        processing_time_ms: 0,
        metadata: { mock_data: false, source: 'websocket' }
      };
    }

    // Determine the pose source for this message
    const poseSource = originalMessage.pose_source || zoneData.pose_source || null;

    // Choose confidence threshold based on pose source
    const threshold = (poseSource === 'model_inference' || this.modelActive)
      ? this.config.confidenceThresholdModelInference
      : this.config.confidenceThreshold;

    // Extract persons from zone data, applying source-aware filtering
    const rawPersons = zoneData.pose.persons || [];
    const persons = rawPersons.filter(p => p.confidence === undefined || p.confidence >= threshold);
    console.log('Extracted persons:', persons.length, '/', rawPersons.length, '(threshold:', threshold, ')');
    
    // Create zone summary
    const zoneSummary = {};
    if (zoneId && persons.length > 0) {
      zoneSummary[zoneId] = persons.length;
    }
    console.log('📍 Zone summary:', zoneSummary);

    const result = {
      timestamp: originalMessage.timestamp || new Date().toISOString(),
      frame_id: zoneData.metadata?.frame_id || `ws_frame_${Date.now()}`,
      persons: persons,
      zone_summary: zoneSummary,
      processing_time_ms: zoneData.metadata?.processing_time_ms || 0,
      pose_source: poseSource,
      metadata: {
        mock_data: false,
        source: 'websocket',
        zone_id: zoneId,
        confidence: zoneData.confidence,
        activity: zoneData.activity
      }
    };
    
    console.log('✅ Final converted result:', result);
    return result;
  }

  // Notify pose subscribers
  notifyPoseSubscribers(update) {
    this.poseSubscribers.forEach(callback => {
      try {
        callback(update);
      } catch (error) {
        console.error('Error in pose subscriber:', error);
      }
    });
  }

  // Start event stream
  startEventStream(options = {}) {
    if (this.eventConnection) {
      console.warn('Event stream already active');
      return this.eventConnection;
    }

    const params = {
      event_types: options.eventTypes?.join(','),
      zone_ids: options.zoneIds?.join(','),
      token: options.token || apiService.authToken
    };

    // Remove undefined values
    Object.keys(params).forEach(key => 
      params[key] === undefined && delete params[key]
    );

    this.eventConnection = wsService.connect(
      API_CONFIG.ENDPOINTS.STREAM.WS_EVENTS,
      params,
      {
        onOpen: () => {
          console.log('Event stream connected');
          this.notifyEventSubscribers({ type: 'connected' });
        },
        onMessage: (data) => {
          this.handleEventMessage(data);
        },
        onError: (error) => {
          console.error('Event stream error:', error);
          this.notifyEventSubscribers({ type: 'error', error });
        },
        onClose: () => {
          console.log('Event stream disconnected');
          this.eventConnection = null;
          this.notifyEventSubscribers({ type: 'disconnected' });
        }
      }
    );

    return this.eventConnection;
  }

  // Stop event stream
  stopEventStream() {
    if (this.eventConnection) {
      wsService.disconnect(this.eventConnection);
      this.eventConnection = null;
    }
  }

  // Subscribe to events
  subscribeToEvents(callback) {
    this.eventSubscribers.push(callback);
    
    // Return unsubscribe function
    return () => {
      const index = this.eventSubscribers.indexOf(callback);
      if (index > -1) {
        this.eventSubscribers.splice(index, 1);
      }
    };
  }

  // Handle event stream messages
  handleEventMessage(data) {
    this.notifyEventSubscribers({
      type: 'event',
      data
    });
  }

  // Notify event subscribers
  notifyEventSubscribers(update) {
    this.eventSubscribers.forEach(callback => {
      try {
        callback(update);
      } catch (error) {
        console.error('Error in event subscriber:', error);
      }
    });
  }

  // Update stream configuration
  updateStreamConfig(connectionId, config) {
    wsService.sendCommand(connectionId, 'update_config', config);
  }

  // Get stream status
  requestStreamStatus(connectionId) {
    wsService.sendCommand(connectionId, 'get_status');
  }

  // Utility methods
  getConnectionState() {
    return this.connectionState;
  }

  getLastPoseData() {
    return this.lastPoseData;
  }

  getValidationErrors() {
    return [...this.validationErrors];
  }

  clearValidationErrors() {
    this.validationErrors = [];
    this.logger.info('Validation errors cleared');
  }

  updateConfig(newConfig) {
    this.config = { ...this.config, ...newConfig };
    this.logger.info('Configuration updated', { config: this.config });
  }

  // Enable or disable model inference mode.
  // When active, confidence thresholds are lowered because model inference
  // produces more reliable detections than raw signal-derived heuristics.
  setModelMode(active) {
    this.modelActive = !!active;
    this.logger.info('Model mode updated', { modelActive: this.modelActive });
  }

  // Health check
  async healthCheck() {
    try {
      const stats = await this.getStats(1);
      return {
        healthy: true,
        connectionState: this.connectionState,
        lastUpdate: this.performanceMetrics.lastUpdateTime,
        messageCount: this.performanceMetrics.messageCount,
        errorCount: this.performanceMetrics.errorCount,
        apiHealthy: !!stats
      };
    } catch (error) {
      return {
        healthy: false,
        error: error.message,
        connectionState: this.connectionState,
        lastUpdate: this.performanceMetrics.lastUpdateTime
      };
    }
  }

  // Force reconnection
  async reconnectStream() {
    if (!this.streamConnection) {
      throw new Error('No active stream connection to reconnect');
    }

    this.logger.info('Forcing stream reconnection');
    
    // Get current connection stats to preserve options
    const stats = wsService.getConnectionStats(this.streamConnection);
    if (!stats) {
      throw new Error('Cannot get connection stats for reconnection');
    }

    // Extract original options from URL parameters
    const url = new URL(stats.url);
    const params = Object.fromEntries(url.searchParams);
    
    const options = {
      zoneIds: params.zone_ids ? params.zone_ids.split(',') : undefined,
      minConfidence: params.min_confidence ? parseFloat(params.min_confidence) : undefined,
      maxFps: params.max_fps ? parseInt(params.max_fps) : undefined,
      token: params.token
    };

    // Stop current stream
    this.stopPoseStream();

    // Start new stream with same options
    return this.startPoseStream(options);
  }

  // Clean up
  dispose() {
    this.logger.info('Disposing pose service');
    this.stopPoseStream();
    this.stopEventStream();
    this.poseSubscribers = [];
    this.eventSubscribers = [];
    this.connectionState = 'disconnected';
    this.lastPoseData = null;
    this.resetPerformanceMetrics();
  }
}

// Create singleton instance
export const poseService = new PoseService();