import { Injectable, Inject, Logger, NotFoundException } from '@nestjs/common';
import { GenerateReportCommand } from './generate-report.command';
import {
  IReportDefinitionRepository,
  IReportSnapshotRepository,
  REPORT_DEFINITION_REPOSITORY,
  REPORT_SNAPSHOT_REPOSITORY,
} from '../../../domain/repositories';
import { ReportGenerationDomainService } from '../../../domain/services';
import { ReportSnapshot } from '../../../domain/aggregates/report-snapshot';
import { DateRange, ReportType } from '../../../domain/value-objects';
import { LeaderboardServiceClient } from '../../../infrastructure/external/leaderboard-service/leaderboard-service.client';
import { PlantingServiceClient } from '../../../infrastructure/external/planting-service/planting-service.client';
import { ReportCacheService } from '../../../infrastructure/redis/report-cache.service';

@Injectable()
export class GenerateReportHandler {
  private readonly logger = new Logger(GenerateReportHandler.name);

  constructor(
    @Inject(REPORT_DEFINITION_REPOSITORY)
    private readonly definitionRepo: IReportDefinitionRepository,
    @Inject(REPORT_SNAPSHOT_REPOSITORY)
    private readonly snapshotRepo: IReportSnapshotRepository,
    private readonly generationService: ReportGenerationDomainService,
    private readonly leaderboardClient: LeaderboardServiceClient,
    private readonly plantingClient: PlantingServiceClient,
    private readonly cacheService: ReportCacheService,
  ) {}

  async execute(command: GenerateReportCommand): Promise<ReportSnapshot> {
    this.logger.log(`Generating report: ${command.reportCode}`);

    // Find report definition
    const definition = await this.definitionRepo.findByCode(command.reportCode);
    if (!definition) {
      throw new NotFoundException(
        `Report definition not found: ${command.reportCode}`,
      );
    }

    // Create date range and period key
    const dateRange = DateRange.create(command.startDate, command.endDate);
    const periodKey = dateRange.toPeriodKey(command.reportPeriod);

    // Check cache first
    const cached = await this.cacheService.getCachedSnapshot<any>(
      command.reportCode,
      periodKey,
    );

    if (cached) {
      this.logger.debug(`Using cached snapshot for ${command.reportCode}:${periodKey}`);
      // Return cached snapshot if exists
      const existingSnapshot = await this.snapshotRepo.findByCodeAndPeriodKey(
        command.reportCode,
        periodKey,
      );
      if (existingSnapshot) {
        return existingSnapshot;
      }
    }

    // Generate report data based on type
    const { rows, summary, dataSources } = await this.generateReportData(
      definition.reportType,
      command,
    );

    // Create snapshot
    const snapshot = this.generationService.generateSnapshot({
      reportType: definition.reportType,
      reportCode: command.reportCode,
      reportPeriod: command.reportPeriod,
      periodKey,
      rows,
      summary,
      dataSources,
      filterParams: command.filters,
      periodStartAt: command.startDate,
      periodEndAt: command.endDate,
    });

    // Save snapshot
    const savedSnapshot = await this.snapshotRepo.save(snapshot);

    // Update definition's last generated timestamp
    definition.markGenerated();
    await this.definitionRepo.save(definition);

    // Cache the result
    await this.cacheService.cacheSnapshot(
      command.reportCode,
      periodKey,
      savedSnapshot.snapshotData.toJSON(),
    );

    this.logger.log(
      `Report generated: ${command.reportCode}:${periodKey} with ${savedSnapshot.rowCount} rows`,
    );

    return savedSnapshot;
  }

  private async generateReportData(
    reportType: ReportType,
    command: GenerateReportCommand,
  ): Promise<{
    rows: any[];
    summary: Record<string, any>;
    dataSources: string[];
  }> {
    switch (reportType) {
      case ReportType.LEADERBOARD_REPORT:
        return this.generateLeaderboardReport(command);

      case ReportType.PLANTING_REPORT:
        return this.generatePlantingReport(command);

      case ReportType.REGIONAL_PLANTING_REPORT:
        return this.generateRegionalPlantingReport(command);

      default:
        return {
          rows: [],
          summary: {},
          dataSources: [],
        };
    }
  }

  private async generateLeaderboardReport(
    command: GenerateReportCommand,
  ): Promise<{
    rows: any[];
    summary: Record<string, any>;
    dataSources: string[];
  }> {
    let leaderboardData;

    switch (command.reportPeriod) {
      case 'DAILY':
        leaderboardData = await this.leaderboardClient.getDailyLeaderboard(
          command.startDate,
        );
        break;
      case 'WEEKLY':
        leaderboardData = await this.leaderboardClient.getWeeklyLeaderboard();
        break;
      case 'MONTHLY':
        leaderboardData = await this.leaderboardClient.getMonthlyLeaderboard();
        break;
      default:
        leaderboardData = await this.leaderboardClient.getDailyLeaderboard(
          command.startDate,
        );
    }

    return {
      rows: leaderboardData.entries,
      summary: {
        totalEntries: leaderboardData.entries.length,
        period: leaderboardData.period,
        type: leaderboardData.type,
      },
      dataSources: ['leaderboard-service'],
    };
  }

  private async generatePlantingReport(
    command: GenerateReportCommand,
  ): Promise<{
    rows: any[];
    summary: Record<string, any>;
    dataSources: string[];
  }> {
    const stats = await this.plantingClient.getStatsForDateRange(
      command.startDate,
      command.endDate,
    );

    return {
      rows: [stats],
      summary: {
        totalOrders: stats.totalOrders,
        totalTrees: stats.totalTrees,
        totalAmount: stats.totalAmount,
        newUsers: stats.newUsers,
        activeUsers: stats.activeUsers,
      },
      dataSources: ['planting-service'],
    };
  }

  private async generateRegionalPlantingReport(
    command: GenerateReportCommand,
  ): Promise<{
    rows: any[];
    summary: Record<string, any>;
    dataSources: string[];
  }> {
    const provincesStats = await this.plantingClient.getAllProvincesStats(
      command.startDate,
      command.endDate,
    );

    const totalOrders = provincesStats.reduce(
      (sum, s) => sum + s.totalOrders,
      0,
    );
    const totalTrees = provincesStats.reduce((sum, s) => sum + s.totalTrees, 0);

    return {
      rows: provincesStats,
      summary: {
        totalProvinces: provincesStats.length,
        totalOrders,
        totalTrees,
      },
      dataSources: ['planting-service'],
    };
  }
}
