/**
 * MPC Event Consumer Service
 *
 * Consumes MPC keygen/signing completion events from mpc-service via Kafka.
 * Updates user wallet addresses when keygen completes.
 */

import {
  Injectable,
  Logger,
  OnModuleInit,
  OnModuleDestroy,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Kafka, Consumer, logLevel, EachMessagePayload } from 'kafkajs';

// MPC Event Topics (events from mpc-service)
export const MPC_TOPICS = {
  KEYGEN_STARTED: 'mpc.KeygenStarted',
  KEYGEN_COMPLETED: 'mpc.KeygenCompleted',
  SIGNING_COMPLETED: 'mpc.SigningCompleted',
  SESSION_FAILED: 'mpc.SessionFailed',
} as const;

export interface KeygenStartedPayload {
  sessionId: string;
  userId: string;
  username: string;
  mpcSessionId: string;
}

export interface KeygenCompletedPayload {
  sessionId: string;
  partyId: string;
  publicKey: string;
  shareId: string;
  threshold: string;
  extraPayload?: {
    userId: string;
    accountSequence: string; // 格式: D + YYMMDD + 5位序号
    username: string;
    delegateShare?: {
      partyId: string;
      partyIndex: number;
      encryptedShare: string;
    };
    serverParties?: string[];
  };
}

export interface SigningCompletedPayload {
  sessionId: string;
  partyId: string;
  messageHash: string;
  signature: string;
  publicKey: string;
  extraPayload?: {
    userId: string;
    username: string;
    mpcSessionId: string;
  };
}

export interface SessionFailedPayload {
  sessionId: string;
  partyId: string;
  sessionType: string;
  errorMessage: string;
  errorCode?: string;
  extraPayload?: {
    userId: string;
    username: string;
  };
}

export type MpcEventHandler<T> = (payload: T) => Promise<void>;

@Injectable()
export class MpcEventConsumerService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(MpcEventConsumerService.name);
  private kafka: Kafka;
  private consumer: Consumer;
  private isConnected = false;
  private isShuttingDown = false;

  private keygenStartedHandler?: MpcEventHandler<KeygenStartedPayload>;
  private keygenCompletedHandler?: MpcEventHandler<KeygenCompletedPayload>;
  private signingCompletedHandler?: MpcEventHandler<SigningCompletedPayload>;
  private sessionFailedHandler?: MpcEventHandler<SessionFailedPayload>;

  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') || 'identity-service';
    const groupId = 'identity-service-mpc-events';

    this.logger.log(`[INIT] MPC Event Consumer initializing...`);
    this.logger.log(`[INIT] ClientId: ${clientId}`);
    this.logger.log(`[INIT] GroupId: ${groupId}`);
    this.logger.log(`[INIT] Brokers: ${brokers.join(', ')}`);
    this.logger.log(
      `[INIT] Topics to subscribe: ${Object.values(MPC_TOPICS).join(', ')}`,
    );

    // 企业级重试配置：指数退避，最多重试约 2.5 小时
    this.kafka = new Kafka({
      clientId,
      brokers,
      logLevel: logLevel.WARN,
      retry: {
        initialRetryTime: 1000, // 1 秒
        maxRetryTime: 300000, // 最大 5 分钟
        retries: 15, // 最多 15 次
        multiplier: 2, // 指数退避因子
        restartOnFailure: async () => true,
      },
    });

    this.consumer = this.kafka.consumer({
      groupId,
      sessionTimeout: 30000,
      heartbeatInterval: 3000,
    });

    // 监听 consumer crash 事件，自动重连
    // 当 Kafka topic-partition 不可用或其他运行时错误导致 consumer 崩溃时触发
    this.consumer.on(this.consumer.events.CRASH, async (event) => {
      if (this.isShuttingDown) return;
      this.logger.error(`[CRASH] Kafka consumer crashed: ${event.payload.error?.message || 'unknown'}, restart: ${event.payload.restart}`);
      if (!event.payload.restart) {
        this.logger.warn(`[CRASH] KafkaJS will not auto-restart, triggering manual reconnect...`);
        this.isConnected = false;
        await this.connectWithRetry();
      }
    });

    await this.connectWithRetry();
  }

  /**
   * 带指数退避的连接重试逻辑
   *
   * 解决问题：服务启动时 Kafka topic-partition 未就绪，导致 subscribe() 抛出
   * "This server does not host this topic-partition" 错误。原实现只 catch 一次就放弃，
   * consumer 永久失效，后续所有 MPC 事件都收不到。
   *
   * 策略：指数退避重试，2s→4s→8s→...→60s（上限），最多 10 次，总等待约 5 分钟。
   */
  private async connectWithRetry(maxRetries = 10): Promise<void> {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      if (this.isShuttingDown) return;

      try {
        if (!this.isConnected) {
          this.logger.log(`[CONNECT] Connecting MPC Event consumer (attempt ${attempt}/${maxRetries})...`);
          await this.consumer.connect();
          this.isConnected = true;
          this.logger.log(`[CONNECT] MPC Event Kafka consumer connected successfully`);
        }

        await this.consumer.subscribe({
          topics: Object.values(MPC_TOPICS),
          fromBeginning: false,
        });
        this.logger.log(
          `[SUBSCRIBE] Subscribed to MPC topics: ${Object.values(MPC_TOPICS).join(', ')}`,
        );

        await this.startConsuming();
        return; // 成功，退出重试循环
      } catch (error: any) {
        this.logger.error(`[ERROR] Failed to connect/subscribe Kafka consumer (attempt ${attempt}/${maxRetries}): ${error.message}`);

        if (attempt < maxRetries) {
          const delay = Math.min(2000 * Math.pow(2, attempt - 1), 60000);
          this.logger.log(`[RETRY] Will retry in ${delay / 1000}s...`);
          await new Promise(resolve => setTimeout(resolve, delay));

          try { await this.consumer.disconnect(); } catch (_) {}
          this.isConnected = false;
        }
      }
    }

    this.logger.error(`[FATAL] Failed to connect Kafka consumer after ${maxRetries} attempts. MPC events will NOT be received!`);
  }

  async onModuleDestroy() {
    this.isShuttingDown = true;
    if (this.isConnected) {
      await this.consumer.disconnect();
      this.logger.log('MPC Event Kafka consumer disconnected');
    }
  }

  /**
   * Register handler for keygen started events
   */
  onKeygenStarted(handler: MpcEventHandler<KeygenStartedPayload>): void {
    this.keygenStartedHandler = handler;
  }

  /**
   * Register handler for keygen completed events
   */
  onKeygenCompleted(handler: MpcEventHandler<KeygenCompletedPayload>): void {
    this.keygenCompletedHandler = handler;
  }

  /**
   * Register handler for signing completed events
   */
  onSigningCompleted(handler: MpcEventHandler<SigningCompletedPayload>): void {
    this.signingCompletedHandler = handler;
  }

  /**
   * Register handler for session failed events
   */
  onSessionFailed(handler: MpcEventHandler<SessionFailedPayload>): void {
    this.sessionFailedHandler = handler;
  }

  private async startConsuming(): Promise<void> {
    await this.consumer.run({
      eachMessage: async ({
        topic,
        partition,
        message,
      }: EachMessagePayload) => {
        const offset = message.offset;
        this.logger.log(
          `[RECEIVE] Message received: topic=${topic}, partition=${partition}, offset=${offset}`,
        );

        try {
          const value = message.value?.toString();
          if (!value) {
            this.logger.warn(`[RECEIVE] Empty message received on ${topic}`);
            return;
          }

          this.logger.log(
            `[RECEIVE] Raw message value: ${value.substring(0, 500)}...`,
          );

          const parsed = JSON.parse(value);
          const payload = parsed.payload || parsed;

          this.logger.log(
            `[RECEIVE] Parsed event: eventType=${parsed.eventType || 'unknown'}`,
          );
          this.logger.log(
            `[RECEIVE] Payload keys: ${Object.keys(payload).join(', ')}`,
          );

          switch (topic) {
            case MPC_TOPICS.KEYGEN_STARTED:
              this.logger.log(`[HANDLE] Processing KeygenStarted event`);
              if (this.keygenStartedHandler) {
                await this.keygenStartedHandler(
                  payload as KeygenStartedPayload,
                );
                this.logger.log(`[HANDLE] KeygenStarted handler completed`);
              } else {
                this.logger.warn(
                  `[HANDLE] No handler registered for KeygenStarted`,
                );
              }
              break;

            case MPC_TOPICS.KEYGEN_COMPLETED:
              this.logger.log(`[HANDLE] Processing KeygenCompleted event`);
              this.logger.log(
                `[HANDLE] publicKey: ${(payload as KeygenCompletedPayload).publicKey?.substring(0, 20)}...`,
              );
              if (this.keygenCompletedHandler) {
                await this.keygenCompletedHandler(
                  payload as KeygenCompletedPayload,
                );
                this.logger.log(`[HANDLE] KeygenCompleted handler completed`);
              } else {
                this.logger.warn(
                  `[HANDLE] No handler registered for KeygenCompleted`,
                );
              }
              break;

            case MPC_TOPICS.SIGNING_COMPLETED:
              this.logger.log(`[HANDLE] Processing SigningCompleted event`);
              if (this.signingCompletedHandler) {
                await this.signingCompletedHandler(
                  payload as SigningCompletedPayload,
                );
                this.logger.log(`[HANDLE] SigningCompleted handler completed`);
              } else {
                this.logger.warn(
                  `[HANDLE] No handler registered for SigningCompleted`,
                );
              }
              break;

            case MPC_TOPICS.SESSION_FAILED:
              this.logger.log(`[HANDLE] Processing SessionFailed event`);
              this.logger.log(
                `[HANDLE] sessionType: ${(payload as SessionFailedPayload).sessionType}`,
              );
              this.logger.log(
                `[HANDLE] errorMessage: ${(payload as SessionFailedPayload).errorMessage}`,
              );
              if (this.sessionFailedHandler) {
                await this.sessionFailedHandler(
                  payload as SessionFailedPayload,
                );
                this.logger.log(`[HANDLE] SessionFailed handler completed`);
              } else {
                this.logger.warn(
                  `[HANDLE] No handler registered for SessionFailed`,
                );
              }
              break;

            default:
              this.logger.warn(`[RECEIVE] Unknown MPC topic: ${topic}`);
          }
        } catch (error) {
          this.logger.error(
            `[ERROR] Error processing MPC event from ${topic}`,
            error,
          );
          // Re-throw to trigger Kafka retry mechanism
          // This ensures messages are not marked as consumed until successfully processed
          throw error;
        }
      },
    });

    this.logger.log(`[START] Started consuming MPC events`);
  }
}
