import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Kafka, Consumer, EachMessagePayload } from 'kafkajs';
import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service';
import { Prisma } from '@prisma/client';
import { PRE_PLANTING_CDC_GROUP_ID } from '../../domain/constants';

/**
 * 预种 CDC 事件（Debezium ExtractNewRecordState 扁平格式）
 */
export interface PrePlantingCdcEvent {
  payload: {
    op: 'c' | 'u' | 'd' | 'r';
    before: any | null;
    after: any | null;
    table: string;
    source_ts_ms: number;
    deleted: boolean;
  };
  topic: string;
  offset: bigint;
}

/**
 * 事务性 handler（带后置回调支持）
 */
export type PrePlantingCdcHandler<T = void> = (
  event: PrePlantingCdcEvent,
  tx: Prisma.TransactionClient,
) => Promise<T>;

export type PostCommitCallback<T> = (result: T) => Promise<void>;

interface RegisteredHandler {
  tableName: string;
  handler: (event: PrePlantingCdcEvent) => Promise<void>;
}

/**
 * 预种 CDC 消费者服务
 *
 * [2026-02-17] 新增：完全独立于现有 CDCConsumerService
 *
 * === 隔离保证 ===
 * - 独立 Kafka consumer group：contribution-pre-planting-cdc
 * - 独立 CDC topics：cdc.pre-planting.public.*
 * - 独立 Debezium connector/replication slot/publication
 * - 幂等性：使用主 PrismaService 的 ProcessedCdcEvent 表（同 DB 事务保证一致性）
 *
 * === 与现有 CDCConsumerService 的区别 ===
 * - 无三阶段顺序消费（预种订单不依赖 user/referral 同步顺序，它们已由主 CDC 同步完成）
 * - 无收集-排序-处理（预种订单间无严格顺序依赖）
 * - 更简单的消费模式：直接消费 + 幂等处理
 */
@Injectable()
export class PrePlantingCdcConsumerService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(PrePlantingCdcConsumerService.name);
  private kafka: Kafka;
  private consumer: Consumer;
  private handlers: Map<string, (event: PrePlantingCdcEvent) => Promise<void>> = new Map();
  private isRunning = false;

  constructor(
    private readonly configService: ConfigService,
    private readonly prisma: PrismaService, // 主 PrismaService，用于幂等性 + 算力写入
  ) {
    const brokers = this.configService.get<string>('KAFKA_BROKERS', 'localhost:9092').split(',');

    this.kafka = new Kafka({
      clientId: 'contribution-service-pre-planting-cdc',
      brokers,
    });

    this.consumer = this.kafka.consumer({
      groupId: PRE_PLANTING_CDC_GROUP_ID,
    });
  }

  async onModuleInit() {
    // 等待 handler 注册后再启动（由 PrePlantingCdcDispatcher 调用 start()）
  }

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

  /**
   * 注册事务性 handler（带幂等保证 + 后置回调）
   *
   * 幂等性通过主 PrismaService 的 ProcessedCdcEvent 表实现，
   * 与业务逻辑在同一个 Serializable 事务中执行。
   */
  registerHandler<T>(
    tableName: string,
    handler: PrePlantingCdcHandler<T>,
    postCommitCallback?: PostCommitCallback<T>,
  ): void {
    const wrappedHandler = async (event: PrePlantingCdcEvent) => {
      const idempotencyKey = `${event.topic}:${event.offset}`;
      let result: T | null = null;
      let shouldCallback = false;

      try {
        await this.prisma.$transaction(async (tx) => {
          // 1. 幂等检查：插入 ProcessedCdcEvent（唯一约束防重复）
          try {
            await tx.processedCdcEvent.create({
              data: {
                sourceTopic: event.topic,
                offset: event.offset,
                tableName: event.payload.table,
                operation: event.payload.op,
              },
            });
          } catch (error: any) {
            if (error.code === 'P2002') {
              this.logger.debug(`[PRE-PLANTING-CDC] Skip duplicate: ${idempotencyKey}`);
              return;
            }
            throw error;
          }

          // 2. 执行业务逻辑
          result = await handler(event, tx);
          shouldCallback = true;

          this.logger.debug(`[PRE-PLANTING-CDC] Processed: ${idempotencyKey}`);
        }, {
          isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
          timeout: 60000,
        });

        // 3. 事务提交后执行后置回调
        if (shouldCallback && postCommitCallback && result !== null) {
          try {
            await postCommitCallback(result);
          } catch (callbackError) {
            this.logger.error(
              `[PRE-PLANTING-CDC] Post-commit callback failed: ${idempotencyKey}`,
              callbackError,
            );
          }
        }
      } catch (error: any) {
        if (error.code === 'P2002') {
          this.logger.debug(`[PRE-PLANTING-CDC] Skip duplicate (concurrent): ${idempotencyKey}`);
          return;
        }
        this.logger.error(`[PRE-PLANTING-CDC] Failed: ${idempotencyKey}`, error);
        throw error;
      }
    };

    this.handlers.set(tableName, wrappedHandler);
    this.logger.log(`[PRE-PLANTING-CDC] Registered handler for table: ${tableName}`);
  }

  /**
   * 启动消费者
   */
  async start(): Promise<void> {
    if (this.isRunning) {
      this.logger.warn('[PRE-PLANTING-CDC] Consumer already running');
      return;
    }

    try {
      await this.consumer.connect();

      // 订阅预种 CDC topics
      const topicOrders = this.configService.get<string>(
        'PRE_PLANTING_CDC_TOPIC_ORDERS',
        'cdc.pre-planting.public.pre_planting_orders',
      );
      const topicPositions = this.configService.get<string>(
        'PRE_PLANTING_CDC_TOPIC_POSITIONS',
        'cdc.pre-planting.public.pre_planting_positions',
      );
      const topicMerges = this.configService.get<string>(
        'PRE_PLANTING_CDC_TOPIC_MERGES',
        'cdc.pre-planting.public.pre_planting_merges',
      );

      await this.consumer.subscribe({
        topics: [topicOrders, topicPositions, topicMerges],
        fromBeginning: true,
      });

      this.isRunning = true;

      await this.consumer.run({
        eachMessage: async (payload: EachMessagePayload) => {
          await this.handleMessage(payload);
        },
      });

      this.logger.log(
        `[PRE-PLANTING-CDC] Consumer started, topics: [${topicOrders}, ${topicPositions}, ${topicMerges}]`,
      );
    } catch (error) {
      this.logger.error('[PRE-PLANTING-CDC] Failed to start consumer', error);
      // 不抛出错误，允许服务在没有 Kafka/预种 CDC 的情况下启动
    }
  }

  async stop(): Promise<void> {
    if (!this.isRunning) return;

    try {
      await this.consumer.disconnect();
      this.isRunning = false;
      this.logger.log('[PRE-PLANTING-CDC] Consumer stopped');
    } catch (error) {
      this.logger.error('[PRE-PLANTING-CDC] Failed to stop consumer', error);
    }
  }

  private async handleMessage(payload: EachMessagePayload): Promise<void> {
    const { topic, partition, message } = payload;

    try {
      if (!message.value) {
        this.logger.warn(`[PRE-PLANTING-CDC] Empty message: topic=${topic}, offset=${message.offset}`);
        return;
      }

      const rawData = JSON.parse(message.value.toString());

      // Debezium ExtractNewRecordState 扁平格式
      const op = rawData.__op || rawData.op;
      const table = rawData.__table;
      const sourceTsMs = rawData.__source_ts_ms || 0;
      const deleted = rawData.__deleted === 'true' || rawData.__deleted === true;

      const { __op, __table, __source_ts_ms, __deleted, ...businessData } = rawData;

      const event: PrePlantingCdcEvent = {
        payload: {
          op: op as 'c' | 'u' | 'd' | 'r',
          before: op === 'd' ? businessData : null,
          after: op !== 'd' ? businessData : null,
          table,
          source_ts_ms: sourceTsMs,
          deleted,
        },
        topic,
        offset: BigInt(message.offset),
      };

      // 从 topic 提取表名作为备选
      const parts = topic.split('.');
      const tableName = table || parts[parts.length - 1];

      const handler = this.handlers.get(tableName);
      if (handler) {
        await handler(event);
        this.logger.debug(
          `[PRE-PLANTING-CDC] Processed: table=${tableName}, op=${op}, offset=${message.offset}`,
        );
      } else {
        this.logger.warn(
          `[PRE-PLANTING-CDC] No handler for table: ${tableName}. ` +
          `Available: ${Array.from(this.handlers.keys()).join(', ')}`,
        );
      }
    } catch (error) {
      this.logger.error(
        `[PRE-PLANTING-CDC] Error: topic=${topic}, partition=${partition}, offset=${message.offset}`,
        error,
      );
    }
  }
}
