import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { ContributionCalculationService } from '../services/contribution-calculation.service';
import { SnapshotService } from '../services/snapshot.service';
import { BonusClaimService } from '../services/bonus-claim.service';
import { ContributionRecordRepository } from '../../infrastructure/persistence/repositories/contribution-record.repository';
import { ContributionAccountRepository } from '../../infrastructure/persistence/repositories/contribution-account.repository';
import { OutboxRepository } from '../../infrastructure/persistence/repositories/outbox.repository';
import { KafkaProducerService } from '../../infrastructure/kafka/kafka-producer.service';
import { RedisService } from '../../infrastructure/redis/redis.service';
import { CDCConsumerService } from '../../infrastructure/kafka/cdc-consumer.service';
import { ContributionAccountUpdatedEvent } from '../../domain/events';

/**
 * 算力相关定时任务
 */
@Injectable()
export class ContributionScheduler implements OnModuleInit {
  private readonly logger = new Logger(ContributionScheduler.name);
  private readonly LOCK_KEY = 'contribution:scheduler:lock';

  constructor(
    private readonly calculationService: ContributionCalculationService,
    private readonly snapshotService: SnapshotService,
    private readonly bonusClaimService: BonusClaimService,
    private readonly contributionRecordRepository: ContributionRecordRepository,
    private readonly contributionAccountRepository: ContributionAccountRepository,
    private readonly outboxRepository: OutboxRepository,
    private readonly kafkaProducer: KafkaProducerService,
    private readonly redis: RedisService,
    private readonly cdcConsumer: CDCConsumerService,
  ) {}

  /**
   * CDC 初始同步是否完成（用户、推荐、认种三阶段全部完成）
   */
  private isCdcReady(): boolean {
    return this.cdcConsumer.getSyncStatus().allPhasesCompleted;
  }

  async onModuleInit() {
    this.logger.log('Contribution scheduler initialized, waiting for CDC initial sync to complete...');
  }

  /**
   * 每分钟处理未处理的认种记录
   */
  @Cron(CronExpression.EVERY_MINUTE)
  async processUnprocessedAdoptions(): Promise<void> {
    if (!this.isCdcReady()) {
      this.logger.debug('[CDC-Gate] processUnprocessedAdoptions skipped: CDC initial sync not yet completed');
      return;
    }

    const lockValue = await this.redis.acquireLock(`${this.LOCK_KEY}:process`, 55);
    if (!lockValue) {
      return; // 其他实例正在处理
    }

    try {
      const processed = await this.calculationService.processUndistributedAdoptions(100);
      if (processed > 0) {
        this.logger.log(`Processed ${processed} unprocessed adoptions`);
      }
    } catch (error) {
      this.logger.error('Failed to process unprocessed adoptions', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:process`, lockValue);
    }
  }

  /**
   * 每天凌晨1点创建每日快照
   */
  @Cron('0 1 * * *')
  async createDailySnapshot(): Promise<void> {
    const lockValue = await this.redis.acquireLock(`${this.LOCK_KEY}:snapshot`, 300);
    if (!lockValue) {
      return;
    }

    try {
      // 创建前一天的快照
      const yesterday = new Date();
      yesterday.setDate(yesterday.getDate() - 1);

      await this.snapshotService.createDailySnapshot(yesterday);
      this.logger.log(`Daily snapshot created for ${yesterday.toISOString().split('T')[0]}`);
    } catch (error) {
      this.logger.error('Failed to create daily snapshot', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:snapshot`, lockValue);
    }
  }

  /**
   * 每天凌晨2点检查过期的算力记录
   */
  @Cron('0 2 * * *')
  async processExpiredRecords(): Promise<void> {
    const lockValue = await this.redis.acquireLock(`${this.LOCK_KEY}:expire`, 300);
    if (!lockValue) {
      return;
    }

    try {
      const now = new Date();
      const expiredRecords = await this.contributionRecordRepository.findExpiredRecords(now, 1000);

      if (expiredRecords.length > 0) {
        const ids = expiredRecords.map((r) => r.id).filter((id): id is bigint => id !== null);
        await this.contributionRecordRepository.markAsExpired(ids);
        this.logger.log(`Marked ${ids.length} contribution records as expired`);

        // TODO: 需要相应地减少账户的算力值
      }
    } catch (error) {
      this.logger.error('Failed to process expired records', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:expire`, lockValue);
    }
  }

  /**
   * 每30秒发布 Outbox 中的事件
   * 使用 4 小时最大退避策略处理失败
   */
  @Cron('*/30 * * * * *')
  async publishOutboxEvents(): Promise<void> {
    const lockValue = await this.redis.acquireLock(`${this.LOCK_KEY}:outbox`, 25);
    if (!lockValue) {
      return;
    }

    try {
      const events = await this.outboxRepository.findUnprocessed(100);

      if (events.length === 0) {
        return;
      }

      const successIds: bigint[] = [];

      for (const event of events) {
        try {
          // 使用事件中指定的 topic，而不是拼接
          await this.kafkaProducer.emit(event.topic, {
            key: event.key,
            value: event.payload,
          });
          successIds.push(event.id);
        } catch (error) {
          // 记录失败，使用退避策略重试
          const errorMessage = error instanceof Error ? error.message : String(error);
          await this.outboxRepository.markAsFailed(event.id, errorMessage);
          this.logger.warn(`Event ${event.id} failed, will retry with backoff: ${errorMessage}`);
        }
      }

      // 标记成功发送的事件为已处理
      if (successIds.length > 0) {
        await this.outboxRepository.markAsProcessed(successIds);
        this.logger.debug(`Published ${successIds.length} outbox events`);
      }
    } catch (error) {
      this.logger.error('Failed to publish outbox events', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:outbox`, lockValue);
    }
  }

  /**
   * 每天凌晨3点清理已处理的 Outbox 事件（保留7天）
   */
  @Cron('0 3 * * *')
  async cleanupOutbox(): Promise<void> {
    const lockValue = await this.redis.acquireLock(`${this.LOCK_KEY}:cleanup`, 300);
    if (!lockValue) {
      return;
    }

    try {
      const sevenDaysAgo = new Date();
      sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

      const deleted = await this.outboxRepository.deleteProcessed(sevenDaysAgo);
      if (deleted > 0) {
        this.logger.log(`Cleaned up ${deleted} processed outbox events`);
      }
    } catch (error) {
      this.logger.error('Failed to cleanup outbox', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:cleanup`, lockValue);
    }
  }

  /**
   * 每10分钟增量发布最近更新的贡献值账户事件
   * 只同步过去15分钟内有变更的账户，作为实时同步的补充
   */
  @Cron('*/10 * * * *')
  async publishRecentlyUpdatedAccounts(): Promise<void> {
    if (!this.isCdcReady()) {
      this.logger.debug('[CDC-Gate] publishRecentlyUpdatedAccounts skipped: CDC initial sync not yet completed');
      return;
    }

    const lockValue = await this.redis.acquireLock(`${this.LOCK_KEY}:incremental-sync`, 540); // 9分钟锁
    if (!lockValue) {
      return;
    }

    try {
      // 查找过去15分钟内更新的账户（比10分钟多5分钟余量，避免遗漏边界情况）
      const fifteenMinutesAgo = new Date(Date.now() - 15 * 60 * 1000);

      const accounts = await this.contributionAccountRepository.findRecentlyUpdated(fifteenMinutesAgo, 500);

      if (accounts.length === 0) {
        return;
      }

      const events = accounts.map((account) => {
        const event = new ContributionAccountUpdatedEvent(
          account.accountSequence,
          account.personalContribution.value.toString(),
          account.totalLevelPending.value.toString(),
          account.totalBonusPending.value.toString(),
          account.effectiveContribution.value.toString(),
          account.effectiveContribution.value.toString(),
          account.hasAdopted,
          account.directReferralAdoptedCount,
          account.unlockedLevelDepth,
          account.unlockedBonusTiers,
          account.createdAt,
        );

        return {
          aggregateType: ContributionAccountUpdatedEvent.AGGREGATE_TYPE,
          aggregateId: account.accountSequence,
          eventType: ContributionAccountUpdatedEvent.EVENT_TYPE,
          payload: event.toPayload(),
        };
      });

      await this.outboxRepository.saveMany(events);

      this.logger.log(`Incremental sync: published ${accounts.length} recently updated accounts`);
    } catch (error) {
      this.logger.error('Failed to publish recently updated accounts', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:incremental-sync`, lockValue);
    }
  }

  /**
   * 每10分钟回收已解锁层级的 LEVEL_OVERFLOW 记录
   * 处理场景：下级认种时上级 unlocked_level_depth 不足导致 overflow，
   *           上级后续解锁后这些 PENDING 记录需要被回收
   */
  @Cron('*/10 * * * *')
  async processLevelOverflowReclaim(): Promise<void> {
    if (!this.isCdcReady()) {
      this.logger.debug('[CDC-Gate] processLevelOverflowReclaim skipped: CDC initial sync not yet completed');
      return;
    }

    const lockValue = await this.redis.acquireLock(`${this.LOCK_KEY}:overflow-reclaim`, 540); // 9分钟锁
    if (!lockValue) {
      return;
    }

    try {
      const reclaimed = await this.bonusClaimService.reclaimLevelOverflows();
      if (reclaimed > 0) {
        this.logger.log(`Level overflow reclaim: ${reclaimed} records reclaimed`);
      }
    } catch (error) {
      this.logger.error('Failed to process level overflow reclaim', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:overflow-reclaim`, lockValue);
    }
  }

  /**
   * 每10分钟扫描并补发未完全解锁的贡献值（全量处理，无数量上限）
   *
   * 处理场景：
   *   1. 下级先于上级认种：下级认种时上级层级/奖励档位不足，贡献值暂存为 LEVEL_OVERFLOW，
   *      待上级后续达到解锁条件后由本任务补发给上级。
   *   2. 预种用户（预先支付费用但稍后正式认种）：其直推下级在 synced_adoptions 中的
   *      status = MINING_ENABLED 后，本任务会检测到上级解锁条件满足并补发对应算力。
   *
   * 解锁规则（见 ContributionCalculatorService）：
   *   - 层级解锁：直推认种数 ≥ 1 → 5级，≥ 3 → 10级，≥ 5 → 15级（满解锁）
   *   - 奖励档位：hasAdopted → 1档，直推 ≥ 2 → 2档，直推 ≥ 4 → 3档（满解锁）
   *
   * 注意：全量处理（无 LIMIT）避免账户因排序靠后而被永久跳过（starvation 问题）。
   * Redis 锁 9 分钟，每 10 分钟触发一次，保证同一时间只有一个实例运行。
   */
  @Cron('*/10 * * * *')
  async processContributionBackfill(): Promise<void> {
    if (!this.isCdcReady()) {
      this.logger.debug('[CDC-Gate] processContributionBackfill skipped: CDC initial sync not yet completed');
      return;
    }

    const lockValue = await this.redis.acquireLock(`${this.LOCK_KEY}:backfill`, 540); // 9分钟锁
    if (!lockValue) {
      return;
    }

    try {
      this.logger.log('Starting contribution backfill scan...');

      // 查找解锁状态不完整的账户（已认种但层级<15或奖励档位<3）
      const accounts = await this.contributionAccountRepository.findAccountsWithIncompleteUnlock();

      if (accounts.length === 0) {
        this.logger.debug('No accounts with incomplete unlock status found');
        return;
      }

      this.logger.log(`Found ${accounts.length} accounts with incomplete unlock status`);

      let backfilledCount = 0;
      let errorCount = 0;

      for (const account of accounts) {
        try {
          const hasBackfill = await this.bonusClaimService.processBackfillForAccount(account.accountSequence);
          if (hasBackfill) {
            backfilledCount++;
          }
        } catch (error) {
          errorCount++;
          this.logger.error(
            `Failed to process backfill for account ${account.accountSequence}`,
            error,
          );
          // 继续处理下一个账户
        }
      }

      this.logger.log(
        `Contribution backfill completed: ${backfilledCount} accounts backfilled, ${errorCount} errors`,
      );
    } catch (error) {
      this.logger.error('Failed to process contribution backfill', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:backfill`, lockValue);
    }
  }

  /**
   * 每天凌晨4点全量发布所有贡献值账户更新事件
   * 作为数据一致性的最终兜底保障
   */
  @Cron('0 4 * * *')
  async publishAllAccountUpdates(): Promise<void> {
    const lockValue = await this.redis.acquireLock(`${this.LOCK_KEY}:full-sync`, 3600); // 1小时锁
    if (!lockValue) {
      return;
    }

    try {
      this.logger.log('Starting daily full sync of contribution accounts...');

      let page = 1;
      const pageSize = 100;
      let totalPublished = 0;

      while (true) {
        const { items: accounts, total } = await this.contributionAccountRepository.findMany({
          page,
          limit: pageSize,
          orderBy: 'effectiveContribution',
          order: 'desc',
        });

        if (accounts.length === 0) {
          break;
        }

        const events = accounts.map((account) => {
          const event = new ContributionAccountUpdatedEvent(
            account.accountSequence,
            account.personalContribution.value.toString(),
            account.totalLevelPending.value.toString(),
            account.totalBonusPending.value.toString(),
            account.effectiveContribution.value.toString(),
            account.effectiveContribution.value.toString(),
            account.hasAdopted,
            account.directReferralAdoptedCount,
            account.unlockedLevelDepth,
            account.unlockedBonusTiers,
            account.createdAt,
          );

          return {
            aggregateType: ContributionAccountUpdatedEvent.AGGREGATE_TYPE,
            aggregateId: account.accountSequence,
            eventType: ContributionAccountUpdatedEvent.EVENT_TYPE,
            payload: event.toPayload(),
          };
        });

        await this.outboxRepository.saveMany(events);
        totalPublished += accounts.length;

        if (accounts.length < pageSize || page * pageSize >= total) {
          break;
        }
        page++;
      }

      this.logger.log(`Daily full sync completed: published ${totalPublished} contribution account events`);
    } catch (error) {
      this.logger.error('Failed to publish all account updates', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:full-sync`, lockValue);
    }
  }
}
