import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';

export interface MetricsUpdateMessage {
  type: 'metrics_updated';
  timestamp: Date;
  metrics: {
    metricType: string;
    granularity: string;
    value: number;
    dimension?: string;
    dimensionValue?: string;
  }[];
}

export type UpdateHandler = (message: MetricsUpdateMessage) => void;

/**
 * Redis pub/sub service for realtime analytics updates.
 * Subscribes to aggregation events from the processor service.
 */
@Injectable()
export class RedisPubSubService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(RedisPubSubService.name);
  private subscriber: Redis;
  private readonly handlers: Set<UpdateHandler> = new Set();
  private readonly CHANNEL = 'analytics:realtime:updates';
  private reconnectAttempts = 0;
  private readonly MAX_RECONNECT_ATTEMPTS = 10;
  private isConnected = false;

  constructor(private readonly config: ConfigService) {}

  async onModuleInit() {
    await this.connect();
  }

  async onModuleDestroy() {
    await this.disconnect();
  }

  /**
   * Connect to Redis and subscribe to updates channel.
   */
  private async connect(): Promise<void> {
    const host = this.config.get('REDIS_HOST', 'localhost');
    const port = this.config.get('REDIS_PORT', 6379);

    this.subscriber = new Redis({
      host,
      port,
      retryStrategy: (times) => {
        if (times > this.MAX_RECONNECT_ATTEMPTS) {
          this.logger.error('Max Redis reconnection attempts reached');
          return null;
        }
        const delay = Math.min(times * 50, 2000);
        this.logger.warn(`Redis reconnecting in ${delay}ms (attempt ${times})`);
        return delay;
      },
      maxRetriesPerRequest: 3,
    });

    this.subscriber.on('connect', () => {
      this.logger.log(`Connected to Redis at ${host}:${port}`);
      this.isConnected = true;
      this.reconnectAttempts = 0;
    });

    this.subscriber.on('error', (error) => {
      this.logger.error(`Redis connection error: ${error.message}`);
      this.isConnected = false;
    });

    this.subscriber.on('close', () => {
      this.logger.warn('Redis connection closed');
      this.isConnected = false;
      this.reconnectAttempts++;
    });

    this.subscriber.on('reconnecting', () => {
      this.logger.log('Reconnecting to Redis...');
    });

    // Subscribe to updates channel
    this.subscriber.on('message', (channel, message) => {
      if (channel === this.CHANNEL) {
        this.handleMessage(message);
      }
    });

    // Wait for connection
    await new Promise<void>((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Redis connection timeout'));
      }, 5000);

      this.subscriber.once('connect', () => {
        clearTimeout(timeout);
        resolve();
      });

      this.subscriber.once('error', (error) => {
        clearTimeout(timeout);
        reject(error);
      });
    });

    // Subscribe to channel
    await this.subscriber.subscribe(this.CHANNEL);
    this.logger.log(`Subscribed to channel: ${this.CHANNEL}`);
  }

  /**
   * Disconnect from Redis.
   */
  private async disconnect(): Promise<void> {
    if (this.subscriber) {
      await this.subscriber.unsubscribe(this.CHANNEL);
      await this.subscriber.quit();
      this.logger.log('Disconnected from Redis');
    }
  }

  /**
   * Handle incoming messages from Redis.
   */
  private handleMessage(message: string): void {
    try {
      const parsed = JSON.parse(message);

      // Convert timestamp string back to Date
      if (parsed.timestamp) {
        parsed.timestamp = new Date(parsed.timestamp);
      }

      // Notify all registered handlers
      for (const handler of this.handlers) {
        try {
          handler(parsed);
        } catch (error) {
          this.logger.error(`Handler error: ${error}`);
        }
      }
    } catch (error) {
      this.logger.error(`Failed to parse message: ${error}`);
    }
  }

  /**
   * Register a handler for metrics updates.
   * Returns an unsubscribe function.
   */
  onUpdate(handler: UpdateHandler): () => void {
    this.handlers.add(handler);
    return () => {
      this.handlers.delete(handler);
    };
  }

  /**
   * Check if Redis connection is active.
   */
  isHealthy(): boolean {
    return this.isConnected && this.subscriber?.status === 'ready';
  }

  /**
   * Get connection status for debugging.
   */
  getStatus() {
    return {
      connected: this.isConnected,
      status: this.subscriber?.status,
      reconnectAttempts: this.reconnectAttempts,
      handlers: this.handlers.size,
    };
  }
}
