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 { 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 { 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 contributionRecordRepository: ContributionRecordRepository,
    private readonly contributionAccountRepository: ContributionAccountRepository,
    private readonly outboxRepository: OutboxRepository,
    private readonly kafkaProducer: KafkaProducerService,
    private readonly redis: RedisService,
  ) {}

  async onModuleInit() {
    this.logger.log('Contribution scheduler initialized');
  }

  /**
   * 每分钟处理未处理的认种记录
   */
  @Cron(CronExpression.EVERY_MINUTE)
  async processUnprocessedAdoptions(): Promise<void> {
    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> {
    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);
    }
  }

  /**
   * 每天凌晨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);
    }
  }
}
