import { Controller, Get, Post, Logger } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
import { OutboxRepository } from '../../infrastructure/persistence/repositories/outbox.repository';
import { UnitOfWork } from '../../infrastructure/persistence/unit-of-work/unit-of-work';
import { ContributionRateService } from '../../application/services/contribution-rate.service';
import {
  ContributionAccountSyncedEvent,
  ReferralSyncedEvent,
  AdoptionSyncedEvent,
  ContributionRecordSyncedEvent,
  NetworkProgressUpdatedEvent,
  SystemAccountSyncedEvent,
  UnallocatedContributionSyncedEvent,
} from '../../domain/events';
import { Public } from '../../shared/guards/jwt-auth.guard';

@ApiTags('Admin')
@Controller('admin')
export class AdminController {
  private readonly logger = new Logger(AdminController.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly outboxRepository: OutboxRepository,
    private readonly unitOfWork: UnitOfWork,
    private readonly contributionRateService: ContributionRateService,
  ) {}

  @Get('accounts/sync')
  @Public()
  @ApiOperation({ summary: '获取所有贡献值账户用于同步' })
  async getAllAccountsForSync() {
    const accounts = await this.prisma.contributionAccount.findMany({
      select: {
        accountSequence: true,
        personalContribution: true,
        totalLevelPending: true,
        totalBonusPending: true,
        totalUnlocked: true,
        effectiveContribution: true,
        hasAdopted: true,
        directReferralAdoptedCount: true,
        unlockedLevelDepth: true,
        unlockedBonusTiers: true,
        createdAt: true,
        updatedAt: true,
      },
    });

    return {
      accounts: accounts.map((acc) => ({
        accountSequence: acc.accountSequence,
        personalContribution: acc.personalContribution.toString(),
        teamLevelContribution: acc.totalLevelPending.toString(),
        teamBonusContribution: acc.totalBonusPending.toString(),
        totalContribution: acc.effectiveContribution.toString(),
        effectiveContribution: acc.effectiveContribution.toString(),
        hasAdopted: acc.hasAdopted,
        directReferralAdoptedCount: acc.directReferralAdoptedCount,
        unlockedLevelDepth: acc.unlockedLevelDepth,
        unlockedBonusTiers: acc.unlockedBonusTiers,
        createdAt: acc.createdAt,
        updatedAt: acc.updatedAt,
      })),
      total: accounts.length,
    };
  }

  @Post('contribution-accounts/publish-all')
  @Public()
  @ApiOperation({ summary: '发布所有贡献值账户事件到 outbox，用于初始同步到 mining-admin-service' })
  async publishAllContributionAccounts(): Promise<{
    success: boolean;
    publishedCount: number;
    failedCount: number;
    message: string;
  }> {
    const accounts = await this.prisma.contributionAccount.findMany({
      select: {
        accountSequence: true,
        personalContribution: true,
        totalLevelPending: true,
        totalBonusPending: true,
        effectiveContribution: true,
        hasAdopted: true,
        directReferralAdoptedCount: true,
        unlockedLevelDepth: true,
        unlockedBonusTiers: true,
        createdAt: true,
      },
    });

    let publishedCount = 0;
    let failedCount = 0;

    // 批量处理，每批 100 条
    const batchSize = 100;
    for (let i = 0; i < accounts.length; i += batchSize) {
      const batch = accounts.slice(i, i + batchSize);

      try {
        await this.unitOfWork.executeInTransaction(async () => {
          const events = batch.map((acc) => {
            const event = new ContributionAccountSyncedEvent(
              acc.accountSequence,
              acc.personalContribution.toString(),
              acc.totalLevelPending.toString(),
              acc.totalBonusPending.toString(),
              acc.effectiveContribution.toString(),
              acc.effectiveContribution.toString(),
              acc.hasAdopted,
              acc.directReferralAdoptedCount,
              acc.unlockedLevelDepth,
              acc.unlockedBonusTiers,
              acc.createdAt,
            );

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

          await this.outboxRepository.saveMany(events);
        });

        publishedCount += batch.length;
        this.logger.debug(`Published batch ${Math.floor(i / batchSize) + 1}: ${batch.length} events`);
      } catch (error) {
        failedCount += batch.length;
        this.logger.error(`Failed to publish batch ${Math.floor(i / batchSize) + 1}`, error);
      }
    }

    this.logger.log(`Published ${publishedCount} contribution account events, ${failedCount} failed`);

    return {
      success: failedCount === 0,
      publishedCount,
      failedCount,
      message: `Published ${publishedCount} events, ${failedCount} failed out of ${accounts.length} total`,
    };
  }

  @Post('referrals/publish-all')
  @Public()
  @ApiOperation({ summary: '发布所有推荐关系事件到 outbox，用于同步到 mining-admin-service' })
  async publishAllReferrals(): Promise<{
    success: boolean;
    publishedCount: number;
    failedCount: number;
    message: string;
  }> {
    const referrals = await this.prisma.syncedReferral.findMany({
      select: {
        accountSequence: true,
        referrerAccountSequence: true,
        referrerUserId: true,
        originalUserId: true,
        ancestorPath: true,
        depth: true,
      },
    });

    let publishedCount = 0;
    let failedCount = 0;

    const batchSize = 100;
    for (let i = 0; i < referrals.length; i += batchSize) {
      const batch = referrals.slice(i, i + batchSize);

      try {
        await this.unitOfWork.executeInTransaction(async () => {
          const events = batch.map((ref) => {
            const event = new ReferralSyncedEvent(
              ref.accountSequence,
              ref.referrerAccountSequence,
              ref.referrerUserId,
              ref.originalUserId,
              ref.ancestorPath,
              ref.depth,
            );

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

          await this.outboxRepository.saveMany(events);
        });

        publishedCount += batch.length;
        this.logger.debug(`Published referral batch ${Math.floor(i / batchSize) + 1}: ${batch.length} events`);
      } catch (error) {
        failedCount += batch.length;
        this.logger.error(`Failed to publish referral batch ${Math.floor(i / batchSize) + 1}`, error);
      }
    }

    this.logger.log(`Published ${publishedCount} referral events, ${failedCount} failed`);

    return {
      success: failedCount === 0,
      publishedCount,
      failedCount,
      message: `Published ${publishedCount} events, ${failedCount} failed out of ${referrals.length} total`,
    };
  }

  @Post('adoptions/publish-all')
  @Public()
  @ApiOperation({ summary: '发布所有认种记录事件到 outbox，用于同步到 mining-admin-service' })
  async publishAllAdoptions(): Promise<{
    success: boolean;
    publishedCount: number;
    failedCount: number;
    message: string;
  }> {
    const adoptions = await this.prisma.syncedAdoption.findMany({
      select: {
        originalAdoptionId: true,
        accountSequence: true,
        treeCount: true,
        adoptionDate: true,
        status: true,
        contributionPerTree: true,
      },
    });

    let publishedCount = 0;
    let failedCount = 0;

    const batchSize = 100;
    for (let i = 0; i < adoptions.length; i += batchSize) {
      const batch = adoptions.slice(i, i + batchSize);

      try {
        await this.unitOfWork.executeInTransaction(async () => {
          const events = batch.map((adoption) => {
            const event = new AdoptionSyncedEvent(
              adoption.originalAdoptionId,
              adoption.accountSequence,
              adoption.treeCount,
              adoption.adoptionDate,
              adoption.status,
              adoption.contributionPerTree.toString(),
            );

            return {
              aggregateType: AdoptionSyncedEvent.AGGREGATE_TYPE,
              aggregateId: adoption.originalAdoptionId.toString(),
              eventType: AdoptionSyncedEvent.EVENT_TYPE,
              payload: event.toPayload(),
            };
          });

          await this.outboxRepository.saveMany(events);
        });

        publishedCount += batch.length;
        this.logger.debug(`Published adoption batch ${Math.floor(i / batchSize) + 1}: ${batch.length} events`);
      } catch (error) {
        failedCount += batch.length;
        this.logger.error(`Failed to publish adoption batch ${Math.floor(i / batchSize) + 1}`, error);
      }
    }

    this.logger.log(`Published ${publishedCount} adoption events, ${failedCount} failed`);

    return {
      success: failedCount === 0,
      publishedCount,
      failedCount,
      message: `Published ${publishedCount} events, ${failedCount} failed out of ${adoptions.length} total`,
    };
  }

  @Get('network-progress')
  @Public()
  @ApiOperation({ summary: '获取全网认种进度和算力系数' })
  async getNetworkProgress() {
    const progress = await this.contributionRateService.getNetworkProgress();

    return {
      totalTreeCount: progress.totalTreeCount,
      totalAdoptionOrders: progress.totalAdoptionOrders,
      totalAdoptedUsers: progress.totalAdoptedUsers,
      currentUnit: progress.currentUnit,
      currentMultiplier: progress.currentMultiplier.toString(),
      currentContributionPerTree: progress.currentContributionPerTree.toString(),
      nextUnitTreeCount: progress.nextUnitTreeCount,
      // 计算下一个单位还需要多少棵
      treesToNextUnit: progress.nextUnitTreeCount - progress.totalTreeCount,
    };
  }

  @Post('contribution-records/publish-all')
  @Public()
  @ApiOperation({ summary: '发布所有算力记录事件到 outbox，用于初始同步到 mining-admin-service' })
  async publishAllContributionRecords(): Promise<{
    success: boolean;
    publishedCount: number;
    failedCount: number;
    message: string;
  }> {
    const records = await this.prisma.contributionRecord.findMany({
      select: {
        id: true,
        accountSequence: true,
        sourceType: true,
        sourceAdoptionId: true,
        sourceAccountSequence: true,
        treeCount: true,
        baseContribution: true,
        distributionRate: true,
        levelDepth: true,
        bonusTier: true,
        amount: true,
        effectiveDate: true,
        expireDate: true,
        isExpired: true,
        createdAt: true,
      },
    });

    let publishedCount = 0;
    let failedCount = 0;

    const batchSize = 100;
    for (let i = 0; i < records.length; i += batchSize) {
      const batch = records.slice(i, i + batchSize);

      try {
        await this.unitOfWork.executeInTransaction(async () => {
          const events = batch.map((record) => {
            const event = new ContributionRecordSyncedEvent(
              record.id,
              record.accountSequence,
              record.sourceType,
              record.sourceAdoptionId,
              record.sourceAccountSequence,
              record.treeCount,
              record.baseContribution.toString(),
              record.distributionRate.toString(),
              record.levelDepth,
              record.bonusTier,
              record.amount.toString(),
              record.effectiveDate,
              record.expireDate,
              record.isExpired,
              record.createdAt,
            );

            return {
              aggregateType: ContributionRecordSyncedEvent.AGGREGATE_TYPE,
              aggregateId: record.id.toString(),
              eventType: ContributionRecordSyncedEvent.EVENT_TYPE,
              payload: event.toPayload(),
            };
          });

          await this.outboxRepository.saveMany(events);
        });

        publishedCount += batch.length;
        this.logger.debug(`Published contribution record batch ${Math.floor(i / batchSize) + 1}: ${batch.length} events`);
      } catch (error) {
        failedCount += batch.length;
        this.logger.error(`Failed to publish contribution record batch ${Math.floor(i / batchSize) + 1}`, error);
      }
    }

    this.logger.log(`Published ${publishedCount} contribution record events, ${failedCount} failed`);

    return {
      success: failedCount === 0,
      publishedCount,
      failedCount,
      message: `Published ${publishedCount} events, ${failedCount} failed out of ${records.length} total`,
    };
  }

  @Post('network-progress/publish')
  @Public()
  @ApiOperation({ summary: '发布当前全网进度事件' })
  async publishNetworkProgress(): Promise<{ success: boolean; message: string }> {
    try {
      const progress = await this.contributionRateService.getNetworkProgress();

      const event = new NetworkProgressUpdatedEvent(
        progress.totalTreeCount,
        progress.totalAdoptionOrders,
        progress.totalAdoptedUsers,
        progress.currentUnit,
        progress.currentMultiplier.toString(),
        progress.currentContributionPerTree.toString(),
        progress.nextUnitTreeCount,
      );

      await this.outboxRepository.save({
        aggregateType: NetworkProgressUpdatedEvent.AGGREGATE_TYPE,
        aggregateId: 'network',
        eventType: NetworkProgressUpdatedEvent.EVENT_TYPE,
        payload: event.toPayload(),
      });

      return {
        success: true,
        message: `Published network progress: trees=${progress.totalTreeCount}, unit=${progress.currentUnit}, multiplier=${progress.currentMultiplier.toString()}`,
      };
    } catch (error) {
      this.logger.error('Failed to publish network progress', error);
      return {
        success: false,
        message: `Failed: ${error.message}`,
      };
    }
  }

  @Post('system-accounts/publish-all')
  @Public()
  @ApiOperation({ summary: '发布所有系统账户算力事件到 outbox，用于同步到 mining-service' })
  async publishAllSystemAccounts(): Promise<{
    success: boolean;
    publishedCount: number;
    message: string;
  }> {
    try {
      const systemAccounts = await this.prisma.systemAccount.findMany();

      await this.unitOfWork.executeInTransaction(async () => {
        const events = systemAccounts.map((account) => {
          const event = new SystemAccountSyncedEvent(
            account.accountType,
            account.regionCode,
            account.name,
            account.contributionBalance.toString(),
            account.createdAt,
          );

          return {
            aggregateType: SystemAccountSyncedEvent.AGGREGATE_TYPE,
            aggregateId: `${account.accountType}:${account.regionCode || 'null'}`,
            eventType: SystemAccountSyncedEvent.EVENT_TYPE,
            payload: event.toPayload(),
          };
        });

        await this.outboxRepository.saveMany(events);
      });

      this.logger.log(`Published ${systemAccounts.length} system account events`);

      return {
        success: true,
        publishedCount: systemAccounts.length,
        message: `Published ${systemAccounts.length} system account events`,
      };
    } catch (error) {
      this.logger.error('Failed to publish system accounts', error);
      return {
        success: false,
        publishedCount: 0,
        message: `Failed: ${error.message}`,
      };
    }
  }

  @Get('system-accounts')
  @Public()
  @ApiOperation({ summary: '获取所有系统账户算力' })
  async getSystemAccounts() {
    const systemAccounts = await this.prisma.systemAccount.findMany();

    return {
      accounts: systemAccounts.map((a) => ({
        accountType: a.accountType,
        name: a.name,
        contributionBalance: a.contributionBalance.toString(),
        createdAt: a.createdAt,
        updatedAt: a.updatedAt,
      })),
      total: systemAccounts.length,
    };
  }

  @Get('unallocated-contributions')
  @Public()
  @ApiOperation({ summary: '获取所有未分配算力列表，供 mining-service 定时同步' })
  async getUnallocatedContributions(): Promise<{
    contributions: Array<{
      sourceAdoptionId: string;
      sourceAccountSequence: string;
      wouldBeAccountSequence: string | null;
      contributionType: string;
      amount: string;
      reason: string | null;
      effectiveDate: string;
      expireDate: string;
    }>;
    total: number;
  }> {
    const unallocatedContributions = await this.prisma.unallocatedContribution.findMany({
      where: { status: 'PENDING' },
      select: {
        sourceAdoptionId: true,
        sourceAccountSequence: true,
        wouldBeAccountSequence: true,
        unallocType: true,
        amount: true,
        reason: true,
        effectiveDate: true,
        expireDate: true,
      },
    });

    return {
      contributions: unallocatedContributions.map((uc) => ({
        sourceAdoptionId: uc.sourceAdoptionId.toString(),
        sourceAccountSequence: uc.sourceAccountSequence,
        wouldBeAccountSequence: uc.wouldBeAccountSequence,
        contributionType: uc.unallocType,
        amount: uc.amount.toString(),
        reason: uc.reason,
        effectiveDate: uc.effectiveDate.toISOString(),
        expireDate: uc.expireDate.toISOString(),
      })),
      total: unallocatedContributions.length,
    };
  }

  @Post('unallocated-contributions/publish-all')
  @Public()
  @ApiOperation({ summary: '发布所有未分配算力事件到 outbox，用于同步到 mining-service' })
  async publishAllUnallocatedContributions(): Promise<{
    success: boolean;
    publishedCount: number;
    failedCount: number;
    message: string;
  }> {
    const unallocatedContributions = await this.prisma.unallocatedContribution.findMany({
      where: { status: 'PENDING' },
      select: {
        id: true,
        sourceAdoptionId: true,
        sourceAccountSequence: true,
        wouldBeAccountSequence: true,
        unallocType: true,
        amount: true,
        reason: true,
        effectiveDate: true,
        expireDate: true,
      },
    });

    let publishedCount = 0;
    let failedCount = 0;

    const batchSize = 100;
    for (let i = 0; i < unallocatedContributions.length; i += batchSize) {
      const batch = unallocatedContributions.slice(i, i + batchSize);

      try {
        await this.unitOfWork.executeInTransaction(async () => {
          const events = batch.map((uc) => {
            const event = new UnallocatedContributionSyncedEvent(
              uc.sourceAdoptionId,
              uc.sourceAccountSequence,
              uc.wouldBeAccountSequence,
              uc.unallocType,
              uc.amount.toString(),
              uc.reason,
              uc.effectiveDate,
              uc.expireDate,
            );

            return {
              aggregateType: UnallocatedContributionSyncedEvent.AGGREGATE_TYPE,
              aggregateId: `${uc.sourceAdoptionId}-${uc.unallocType}`,
              eventType: UnallocatedContributionSyncedEvent.EVENT_TYPE,
              payload: event.toPayload(),
            };
          });

          await this.outboxRepository.saveMany(events);
        });

        publishedCount += batch.length;
        this.logger.debug(`Published unallocated contribution batch ${Math.floor(i / batchSize) + 1}: ${batch.length} events`);
      } catch (error) {
        failedCount += batch.length;
        this.logger.error(`Failed to publish unallocated contribution batch ${Math.floor(i / batchSize) + 1}`, error);
      }
    }

    this.logger.log(`Published ${publishedCount} unallocated contribution events, ${failedCount} failed`);

    return {
      success: failedCount === 0,
      publishedCount,
      failedCount,
      message: `Published ${publishedCount} events, ${failedCount} failed out of ${unallocatedContributions.length} total`,
    };
  }
}
