/**
 * Observability & Health Monitoring Implementation
 * Provides structured logging, event codes, and health monitoring
 * 
 * @author Matthew Raymer
 * @version 2.1.0
 */

import {
  type EventLog,
  EVENT_CODES,
  createEventLog,
} from './core/events';
import { DailyNotificationError } from './core/errors';

export interface HealthStatus {
  nextRuns: number[];
  lastOutcomes: string[];
  cacheAgeMs: number | null;
  staleArmed: boolean;
  queueDepth: number;
  circuitBreakers: {
    total: number;
    open: number;
    failures: number;
  };
  performance: {
    avgFetchTime: number;
    avgNotifyTime: number;
    successRate: number;
  };
}

// Re-export EventLog and EVENT_CODES for backward compatibility
export type { EventLog } from './core/events';
export { EVENT_CODES } from './core/events';

export interface PerformanceMetrics {
  fetchTimes: number[];
  notifyTimes: number[];
  callbackTimes: number[];
  successCount: number;
  failureCount: number;
  lastReset: number;
}

export interface UserMetrics {
  optOuts: number;
  optIns: number;
  permissionDenials: number;
  permissionGrants: number;
  lastUserAction: number;
}

export interface PlatformMetrics {
  androidWorkManagerStarts: number;
  iosBgTaskStarts: number;
  electronNotifications: number;
  platformErrors: number;
  lastPlatformEvent: number;
}

/**
 * Observability Manager
 * Handles structured logging, health monitoring, and performance tracking
 */
export class ObservabilityManager {
  private eventLogs: EventLog[] = [];
  private performanceMetrics: PerformanceMetrics = {
    fetchTimes: [],
    notifyTimes: [],
    callbackTimes: [],
    successCount: 0,
    failureCount: 0,
    lastReset: Date.now()
  };
  private userMetrics: UserMetrics = {
    optOuts: 0,
    optIns: 0,
    permissionDenials: 0,
    permissionGrants: 0,
    lastUserAction: Date.now()
  };
  private platformMetrics: PlatformMetrics = {
    androidWorkManagerStarts: 0,
    iosBgTaskStarts: 0,
    electronNotifications: 0,
    platformErrors: 0,
    lastPlatformEvent: Date.now()
  };
  private maxLogs = 1000;
  private maxMetrics = 100;
  private diagnosticMode = false;

  /**
   * Log structured event with event code
   */
  logEvent(
    level: 'INFO' | 'WARN' | 'ERROR',
    eventCode: string,
    message: string,
    data?: Record<string, unknown>,
    duration?: number
  ): void {
    const event = createEventLog(
      level,
      eventCode,
      message,
      data,
      duration,
      this.generateEventId()
    );

    this.eventLogs.unshift(event);
    
    // Keep only recent logs
    if (this.eventLogs.length > this.maxLogs) {
      this.eventLogs = this.eventLogs.slice(0, this.maxLogs);
    }

    // Console output with structured format (commented out)
    // const logMessage = `[${eventCode}] ${message}`;
    // const logData = data ? ` | Data: ${JSON.stringify(data)}` : '';
    // const logDuration = duration ? ` | Duration: ${duration}ms` : '';
    
    switch (level) {
      case 'INFO':
        // console.log(logMessage + logData + logDuration);
        break;
      case 'WARN':
        // console.warn(logMessage + logData + logDuration);
        break;
      case 'ERROR':
        // console.error(logMessage + logData + logDuration);
        break;
    }
  }

  /**
   * Log error with enhanced context
   * @param eventCode Event code
   * @param message Human-readable message
   * @param error Error object
   * @param context Additional context
   */
  logError(eventCode: string, message: string, error: Error, context?: Record<string, unknown>): void {
    const errorData: Record<string, unknown> = {
      error: error.message,
      errorCode: error instanceof DailyNotificationError ? error.code : undefined,
      errorName: error.name,
      ...(error instanceof DailyNotificationError && error.details ? { errorDetails: error.details } : {}),
      ...(error.stack ? { stack: error.stack } : {}),
      ...context,
    };

    // Use toJSON if available for structured error data
    if (error instanceof DailyNotificationError && typeof (error as unknown as { toJSON?: () => Record<string, unknown> }).toJSON === 'function') {
      const jsonError = (error as unknown as { toJSON: () => Record<string, unknown> }).toJSON();
      Object.assign(errorData, jsonError);
    }

    this.logEvent('ERROR', eventCode, message, errorData);
  }

  /**
   * Record performance metrics
   */
  recordMetric(type: 'fetch' | 'notify' | 'callback', duration: number, success: boolean): void {
    switch (type) {
      case 'fetch':
        this.performanceMetrics.fetchTimes.push(duration);
        break;
      case 'notify':
        this.performanceMetrics.notifyTimes.push(duration);
        break;
      case 'callback':
        this.performanceMetrics.callbackTimes.push(duration);
        break;
    }

    if (success) {
      this.performanceMetrics.successCount++;
    } else {
      this.performanceMetrics.failureCount++;
    }

    // Keep only recent metrics
    this.trimMetrics();
  }

  /**
   * Get health status
   */
  async getHealthStatus(): Promise<HealthStatus> {
    const now = Date.now();
    const recentLogs = this.eventLogs.filter(log => now - log.timestamp < 24 * 60 * 60 * 1000); // Last 24 hours
    
    // Calculate next runs (mock implementation)
    const nextRuns = this.calculateNextRuns();
    
    // Get last outcomes from recent logs
    const lastOutcomes = recentLogs
      .filter(log => log.eventCode.startsWith('DNP-FETCH-') || log.eventCode.startsWith('DNP-NOTIFY-'))
      .slice(0, 10)
      .map(log => log.eventCode);
    
    // Calculate cache age (mock implementation)
    const cacheAgeMs = this.calculateCacheAge();
    
    // Check if stale armed
    const staleArmed = cacheAgeMs ? cacheAgeMs > 3600000 : true; // 1 hour
    
    // Calculate queue depth
    const queueDepth = recentLogs.filter(log => 
      log.eventCode.includes('QUEUE') || log.eventCode.includes('RETRY')
    ).length;
    
    // Circuit breaker status
    const circuitBreakers = this.getCircuitBreakerStatus();
    
    // Performance metrics
    const performance = this.calculatePerformanceMetrics();
    
    return {
      nextRuns,
      lastOutcomes,
      cacheAgeMs,
      staleArmed,
      queueDepth,
      circuitBreakers,
      performance
    };
  }

  /**
   * Export metrics as JSON
   * @returns JSON string of all metrics
   */
  exportMetrics(): string {
    return JSON.stringify({
      performance: this.performanceMetrics,
      user: this.userMetrics,
      platform: this.platformMetrics,
      events: this.eventLogs.slice(0, 100), // Last 100 events
      exportedAt: Date.now(),
      schemaVersion: 1
    }, null, 2);
  }

  /**
   * Get metrics summary (lightweight)
   * @returns Summary object
   */
  getMetricsSummary(): {
    eventCount: number;
    successRate: number;
    avgFetchTime: number;
    avgNotifyTime: number;
  } {
    const fetchTimes = this.performanceMetrics.fetchTimes;
    const notifyTimes = this.performanceMetrics.notifyTimes;
    const total = this.performanceMetrics.successCount + this.performanceMetrics.failureCount;
    
    return {
      eventCount: this.eventLogs.length,
      successRate: total > 0 ? this.performanceMetrics.successCount / total : 0,
      avgFetchTime: fetchTimes.length > 0 
        ? fetchTimes.reduce((a, b) => a + b, 0) / fetchTimes.length 
        : 0,
      avgNotifyTime: notifyTimes.length > 0
        ? notifyTimes.reduce((a, b) => a + b, 0) / notifyTimes.length
        : 0
    };
  }

  /**
   * Get recent event logs
   */
  getRecentLogs(limit = 50): EventLog[] {
    return this.eventLogs.slice(0, limit);
  }

  /**
   * Get performance metrics
   */
  getPerformanceMetrics(): PerformanceMetrics {
    return { ...this.performanceMetrics };
  }

  /**
   * Get user interaction metrics
   */
  getUserMetrics(): UserMetrics {
    return { ...this.userMetrics };
  }

  /**
   * Get platform-specific metrics
   */
  getPlatformMetrics(): PlatformMetrics {
    return { ...this.platformMetrics };
  }

  /**
   * Record user interaction event
   */
  recordUserEvent(type: 'optOut' | 'optIn' | 'permissionDenied' | 'permissionGranted'): void {
    switch (type) {
      case 'optOut':
        this.userMetrics.optOuts++;
        this.logEvent('INFO', EVENT_CODES.USER_OPT_OUT, 'User opted out of notifications');
        break;
      case 'optIn':
        this.userMetrics.optIns++;
        this.logEvent('INFO', EVENT_CODES.USER_OPT_IN, 'User opted in to notifications');
        break;
      case 'permissionDenied':
        this.userMetrics.permissionDenials++;
        this.logEvent('WARN', EVENT_CODES.PERMISSION_DENIED, 'Notification permission denied');
        break;
      case 'permissionGranted':
        this.userMetrics.permissionGrants++;
        this.logEvent('INFO', EVENT_CODES.PERMISSION_GRANTED, 'Notification permission granted');
        break;
    }
    this.userMetrics.lastUserAction = Date.now();
  }

  /**
   * Record platform-specific event
   */
  recordPlatformEvent(type: 'androidWorkManager' | 'iosBgTask' | 'electronNotification' | 'platformError'): void {
    switch (type) {
      case 'androidWorkManager':
        this.platformMetrics.androidWorkManagerStarts++;
        this.logEvent('INFO', EVENT_CODES.ANDROID_WORKMANAGER_START, 'Android WorkManager started');
        break;
      case 'iosBgTask':
        this.platformMetrics.iosBgTaskStarts++;
        this.logEvent('INFO', EVENT_CODES.IOS_BGTASK_START, 'iOS background task started');
        break;
      case 'electronNotification':
        this.platformMetrics.electronNotifications++;
        this.logEvent('INFO', EVENT_CODES.ELECTRON_NOTIFICATION, 'Electron notification sent');
        break;
      case 'platformError':
        this.platformMetrics.platformErrors++;
        this.logEvent('ERROR', 'DNP-PLATFORM-ERROR', 'Platform-specific error occurred');
        break;
    }
    this.platformMetrics.lastPlatformEvent = Date.now();
  }

  /**
   * Reset performance metrics
   */
  resetMetrics(): void {
    this.performanceMetrics = {
      fetchTimes: [],
      notifyTimes: [],
      callbackTimes: [],
      successCount: 0,
      failureCount: 0,
      lastReset: Date.now()
    };
    
    this.logEvent('INFO', 'DNP-METRICS-RESET', 'Performance metrics reset');
  }

  /**
   * Compact old logs (called by cleanup job)
   */
  compactLogs(olderThanMs: number = 30 * 24 * 60 * 60 * 1000): number { // 30 days
    const cutoff = Date.now() - olderThanMs;
    const initialCount = this.eventLogs.length;
    
    this.eventLogs = this.eventLogs.filter(log => log.timestamp >= cutoff);
    
    const removedCount = initialCount - this.eventLogs.length;
    if (removedCount > 0) {
      this.logEvent('INFO', 'DNP-LOGS-COMPACTED', `Removed ${removedCount} old logs`);
    }
    
    return removedCount;
  }

  // Private helper methods
  private generateEventId(): string {
    return `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  /**
   * Enable diagnostic mode (verbose logging)
   */
  enableDiagnosticMode(): void {
    this.diagnosticMode = true;
    this.logEvent('INFO', EVENT_CODES.METRICS_RESET, 'Diagnostic mode enabled');
  }

  /**
   * Disable diagnostic mode
   */
  disableDiagnosticMode(): void {
    this.diagnosticMode = false;
  }

  /**
   * Check if diagnostic mode is enabled
   */
  isDiagnosticMode(): boolean {
    return this.diagnosticMode;
  }

  /**
   * Get diagnostic information
   * @returns Diagnostic info object
   */
  getDiagnosticInfo(): { metrics: string; eventCount: number; diagnosticMode: boolean } {
    return {
      metrics: this.exportMetrics(),
      eventCount: this.eventLogs.length,
      diagnosticMode: this.diagnosticMode
    };
  }

  private trimMetrics(): void {
    if (this.performanceMetrics.fetchTimes.length > this.maxMetrics) {
      this.performanceMetrics.fetchTimes = this.performanceMetrics.fetchTimes.slice(-this.maxMetrics);
    }
    if (this.performanceMetrics.notifyTimes.length > this.maxMetrics) {
      this.performanceMetrics.notifyTimes = this.performanceMetrics.notifyTimes.slice(-this.maxMetrics);
    }
    if (this.performanceMetrics.callbackTimes.length > this.maxMetrics) {
      this.performanceMetrics.callbackTimes = this.performanceMetrics.callbackTimes.slice(-this.maxMetrics);
    }
  }

  private calculateNextRuns(): number[] {
    // Mock implementation - would calculate from actual schedules
    const now = Date.now();
    return [
      now + (60 * 60 * 1000), // 1 hour from now
      now + (24 * 60 * 60 * 1000) // 24 hours from now
    ];
  }

  private calculateCacheAge(): number | null {
    // Mock implementation - would get from actual cache
    return 1800000; // 30 minutes
  }

  private getCircuitBreakerStatus(): { total: number; open: number; failures: number } {
    // Mock implementation - would get from actual circuit breakers
    return {
      total: 3,
      open: 1,
      failures: 5
    };
  }

  private calculatePerformanceMetrics(): {
    avgFetchTime: number;
    avgNotifyTime: number;
    successRate: number;
  } {
    const fetchTimes = this.performanceMetrics.fetchTimes;
    const notifyTimes = this.performanceMetrics.notifyTimes;
    const totalOperations = this.performanceMetrics.successCount + this.performanceMetrics.failureCount;
    
    return {
      avgFetchTime: fetchTimes.length > 0 ? 
        fetchTimes.reduce((a, b) => a + b, 0) / fetchTimes.length : 0,
      avgNotifyTime: notifyTimes.length > 0 ? 
        notifyTimes.reduce((a, b) => a + b, 0) / notifyTimes.length : 0,
      successRate: totalOperations > 0 ? 
        this.performanceMetrics.successCount / totalOperations : 0
    };
  }
}

// Singleton instance
export const observability = new ObservabilityManager();
