import { Injectable, Inject, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import {
  IReportDefinitionRepository,
  REPORT_DEFINITION_REPOSITORY,
} from '../../domain/repositories';
import { GenerateReportHandler } from '../commands/generate-report/generate-report.handler';
import { GenerateReportCommand } from '../commands/generate-report/generate-report.command';
import { DateRange, ReportPeriod } from '../../domain/value-objects';

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

  constructor(
    @Inject(REPORT_DEFINITION_REPOSITORY)
    private readonly definitionRepo: IReportDefinitionRepository,
    private readonly generateReportHandler: GenerateReportHandler,
  ) {}

  @Cron(CronExpression.EVERY_DAY_AT_1AM)
  async generateDailyReports(): Promise<void> {
    this.logger.log('Starting daily report generation...');

    try {
      const definitions = await this.definitionRepo.findScheduled();
      const yesterday = this.getYesterday();
      const dateRange = DateRange.create(yesterday.start, yesterday.end);

      for (const definition of definitions) {
        try {
          const command = new GenerateReportCommand(
            definition.reportCode,
            ReportPeriod.DAILY,
            yesterday.start,
            yesterday.end,
          );

          await this.generateReportHandler.execute(command);
          this.logger.log(`Daily report generated: ${definition.reportCode}`);
        } catch (error) {
          this.logger.error(
            `Failed to generate daily report: ${definition.reportCode}`,
            error,
          );
        }
      }

      this.logger.log('Daily report generation completed');
    } catch (error) {
      this.logger.error('Daily report generation failed', error);
    }
  }

  @Cron(CronExpression.EVERY_WEEK)
  async generateWeeklyReports(): Promise<void> {
    this.logger.log('Starting weekly report generation...');

    try {
      const definitions = await this.definitionRepo.findScheduled();
      const lastWeek = this.getLastWeek();

      for (const definition of definitions) {
        try {
          const command = new GenerateReportCommand(
            definition.reportCode,
            ReportPeriod.WEEKLY,
            lastWeek.start,
            lastWeek.end,
          );

          await this.generateReportHandler.execute(command);
          this.logger.log(`Weekly report generated: ${definition.reportCode}`);
        } catch (error) {
          this.logger.error(
            `Failed to generate weekly report: ${definition.reportCode}`,
            error,
          );
        }
      }

      this.logger.log('Weekly report generation completed');
    } catch (error) {
      this.logger.error('Weekly report generation failed', error);
    }
  }

  @Cron('0 0 1 * *') // First day of month at midnight
  async generateMonthlyReports(): Promise<void> {
    this.logger.log('Starting monthly report generation...');

    try {
      const definitions = await this.definitionRepo.findScheduled();
      const lastMonth = this.getLastMonth();

      for (const definition of definitions) {
        try {
          const command = new GenerateReportCommand(
            definition.reportCode,
            ReportPeriod.MONTHLY,
            lastMonth.start,
            lastMonth.end,
          );

          await this.generateReportHandler.execute(command);
          this.logger.log(`Monthly report generated: ${definition.reportCode}`);
        } catch (error) {
          this.logger.error(
            `Failed to generate monthly report: ${definition.reportCode}`,
            error,
          );
        }
      }

      this.logger.log('Monthly report generation completed');
    } catch (error) {
      this.logger.error('Monthly report generation failed', error);
    }
  }

  private getYesterday(): { start: Date; end: Date } {
    const now = new Date();
    const yesterday = new Date(now);
    yesterday.setDate(yesterday.getDate() - 1);

    const start = new Date(
      yesterday.getFullYear(),
      yesterday.getMonth(),
      yesterday.getDate(),
      0,
      0,
      0,
    );
    const end = new Date(
      yesterday.getFullYear(),
      yesterday.getMonth(),
      yesterday.getDate(),
      23,
      59,
      59,
      999,
    );

    return { start, end };
  }

  private getLastWeek(): { start: Date; end: Date } {
    const now = new Date();
    const dayOfWeek = now.getDay();
    const diffToLastMonday = dayOfWeek === 0 ? -13 : -6 - dayOfWeek;

    const start = new Date(now);
    start.setDate(now.getDate() + diffToLastMonday);
    start.setHours(0, 0, 0, 0);

    const end = new Date(start);
    end.setDate(start.getDate() + 6);
    end.setHours(23, 59, 59, 999);

    return { start, end };
  }

  private getLastMonth(): { start: Date; end: Date } {
    const now = new Date();
    const start = new Date(now.getFullYear(), now.getMonth() - 1, 1, 0, 0, 0);
    const end = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);

    return { start, end };
  }
}
