import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Kafka, Consumer, logLevel, EachMessagePayload } from 'kafkajs';
import { PrismaService } from '../persistence/prisma/prisma.service';
import { Decimal } from '@prisma/client/runtime/library';

/**
 * Debezium CDC 事件结构 (经过 ExtractNewRecordState 转换后)
 */

// planting_orders 表
interface CdcPlantingOrderPayload {
  order_id: string;
  order_no: string;
  user_id: string;
  account_sequence: string;
  tree_count: number;
  total_amount: string;
  selected_province?: string | null;
  selected_city?: string | null;
  status: string;
  pool_injection_batch_id?: string | null;
  created_at: string;
  paid_at?: string | null;
  fund_allocated_at?: string | null;
  mining_enabled_at?: string | null;
  __op: 'c' | 'u' | 'd' | 'r';
  __table: string;
  __source_ts_ms: number;
  __deleted?: string;
}

// planting_positions 表
interface CdcPlantingPositionPayload {
  position_id: string;
  user_id: string;
  total_tree_count: number;
  effective_tree_count: number;
  pending_tree_count: number;
  first_mining_start_at?: string | null;
  created_at: string;
  __op: 'c' | 'u' | 'd' | 'r';
  __table: string;
  __source_ts_ms: number;
  __deleted?: string;
}

// fund_allocations 表 (资金分配分类账)
interface CdcFundAllocationPayload {
  allocation_id: string;
  order_id: string;
  target_type: string;
  amount: string;
  target_account_id?: string | null;
  created_at: string;
  __op: 'c' | 'u' | 'd' | 'r';
  __table: string;
  __source_ts_ms: number;
  __deleted?: string;
}

// contract_signing_tasks 表
interface CdcContractSigningTaskPayload {
  task_id: string;
  order_no: string;
  contract_no: string;
  user_id: string;
  account_sequence: string;
  template_id: number;
  contract_version: string;
  tree_count: number;
  total_amount: string;
  province_code: string;
  province_name: string;
  city_code: string;
  city_name: string;
  status: string;
  expires_at: string;
  signed_at?: string | null;
  created_at: string;
  __op: 'c' | 'u' | 'd' | 'r';
  __table: string;
  __source_ts_ms: number;
  __deleted?: string;
}

type CdcPlantingPayload = CdcPlantingOrderPayload | CdcPlantingPositionPayload | CdcFundAllocationPayload | CdcContractSigningTaskPayload;

/**
 * Planting CDC 消费者服务
 *
 * 消费 Debezium 从 planting-service PostgreSQL 捕获的数据变更
 *
 * Topics:
 * - cdc.planting.public.planting_orders
 * - cdc.planting.public.planting_positions
 * - cdc.planting.public.contract_signing_tasks
 */
@Injectable()
export class PlantingCdcConsumerService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(PlantingCdcConsumerService.name);
  private kafka: Kafka;
  private consumer: Consumer;
  private isRunning = false;

  // CDC Topics
  private readonly cdcTopics = [
    'cdc.planting.public.planting_orders',
    'cdc.planting.public.planting_positions',
    'cdc.planting.public.contract_signing_tasks',
    'cdc.planting.public.fund_allocations',
  ];
  private readonly consumerGroup: string;

  constructor(
    private readonly configService: ConfigService,
    private readonly prisma: PrismaService,
  ) {
    const brokers = (this.configService.get<string>('KAFKA_BROKERS', 'localhost:9092')).split(',');
    const clientId = this.configService.get<string>('KAFKA_CLIENT_ID', 'admin-service');
    this.consumerGroup = this.configService.get<string>('KAFKA_PLANTING_CDC_GROUP', 'admin-service-planting-cdc');

    this.kafka = new Kafka({
      clientId: `${clientId}-planting-cdc`,
      brokers,
      logLevel: logLevel.WARN,
    });

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

    this.logger.log(`[Planting-CDC] Configured to consume topics: ${this.cdcTopics.join(', ')}`);
  }

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

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

  async start(): Promise<void> {
    if (this.isRunning) {
      this.logger.warn('[Planting-CDC] Consumer already running');
      return;
    }

    try {
      this.logger.log('[Planting-CDC] Connecting to Kafka...');
      await this.consumer.connect();

      for (const topic of this.cdcTopics) {
        await this.consumer.subscribe({
          topic,
          fromBeginning: false,
        });
      }

      this.logger.log(`[Planting-CDC] Subscribed to topics: ${this.cdcTopics.join(', ')}`);

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

      this.isRunning = true;
      this.logger.log('[Planting-CDC] Consumer started successfully');
    } catch (error) {
      this.logger.error('[Planting-CDC] Failed to start consumer:', error);
    }
  }

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

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

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

    if (!message.value) {
      this.logger.warn(`[Planting-CDC] Empty message from ${topic}:${partition}`);
      return;
    }

    try {
      const data = JSON.parse(message.value.toString()) as CdcPlantingPayload;
      const operation = data.__op;
      const table = data.__table;

      this.logger.debug(
        `[Planting-CDC] Received ${operation} event for table ${table}`
      );

      // 幂等性检查
      const eventId = `planting-cdc:${topic}:${partition}:${message.offset}`;
      if (await this.isEventProcessed(eventId)) {
        this.logger.debug(`[Planting-CDC] Event ${eventId} already processed, skipping`);
        return;
      }

      // 根据表名处理不同的事件
      switch (table) {
        case 'planting_orders':
          await this.processPlantingOrderEvent(data as CdcPlantingOrderPayload);
          break;
        case 'planting_positions':
          await this.processPlantingPositionEvent(data as CdcPlantingPositionPayload);
          break;
        case 'contract_signing_tasks':
          await this.processContractSigningTaskEvent(data as CdcContractSigningTaskPayload);
          break;
        case 'fund_allocations':
          await this.processFundAllocationEvent(data as CdcFundAllocationPayload);
          break;
        default:
          this.logger.warn(`[Planting-CDC] Unknown table: ${table}`);
      }

      // 记录已处理
      await this.markEventProcessed(eventId, `planting-cdc:${table}:${operation}`);

      this.logger.log(
        `[Planting-CDC] ✓ Processed ${operation} for table: ${table}`
      );
    } catch (error) {
      this.logger.error(`[Planting-CDC] Failed to process message:`, error);
      throw error; // 让 KafkaJS 重试
    }
  }

  // ==================== planting_orders 处理 ====================

  private async processPlantingOrderEvent(data: CdcPlantingOrderPayload): Promise<void> {
    const operation = data.__op;
    const isDeleted = data.__deleted === 'true';

    if (operation === 'd' || isDeleted) {
      await this.handlePlantingOrderDelete(data);
    } else if (operation === 'c' || operation === 'r') {
      await this.handlePlantingOrderCreateOrSnapshot(data);
    } else if (operation === 'u') {
      await this.handlePlantingOrderUpdate(data);
    }
  }

  private async handlePlantingOrderCreateOrSnapshot(data: CdcPlantingOrderPayload): Promise<void> {
    await this.prisma.plantingOrderQueryView.upsert({
      where: { id: BigInt(data.order_id) },
      create: {
        id: BigInt(data.order_id),
        orderNo: data.order_no,
        userId: BigInt(data.user_id),
        accountSequence: data.account_sequence,
        treeCount: data.tree_count,
        totalAmount: new Decimal(data.total_amount),
        selectedProvince: data.selected_province || null,
        selectedCity: data.selected_city || null,
        status: data.status,
        poolInjectionBatchId: data.pool_injection_batch_id ? BigInt(data.pool_injection_batch_id) : null,
        createdAt: new Date(data.created_at),
        paidAt: data.paid_at ? new Date(data.paid_at) : null,
        fundAllocatedAt: data.fund_allocated_at ? new Date(data.fund_allocated_at) : null,
        miningEnabledAt: data.mining_enabled_at ? new Date(data.mining_enabled_at) : null,
        syncedAt: new Date(),
      },
      update: {
        orderNo: data.order_no,
        userId: BigInt(data.user_id),
        accountSequence: data.account_sequence,
        treeCount: data.tree_count,
        totalAmount: new Decimal(data.total_amount),
        selectedProvince: data.selected_province || null,
        selectedCity: data.selected_city || null,
        status: data.status,
        poolInjectionBatchId: data.pool_injection_batch_id ? BigInt(data.pool_injection_batch_id) : null,
        paidAt: data.paid_at ? new Date(data.paid_at) : null,
        fundAllocatedAt: data.fund_allocated_at ? new Date(data.fund_allocated_at) : null,
        miningEnabledAt: data.mining_enabled_at ? new Date(data.mining_enabled_at) : null,
        syncedAt: new Date(),
      },
    });

    this.logger.log(`[Planting-CDC] Created/Snapshot planting order: ${data.order_no}`);
  }

  private async handlePlantingOrderUpdate(data: CdcPlantingOrderPayload): Promise<void> {
    const id = BigInt(data.order_id);
    const exists = await this.prisma.plantingOrderQueryView.findUnique({
      where: { id },
      select: { id: true },
    });

    if (!exists) {
      await this.handlePlantingOrderCreateOrSnapshot(data);
      return;
    }

    await this.prisma.plantingOrderQueryView.update({
      where: { id },
      data: {
        selectedProvince: data.selected_province || null,
        selectedCity: data.selected_city || null,
        status: data.status,
        poolInjectionBatchId: data.pool_injection_batch_id ? BigInt(data.pool_injection_batch_id) : null,
        paidAt: data.paid_at ? new Date(data.paid_at) : null,
        fundAllocatedAt: data.fund_allocated_at ? new Date(data.fund_allocated_at) : null,
        miningEnabledAt: data.mining_enabled_at ? new Date(data.mining_enabled_at) : null,
        syncedAt: new Date(),
      },
    });

    this.logger.log(`[Planting-CDC] Updated planting order: ${data.order_no}`);
  }

  private async handlePlantingOrderDelete(data: CdcPlantingOrderPayload): Promise<void> {
    const id = BigInt(data.order_id);

    try {
      await this.prisma.plantingOrderQueryView.delete({
        where: { id },
      });
      this.logger.log(`[Planting-CDC] Deleted planting order: ${data.order_no}`);
    } catch {
      this.logger.warn(`[Planting-CDC] Planting order not found for delete: ${data.order_no}`);
    }
  }

  // ==================== planting_positions 处理 ====================

  private async processPlantingPositionEvent(data: CdcPlantingPositionPayload): Promise<void> {
    const operation = data.__op;
    const isDeleted = data.__deleted === 'true';

    if (operation === 'd' || isDeleted) {
      await this.handlePlantingPositionDelete(data);
    } else if (operation === 'c' || operation === 'r') {
      await this.handlePlantingPositionCreateOrSnapshot(data);
    } else if (operation === 'u') {
      await this.handlePlantingPositionUpdate(data);
    }
  }

  private async handlePlantingPositionCreateOrSnapshot(data: CdcPlantingPositionPayload): Promise<void> {
    await this.prisma.plantingPositionQueryView.upsert({
      where: { id: BigInt(data.position_id) },
      create: {
        id: BigInt(data.position_id),
        userId: BigInt(data.user_id),
        totalTreeCount: data.total_tree_count,
        effectiveTreeCount: data.effective_tree_count,
        pendingTreeCount: data.pending_tree_count,
        firstMiningStartAt: data.first_mining_start_at ? new Date(data.first_mining_start_at) : null,
        createdAt: new Date(data.created_at),
        syncedAt: new Date(),
      },
      update: {
        userId: BigInt(data.user_id),
        totalTreeCount: data.total_tree_count,
        effectiveTreeCount: data.effective_tree_count,
        pendingTreeCount: data.pending_tree_count,
        firstMiningStartAt: data.first_mining_start_at ? new Date(data.first_mining_start_at) : null,
        syncedAt: new Date(),
      },
    });

    this.logger.log(`[Planting-CDC] Created/Snapshot planting position for user: ${data.user_id}`);
  }

  private async handlePlantingPositionUpdate(data: CdcPlantingPositionPayload): Promise<void> {
    const id = BigInt(data.position_id);
    const exists = await this.prisma.plantingPositionQueryView.findUnique({
      where: { id },
      select: { id: true },
    });

    if (!exists) {
      await this.handlePlantingPositionCreateOrSnapshot(data);
      return;
    }

    await this.prisma.plantingPositionQueryView.update({
      where: { id },
      data: {
        totalTreeCount: data.total_tree_count,
        effectiveTreeCount: data.effective_tree_count,
        pendingTreeCount: data.pending_tree_count,
        firstMiningStartAt: data.first_mining_start_at ? new Date(data.first_mining_start_at) : null,
        syncedAt: new Date(),
      },
    });

    this.logger.log(`[Planting-CDC] Updated planting position for user: ${data.user_id}`);
  }

  private async handlePlantingPositionDelete(data: CdcPlantingPositionPayload): Promise<void> {
    const id = BigInt(data.position_id);

    try {
      await this.prisma.plantingPositionQueryView.delete({
        where: { id },
      });
      this.logger.log(`[Planting-CDC] Deleted planting position for user: ${data.user_id}`);
    } catch {
      this.logger.warn(`[Planting-CDC] Planting position not found for delete: ${data.user_id}`);
    }
  }

  // ==================== contract_signing_tasks 处理 ====================

  private async processContractSigningTaskEvent(data: CdcContractSigningTaskPayload): Promise<void> {
    const operation = data.__op;
    const isDeleted = data.__deleted === 'true';

    if (operation === 'd' || isDeleted) {
      await this.handleContractSigningTaskDelete(data);
    } else if (operation === 'c' || operation === 'r') {
      await this.handleContractSigningTaskCreateOrSnapshot(data);
    } else if (operation === 'u') {
      await this.handleContractSigningTaskUpdate(data);
    }
  }

  private async handleContractSigningTaskCreateOrSnapshot(data: CdcContractSigningTaskPayload): Promise<void> {
    await this.prisma.contractSigningTaskQueryView.upsert({
      where: { id: BigInt(data.task_id) },
      create: {
        id: BigInt(data.task_id),
        orderNo: data.order_no,
        contractNo: data.contract_no,
        userId: BigInt(data.user_id),
        accountSequence: data.account_sequence,
        contractVersion: data.contract_version,
        treeCount: data.tree_count,
        totalAmount: new Decimal(data.total_amount),
        provinceCode: data.province_code,
        provinceName: data.province_name,
        cityCode: data.city_code,
        cityName: data.city_name,
        status: data.status,
        expiresAt: new Date(data.expires_at),
        signedAt: data.signed_at ? new Date(data.signed_at) : null,
        createdAt: new Date(data.created_at),
        syncedAt: new Date(),
      },
      update: {
        orderNo: data.order_no,
        contractNo: data.contract_no,
        userId: BigInt(data.user_id),
        accountSequence: data.account_sequence,
        contractVersion: data.contract_version,
        treeCount: data.tree_count,
        totalAmount: new Decimal(data.total_amount),
        provinceCode: data.province_code,
        provinceName: data.province_name,
        cityCode: data.city_code,
        cityName: data.city_name,
        status: data.status,
        expiresAt: new Date(data.expires_at),
        signedAt: data.signed_at ? new Date(data.signed_at) : null,
        syncedAt: new Date(),
      },
    });

    this.logger.log(`[Planting-CDC] Created/Snapshot contract signing task: ${data.contract_no}`);
  }

  private async handleContractSigningTaskUpdate(data: CdcContractSigningTaskPayload): Promise<void> {
    const id = BigInt(data.task_id);
    const exists = await this.prisma.contractSigningTaskQueryView.findUnique({
      where: { id },
      select: { id: true },
    });

    if (!exists) {
      await this.handleContractSigningTaskCreateOrSnapshot(data);
      return;
    }

    await this.prisma.contractSigningTaskQueryView.update({
      where: { id },
      data: {
        status: data.status,
        signedAt: data.signed_at ? new Date(data.signed_at) : null,
        syncedAt: new Date(),
      },
    });

    this.logger.log(`[Planting-CDC] Updated contract signing task: ${data.contract_no}`);
  }

  private async handleContractSigningTaskDelete(data: CdcContractSigningTaskPayload): Promise<void> {
    const id = BigInt(data.task_id);

    try {
      await this.prisma.contractSigningTaskQueryView.delete({
        where: { id },
      });
      this.logger.log(`[Planting-CDC] Deleted contract signing task: ${data.contract_no}`);
    } catch {
      this.logger.warn(`[Planting-CDC] Contract signing task not found for delete: ${data.contract_no}`);
    }
  }

  // ==================== fund_allocations 处理 ====================

  private async processFundAllocationEvent(data: CdcFundAllocationPayload): Promise<void> {
    const operation = data.__op;
    const isDeleted = data.__deleted === 'true';

    // 资金分配是 append-only，通常只有 create 和 snapshot
    if (operation === 'd' || isDeleted) {
      await this.handleFundAllocationDelete(data);
    } else if (operation === 'c' || operation === 'r') {
      await this.handleFundAllocationCreate(data);
    } else if (operation === 'u') {
      await this.handleFundAllocationUpdate(data);
    }
  }

  private async handleFundAllocationCreate(data: CdcFundAllocationPayload): Promise<void> {
    await this.prisma.fundAllocationView.upsert({
      where: { id: BigInt(data.allocation_id) },
      create: {
        id: BigInt(data.allocation_id),
        orderId: BigInt(data.order_id),
        targetType: data.target_type,
        amount: new Decimal(data.amount),
        targetAccountId: data.target_account_id || null,
        createdAt: new Date(data.created_at),
        syncedAt: new Date(),
      },
      update: {
        orderId: BigInt(data.order_id),
        targetType: data.target_type,
        amount: new Decimal(data.amount),
        targetAccountId: data.target_account_id || null,
        syncedAt: new Date(),
      },
    });

    this.logger.log(`[Planting-CDC] Created fund allocation: ${data.allocation_id} (${data.target_type})`);
  }

  private async handleFundAllocationUpdate(data: CdcFundAllocationPayload): Promise<void> {
    await this.handleFundAllocationCreate(data);
    this.logger.log(`[Planting-CDC] Updated fund allocation: ${data.allocation_id}`);
  }

  private async handleFundAllocationDelete(data: CdcFundAllocationPayload): Promise<void> {
    const id = BigInt(data.allocation_id);

    try {
      await this.prisma.fundAllocationView.delete({
        where: { id },
      });
      this.logger.log(`[Planting-CDC] Deleted fund allocation: ${data.allocation_id}`);
    } catch {
      this.logger.warn(`[Planting-CDC] Fund allocation not found for delete: ${data.allocation_id}`);
    }
  }

  // ==================== Helper Methods ====================

  private async isEventProcessed(eventId: string): Promise<boolean> {
    const count = await this.prisma.processedEvent.count({
      where: { eventId },
    });
    return count > 0;
  }

  private async markEventProcessed(eventId: string, eventType: string): Promise<void> {
    await this.prisma.processedEvent.create({
      data: {
        eventId,
        eventType,
        processedAt: new Date(),
      },
    });
  }

  /**
   * 获取消费者状态
   */
  getStatus(): { isRunning: boolean; topics: string[]; consumerGroup: string } {
    return {
      isRunning: this.isRunning,
      topics: this.cdcTopics,
      consumerGroup: this.consumerGroup,
    };
  }
}
