/**
 * Event Publisher Service
 *
 * Publishes domain events to Kafka.
 */

import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Kafka, Producer, logLevel } from 'kafkajs';
import { DomainEvent, MPC_TOPICS } from '../../../domain/events';

@Injectable()
export class EventPublisherService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(EventPublisherService.name);
  private kafka: Kafka;
  private producer: Producer;
  private isConnected = false;

  constructor(private readonly configService: ConfigService) {}

  async onModuleInit() {
    const brokers = this.configService.get<string>('KAFKA_BROKERS')?.split(',') || ['localhost:9092'];
    const clientId = this.configService.get<string>('KAFKA_CLIENT_ID') || 'mpc-party-service';

    this.logger.log(`[INIT] MPC Event Publisher initializing...`);
    this.logger.log(`[INIT] ClientId: ${clientId}`);
    this.logger.log(`[INIT] Brokers: ${brokers.join(', ')}`);

    this.kafka = new Kafka({
      clientId,
      brokers,
      logLevel: logLevel.WARN,
      retry: {
        initialRetryTime: 100,
        retries: 8,
      },
    });

    this.producer = this.kafka.producer({
      allowAutoTopicCreation: true,
      transactionTimeout: 30000,
    });

    try {
      this.logger.log(`[CONNECT] Connecting Kafka producer...`);
      await this.producer.connect();
      this.isConnected = true;
      this.logger.log(`[CONNECT] Kafka producer connected successfully`);
    } catch (error) {
      this.logger.error(`[ERROR] Failed to connect Kafka producer`, error);
    }
  }

  async onModuleDestroy() {
    if (this.isConnected) {
      await this.producer.disconnect();
      this.logger.log('Kafka producer disconnected');
    }
  }

  /**
   * Publish a single domain event
   */
  async publish(event: DomainEvent): Promise<void> {
    if (!this.isConnected) {
      this.logger.warn(`[PUBLISH] Kafka not connected, skipping event publish for ${event.eventType}`);
      return;
    }

    const topic = this.getTopicForEvent(event);

    this.logger.log(`[PUBLISH] Publishing event: type=${event.eventType}, topic=${topic}`);
    this.logger.log(`[PUBLISH] EventId: ${event.eventId}`);
    this.logger.log(`[PUBLISH] AggregateId: ${event.aggregateId}`);

    // Merge extraPayload into payload if present (used by KeygenCompletedEvent etc.)
    const extraPayload = (event as any).extraPayload || {};
    const fullPayload = { ...event.payload, extraPayload };

    const messageValue = {
      eventId: event.eventId,
      eventType: event.eventType,
      occurredAt: event.occurredAt.toISOString(),
      aggregateId: event.aggregateId,
      aggregateType: event.aggregateType,
      payload: fullPayload,
    };

    this.logger.log(`[PUBLISH] Payload keys: ${Object.keys(event.payload).join(', ')}`);

    const message = {
      key: event.eventId,
      value: JSON.stringify(messageValue),
      headers: {
        eventType: event.eventType,
        aggregateType: event.aggregateType,
        version: '1.0',
      },
    };

    try {
      await this.producer.send({
        topic,
        messages: [message],
      });
      this.logger.log(`[PUBLISH] Successfully published ${event.eventType} to ${topic}`);
    } catch (error) {
      this.logger.error(`[ERROR] Failed to publish event: ${event.eventType}`, error);
      throw error;
    }
  }

  /**
   * Publish multiple domain events
   */
  async publishAll(events: DomainEvent[]): Promise<void> {
    for (const event of events) {
      await this.publish(event);
    }
  }

  /**
   * Publish with retry logic
   */
  async publishWithRetry(event: DomainEvent, maxRetries = 3): Promise<void> {
    this.logger.log(`[RETRY] Publishing with retry: ${event.eventType}, maxRetries=${maxRetries}`);
    let lastError: Error | undefined;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        this.logger.log(`[RETRY] Attempt ${attempt}/${maxRetries} for ${event.eventType}`);
        await this.publish(event);
        this.logger.log(`[RETRY] Successfully published on attempt ${attempt}`);
        return;
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));
        this.logger.warn(`[RETRY] Attempt ${attempt} failed: ${lastError.message}`);

        if (attempt < maxRetries) {
          const delay = Math.pow(2, attempt) * 100; // Exponential backoff
          this.logger.log(`[RETRY] Waiting ${delay}ms before next attempt...`);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }

    this.logger.error(`[RETRY] All ${maxRetries} attempts failed for ${event.eventType}`);
    throw lastError;
  }

  private getTopicForEvent(event: DomainEvent): string {
    const topicMap: Record<string, string> = {
      ShareCreated: MPC_TOPICS.SHARE_CREATED,
      ShareRotated: MPC_TOPICS.SHARE_ROTATED,
      ShareRevoked: MPC_TOPICS.SHARE_REVOKED,
      ShareUsed: MPC_TOPICS.SHARE_USED,
      KeygenStarted: MPC_TOPICS.KEYGEN_STARTED,
      KeygenCompleted: MPC_TOPICS.KEYGEN_COMPLETED,
      SigningCompleted: MPC_TOPICS.SIGNING_COMPLETED,
      SessionFailed: MPC_TOPICS.SESSION_FAILED,
      PartyJoinedSession: MPC_TOPICS.PARTY_JOINED_SESSION,
      SessionTimeout: MPC_TOPICS.SESSION_TIMEOUT,
      ShareDecryptionAttempted: MPC_TOPICS.SHARE_DECRYPTION_ATTEMPTED,
    };

    return topicMap[event.eventType] || `mpc.${event.eventType}`;
  }
}
