/**
 * TimeSafari Daily Notification Service
 * 
 * Main service layer for TimeSafari Daily Notification Plugin integration.
 * Implements singleton pattern following TimeSafari conventions with structured
 * logging, circuit breaker patterns, and comprehensive error handling.
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

import { TimeSafariIntegrationService } from '../timesafari-integration';
import { TimeSafariCommunityIntegrationService } from '../timesafari-community-integration';
import { TimeSafariStorageAdapterImpl, StorageFactory } from '../timesafari-storage-adapter';
import { observability, EVENT_CODES } from '../observability';

/**
 * TimeSafari Daily Notification Service Configuration
 */
export interface DailyNotificationServiceConfig {
  /**
   * Active DID for TimeSafari integration
   */
  activeDid: string;
  
  /**
   * Storage adapter for data persistence
   */
  storageAdapter?: TimeSafariStorageAdapterImpl;
  
  /**
   * Circuit breaker configuration
   */
  circuitBreaker?: CircuitBreakerConfig;
  
  /**
   * Backoff configuration
   */
  backoff?: BackoffConfig;
  
  /**
   * Logging configuration
   */
  logging?: LoggingConfig;
  
  /**
   * Platform-specific configuration
   */
  platform?: 'android' | 'ios' | 'electron';
}

/**
 * Circuit Breaker Configuration
 */
export interface CircuitBreakerConfig {
  failureThreshold: number;
  recoveryTimeout: number; // milliseconds
  monitoringPeriod: number; // milliseconds
  halfOpenMaxCalls: number;
}

/**
 * Backoff Configuration
 */
export interface BackoffConfig {
  initialDelay: number; // milliseconds
  maxDelay: number; // milliseconds
  multiplier: number;
  jitter: boolean;
  maxRetries: number;
}

/**
 * Logging Configuration
 */
export interface LoggingConfig {
  level: 'debug' | 'info' | 'warn' | 'error';
  enableStructuredLogging: boolean;
  enableEventIds: boolean;
  enablePerformanceMetrics: boolean;
}

/**
 * Service Status
 */
export interface ServiceStatus {
  initialized: boolean;
  activeDid: string | null;
  platform: string;
  circuitBreakerState: 'closed' | 'open' | 'half-open';
  lastError: string | null;
  lastErrorTime: number | null;
  totalRequests: number;
  successfulRequests: number;
  failedRequests: number;
  uptime: number;
}

/**
 * TimeSafari Daily Notification Service
 * 
 * Singleton service that provides a unified interface for TimeSafari
 * daily notification functionality across all platforms.
 */
export class DailyNotificationService {
  private static instance: DailyNotificationService;
  private config: DailyNotificationServiceConfig;
  private initialized = false;
  private startTime: number = Date.now();
  private requestCount = 0;
  private successCount = 0;
  private failureCount = 0;
  private lastError: string | null = null;
  private lastErrorTime: number | null = null;
  
  // Circuit breaker state
  private circuitBreakerState: 'closed' | 'open' | 'half-open' = 'closed';
  private circuitBreakerFailures = 0;
  private circuitBreakerLastFailure = 0;
  private circuitBreakerHalfOpenCalls = 0;
  
  // Services
  private integrationService: TimeSafariIntegrationService;
  private communityService: TimeSafariCommunityIntegrationService;
  private storageAdapter: TimeSafariStorageAdapterImpl;
  
  private constructor() {
    this.config = this.getDefaultConfig();
    this.integrationService = TimeSafariIntegrationService.getInstance();
    this.communityService = TimeSafariCommunityIntegrationService.getInstance();
    this.storageAdapter = new TimeSafariStorageAdapterImpl(
      StorageFactory.createStorage('android')
    );
  }
  
  /**
   * Get singleton instance
   */
  static getInstance(): DailyNotificationService {
    if (!DailyNotificationService.instance) {
      DailyNotificationService.instance = new DailyNotificationService();
    }
    return DailyNotificationService.instance;
  }
  
  /**
   * Initialize the service with TimeSafari configuration
   */
  async initialize(config: DailyNotificationServiceConfig): Promise<void> {
    try {
      const eventId = this.generateEventId();
      observability.logEvent('INFO', EVENT_CODES.FETCH_START, 'Initializing DailyNotificationService', { eventId });
      
      this.config = { ...this.getDefaultConfig(), ...config };
      
      // Initialize storage adapter if provided
      if (config.storageAdapter) {
        this.storageAdapter = config.storageAdapter;
      }
      
      // Initialize TimeSafari integration service
      await this.integrationService.initialize({
        activeDid: config.activeDid,
        storageAdapter: this.storageAdapter,
        endorserApiBaseUrl: 'https://endorser.ch/api/v1'
      });
      
      // Initialize community integration service
      await this.communityService.initialize({
        maxRequestsPerMinute: 30,
        maxRequestsPerHour: 1000,
        burstLimit: 10,
        initialBackoffMs: this.config.backoff?.initialDelay || 1000,
        maxBackoffMs: this.config.backoff?.maxDelay || 30000,
        backoffMultiplier: this.config.backoff?.multiplier || 2,
        jitterEnabled: this.config.backoff?.jitter !== false,
        basePollingIntervalMs: 300000, // 5 minutes
        maxPollingIntervalMs: 1800000, // 30 minutes
        adaptivePolling: true
      });
      
      this.initialized = true;
      
      observability.logEvent('INFO', EVENT_CODES.FETCH_SUCCESS, 'DailyNotificationService initialized successfully', { 
        eventId,
        activeDid: config.activeDid,
        platform: config.platform || 'android'
      });
      
    } catch (error) {
      const eventId = this.generateEventId();
      this.recordError(error as Error);
      
      observability.logEvent('ERROR', EVENT_CODES.FETCH_FAILURE, 'Failed to initialize DailyNotificationService', { 
        eventId,
        error: (error as Error).message
      });
      
      throw error;
    }
  }
  
  /**
   * Schedule a daily notification with TimeSafari integration
   */
  async scheduleDailyNotification(options: {
    title: string;
    body: string;
    time: string;
    channel?: string;
    actions?: Array<{ id: string; title: string }>;
  }): Promise<void> {
    this.checkInitialized();
    
    try {
      const eventId = this.generateEventId();
      this.recordRequest();
      
      observability.logEvent('INFO', EVENT_CODES.FETCH_START, 'Scheduling daily notification', { 
        eventId,
        channel: options.channel || 'default'
      });
      
      // Use the appropriate notification channel based on content
      const channel = options.channel || this.determineNotificationChannel(options.title, options.body);
      
      // Schedule the notification using native platform implementation
      // This will be handled by the native Android/iOS/Electron implementations
      // For now, we'll store the notification data and let the native layer handle scheduling
      observability.logEvent('INFO', EVENT_CODES.FETCH_SUCCESS, 'Scheduling notification via native platform', {
        eventId,
        title: options.title,
        body: options.body,
        time: options.time,
        channel: channel
      });
      
      // Store notification metadata
      await this.storageAdapter.store(`notification_${eventId}`, {
        title: options.title,
        body: options.body,
        time: options.time,
        channel: channel,
        scheduledAt: Date.now(),
        activeDid: this.config.activeDid
      }, 7 * 24 * 60 * 60 * 1000); // 7 days TTL
      
      this.recordSuccess();
      
      observability.logEvent('INFO', EVENT_CODES.FETCH_SUCCESS, 'Daily notification scheduled successfully', { 
        eventId,
        channel: channel
      });
      
    } catch (error) {
      this.recordError(error as Error);
      throw error;
    }
  }
  
  /**
   * Fetch TimeSafari community data with rate limiting and backoff
   */
  async fetchCommunityData(): Promise<unknown> {
    this.checkInitialized();
    
    try {
      const eventId = this.generateEventId();
      this.recordRequest();
      
      observability.logEvent('INFO', EVENT_CODES.FETCH_START, 'Fetching TimeSafari community data', { eventId });
      
      const userConfig = {
        activeDid: this.config.activeDid,
        fetchOffersToPerson: true,
        fetchOffersToProjects: true,
        fetchProjectUpdates: true,
        starredPlanIds: await this.getStarredPlanIds()
      };
      
      const bundle = await this.communityService.fetchCommunityDataWithRateLimit(userConfig);
      
      // Store community data
      await this.storageAdapter.store('community_data', bundle, 3600000); // 1 hour TTL
      
      this.recordSuccess();
      
      observability.logEvent('INFO', EVENT_CODES.FETCH_SUCCESS, 'Community data fetched successfully', { 
        eventId,
        dataSize: JSON.stringify(bundle).length
      });
      
      return bundle;
      
    } catch (error) {
      this.recordError(error as Error);
      throw error;
    }
  }
  
  /**
   * Get service status
   */
  getStatus(): ServiceStatus {
    return {
      initialized: this.initialized,
      activeDid: this.config.activeDid,
      platform: this.config.platform || 'android',
      circuitBreakerState: this.circuitBreakerState,
      lastError: this.lastError,
      lastErrorTime: this.lastErrorTime,
      totalRequests: this.requestCount,
      successfulRequests: this.successCount,
      failedRequests: this.failureCount,
      uptime: Date.now() - this.startTime
    };
  }
  
  /**
   * Get circuit breaker status
   */
  getCircuitBreakerStatus(): {
    state: 'closed' | 'open' | 'half-open';
    failures: number;
    lastFailure: number;
    halfOpenCalls: number;
  } {
    return {
      state: this.circuitBreakerState,
      failures: this.circuitBreakerFailures,
      lastFailure: this.circuitBreakerLastFailure,
      halfOpenCalls: this.circuitBreakerHalfOpenCalls
    };
  }
  
  /**
   * Get rate limiting status
   */
  getRateLimitStatus(): unknown {
    return this.communityService.getRateLimitStatus();
  }
  
  /**
   * Get backoff status
   */
  getBackoffStatus(): unknown {
    return this.communityService.getBackoffStatus();
  }
  
  /**
   * Get polling status
   */
  getPollingStatus(): unknown {
    return this.communityService.getPollingStatus();
  }
  
  /**
   * Update configuration
   */
  updateConfig(updates: Partial<DailyNotificationServiceConfig>): void {
    this.config = { ...this.config, ...updates };
    
    const eventId = this.generateEventId();
    observability.logEvent('INFO', EVENT_CODES.FETCH_START, 'Service configuration updated', { 
      eventId,
      updatedFields: Object.keys(updates)
    });
  }
  
  /**
   * Reset circuit breaker
   */
  resetCircuitBreaker(): void {
    this.circuitBreakerState = 'closed';
    this.circuitBreakerFailures = 0;
    this.circuitBreakerLastFailure = 0;
    this.circuitBreakerHalfOpenCalls = 0;
    
    const eventId = this.generateEventId();
    observability.logEvent('INFO', EVENT_CODES.FETCH_START, 'Circuit breaker reset', { eventId });
  }
  
  /**
   * Shutdown the service
   */
  async shutdown(): Promise<void> {
    try {
      const eventId = this.generateEventId();
      observability.logEvent('INFO', EVENT_CODES.FETCH_START, 'Shutting down DailyNotificationService', { eventId });
      
      // Clear any scheduled notifications via native platform
      // This will be handled by the native Android/iOS/Electron implementations
      observability.logEvent('INFO', EVENT_CODES.FETCH_SUCCESS, 'Clearing notifications via native platform', { eventId });
      
      // Clear storage
      await this.storageAdapter.clear();
      
      this.initialized = false;
      
      observability.logEvent('INFO', EVENT_CODES.FETCH_SUCCESS, 'DailyNotificationService shutdown complete', { eventId });
      
    } catch (error) {
      const eventId = this.generateEventId();
      observability.logEvent('ERROR', EVENT_CODES.FETCH_FAILURE, 'Error during service shutdown', { 
        eventId,
        error: (error as Error).message
      });
      
      throw error;
    }
  }
  
  // Private methods
  
  private getDefaultConfig(): DailyNotificationServiceConfig {
    return {
      activeDid: '',
      circuitBreaker: {
        failureThreshold: 5,
        recoveryTimeout: 60000, // 1 minute
        monitoringPeriod: 10000, // 10 seconds
        halfOpenMaxCalls: 3
      },
      backoff: {
        initialDelay: 1000,
        maxDelay: 30000,
        multiplier: 2,
        jitter: true,
        maxRetries: 3
      },
      logging: {
        level: 'info',
        enableStructuredLogging: true,
        enableEventIds: true,
        enablePerformanceMetrics: true
      },
      platform: 'android'
    };
  }
  
  private checkInitialized(): void {
    if (!this.initialized) {
      throw new Error('DailyNotificationService not initialized. Call initialize() first.');
    }
  }
  
  private recordRequest(): void {
    this.requestCount++;
  }
  
  private recordSuccess(): void {
    this.successCount++;
    this.resetCircuitBreakerOnSuccess();
  }
  
  private recordError(error: Error): void {
    this.failureCount++;
    this.lastError = error.message;
    this.lastErrorTime = Date.now();
    this.updateCircuitBreakerOnFailure();
  }
  
  private updateCircuitBreakerOnFailure(): void {
    this.circuitBreakerFailures++;
    this.circuitBreakerLastFailure = Date.now();
    
    if (this.circuitBreakerFailures >= (this.config.circuitBreaker?.failureThreshold || 5)) {
      this.circuitBreakerState = 'open';
      
      const eventId = this.generateEventId();
      observability.logEvent('WARN', EVENT_CODES.FETCH_FAILURE, 'Circuit breaker opened due to failures', { 
        eventId,
        failures: this.circuitBreakerFailures
      });
    }
  }
  
  private resetCircuitBreakerOnSuccess(): void {
    if (this.circuitBreakerState === 'half-open') {
      this.circuitBreakerState = 'closed';
      this.circuitBreakerFailures = 0;
      this.circuitBreakerHalfOpenCalls = 0;
      
      const eventId = this.generateEventId();
      observability.logEvent('INFO', EVENT_CODES.FETCH_SUCCESS, 'Circuit breaker closed after successful recovery', { eventId });
    }
  }
  
  private determineNotificationChannel(title: string, body: string): string {
    // Determine appropriate notification channel based on content
    if (title.toLowerCase().includes('community') || body.toLowerCase().includes('community')) {
      return 'timesafari_community_updates';
    } else if (title.toLowerCase().includes('project') || body.toLowerCase().includes('project')) {
      return 'timesafari_project_notifications';
    } else if (title.toLowerCase().includes('trust') || body.toLowerCase().includes('trust')) {
      return 'timesafari_trust_network';
    } else if (title.toLowerCase().includes('reminder') || body.toLowerCase().includes('reminder')) {
      return 'timesafari_reminders';
    } else {
      return 'timesafari_system';
    }
  }
  
  private async getStarredPlanIds(): Promise<string[]> {
    try {
      const starredPlans = await this.storageAdapter.retrieve('starred_plans');
      return (starredPlans as string[]) || [];
    } catch (error) {
      observability.logEvent('WARN', EVENT_CODES.FETCH_FAILURE, 'Failed to retrieve starred plans', { 
        error: (error as Error).message
      });
      return [];
    }
  }
  
  private generateEventId(): string {
    return `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}
