/**
 * KeygenRequested Event Handler
 *
 * Handles keygen requests from identity-service via Kafka.
 * Processes the keygen and publishes completion/failure events.
 *
 * Idempotency Protection:
 * - Uses distributed lock to prevent duplicate keygen for the same user
 * - Lock is held for the entire keygen process (up to 5 minutes)
 * - If lock cannot be acquired, the request is skipped (another instance is processing)
 */

import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { MPCCoordinatorService } from '../services/mpc-coordinator.service';
import { EventPublisherService } from '../../infrastructure/messaging/kafka/event-publisher.service';
import {
  EventConsumerService,
  MPC_CONSUME_TOPICS,
  KeygenRequestedPayload,
} from '../../infrastructure/messaging/kafka/event-consumer.service';
import { BackupClientService } from '../../infrastructure/external/backup';
import { BlockchainClientService } from '../../infrastructure/external/blockchain';
import { KeygenStartedEvent } from '../../domain/events/keygen-started.event';
import { KeygenCompletedEvent } from '../../domain/events/keygen-completed.event';
import { SessionFailedEvent } from '../../domain/events/session-failed.event';
import { SessionType } from '../../domain/enums';
import { DistributedLockService } from '../../infrastructure/redis/lock/distributed-lock.service';

// Lock TTL for keygen process: 5 minutes (keygen may take a while)
const KEYGEN_LOCK_TTL_MS = 5 * 60 * 1000;

@Injectable()
export class KeygenRequestedHandler implements OnModuleInit {
  private readonly logger = new Logger(KeygenRequestedHandler.name);

  constructor(
    private readonly eventConsumer: EventConsumerService,
    private readonly eventPublisher: EventPublisherService,
    private readonly mpcCoordinator: MPCCoordinatorService,
    private readonly backupClient: BackupClientService,
    private readonly blockchainClient: BlockchainClientService,
    private readonly distributedLock: DistributedLockService,
  ) {}

  async onModuleInit() {
    this.logger.log(`[INIT] KeygenRequestedHandler initializing...`);
    await this.eventConsumer.subscribe(
      MPC_CONSUME_TOPICS.KEYGEN_REQUESTED,
      this.handleMessage.bind(this),
    );
    this.logger.log(`[INIT] Subscribed to ${MPC_CONSUME_TOPICS.KEYGEN_REQUESTED}`);
  }

  private async handleMessage(topic: string, payload: Record<string, unknown>): Promise<void> {
    this.logger.log(`[HANDLE] Received keygen request from topic: ${topic}`);
    this.logger.log(`[HANDLE] Payload: ${JSON.stringify(payload)}`);

    const data = payload as unknown as KeygenRequestedPayload;
    const { sessionId, userId, accountSequence, username, threshold, totalParties, requireDelegate } = data;

    this.logger.log(`[HANDLE] Parsed request: sessionId=${sessionId}`);
    this.logger.log(`[HANDLE] userId=${userId}, username=${username}`);
    this.logger.log(`[HANDLE] threshold=${threshold}, totalParties=${totalParties}, requireDelegate=${requireDelegate}`);

    // Idempotency check: Try to acquire lock for this user's keygen
    // If lock is already held, another keygen is in progress for this user
    const lockKey = `keygen:user:${userId}`;
    const lock = await this.distributedLock.acquire(lockKey, {
      ttl: KEYGEN_LOCK_TTL_MS,
      retryCount: 1, // Only try once, don't wait
      retryDelay: 0,
    });

    if (!lock) {
      this.logger.warn(
        `[HANDLE] Keygen already in progress for user: ${userId}, skipping duplicate request`,
      );
      return; // Skip this request, another instance is handling it
    }

    this.logger.log(`[HANDLE] Acquired keygen lock for user: ${userId}`);

    try {
      // Step 1: Create keygen session via mpc-system
      const createResult = await this.mpcCoordinator.createKeygenSession({
        username,
        thresholdN: totalParties,
        thresholdT: threshold,
        requireDelegate,
      });

      const mpcSessionId = createResult.sessionId;
      this.logger.log(`Keygen session created in mpc-system: ${mpcSessionId}`);

      // Step 1.5: Publish KeygenStarted event to notify identity-service
      const startedEvent = new KeygenStartedEvent(sessionId, userId, username, mpcSessionId);
      await this.eventPublisher.publish(startedEvent);
      this.logger.log(`Published KeygenStarted event: userId=${userId}, mpcSessionId=${mpcSessionId}`);

      // Step 2: Poll for completion (with max retries)
      const result = await this.pollKeygenCompletion(mpcSessionId, 150, 2000);

      if (result.status === 'completed' && result.publicKey) {
        // Cache public key
        await this.mpcCoordinator.savePublicKeyCache(username, result.publicKey);

        // Step 3: Derive addresses for all chains (BSC, KAVA, DST) via blockchain-service
        let derivedAddresses: { chainType: string; address: string }[] = [];
        try {
          const deriveResult = await this.blockchainClient.deriveAddresses({
            userId,
            accountSequence,  // 账户序列号，格式: D + YYMMDD + 5位序号，如 D2512110008
            publicKey: result.publicKey,
          });
          derivedAddresses = deriveResult.addresses;
          this.logger.log(`Addresses derived: userId=${userId}, count=${derivedAddresses.length}`);
        } catch (deriveError) {
          // 地址派生失败不阻塞主流程，但记录错误
          this.logger.error(`Failed to derive addresses: userId=${userId}`, deriveError);
        }

        // Save delegate share to backup-service if exists
        if (result.delegateShare) {
          // 1. 保存到本地缓存（用于签名时快速访问）
          await this.mpcCoordinator.saveDelegateShare({
            username,
            partyId: result.delegateShare.partyId,
            partyIndex: result.delegateShare.partyIndex,
            encryptedShare: result.delegateShare.encryptedShare,
          });

          // 2. 存储到 backup-service（用于灾备恢复）
          try {
            await this.backupClient.storeBackupShare({
              userId,
              accountSequence,
              username,
              publicKey: result.publicKey,
              partyId: result.delegateShare.partyId,
              partyIndex: result.delegateShare.partyIndex,
              encryptedShare: result.delegateShare.encryptedShare,
            });
            this.logger.log(`Delegate share stored to backup-service: userId=${userId}, accountSequence=${accountSequence}`);
          } catch (backupError) {
            // 备份失败不阻塞主流程，但记录错误
            this.logger.error(`Failed to store delegate share to backup-service: userId=${userId}`, backupError);
          }
        }

        // Publish success event
        const completedEvent = new KeygenCompletedEvent(
          sessionId, // Original session ID from identity-service
          result.delegateShare?.partyId || '',
          result.publicKey,
          mpcSessionId,
          `${threshold}-of-${totalParties}`,
        );

        // Add extra payload for identity-service
        (completedEvent as any).extraPayload = {
          userId,
          accountSequence,  // 账户序列号，格式: D + YYMMDD + 5位序号，如 D2512110008
          username,
          delegateShare: result.delegateShare,
          derivedAddresses, // BSC, KAVA, DST addresses
          serverParties: [], // mpc-system manages this
        };

        await this.eventPublisher.publishWithRetry(completedEvent);
        this.logger.log(`Keygen completed: userId=${userId}, publicKey=${result.publicKey}`);
      } else {
        // Publish failure event
        const failedEvent = new SessionFailedEvent(
          sessionId,
          '', // partyId
          SessionType.KEYGEN,
          `Keygen failed with status: ${result.status}`,
        );
        (failedEvent as any).extraPayload = { userId, username };

        await this.eventPublisher.publishWithRetry(failedEvent);
        this.logger.warn(`Keygen failed: userId=${userId}, status=${result.status}`);
      }
    } catch (error) {
      this.logger.error(`Keygen processing error: userId=${userId}`, error);

      // Publish failure event
      const failedEvent = new SessionFailedEvent(
        sessionId,
        '', // partyId
        SessionType.KEYGEN,
        error instanceof Error ? error.message : 'Unknown error',
      );
      (failedEvent as any).extraPayload = { userId, username };

      try {
        await this.eventPublisher.publishWithRetry(failedEvent);
      } catch (publishError) {
        this.logger.error('Failed to publish failure event', publishError);
      }
    } finally {
      // Always release the lock when done (success or failure)
      await lock.release();
      this.logger.log(`[HANDLE] Released keygen lock for user: ${userId}`);
    }
  }

  private async pollKeygenCompletion(
    sessionId: string,
    maxAttempts: number,
    intervalMs: number,
  ): Promise<{
    status: string;
    publicKey?: string;
    delegateShare?: {
      partyId: string;
      partyIndex: number;
      encryptedShare: string;
    };
  }> {
    for (let i = 0; i < maxAttempts; i++) {
      const status = await this.mpcCoordinator.getKeygenStatus(sessionId);

      if (status.status === 'completed') {
        return {
          status: 'completed',
          publicKey: status.publicKey,
          delegateShare: status.delegateShare,
        };
      }

      if (status.status === 'failed' || status.status === 'expired') {
        return { status: status.status };
      }

      await this.sleep(intervalMs);
    }

    return { status: 'timeout' };
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}
