import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { InjectRepository } from '@nestjs/typeorm';
import { LoggingService } from 'src/database/logging/logging.service';
import { Player } from 'src/players/entities/player.entity';
import { Transaction } from 'src/transactions/entitites/transaction.entity';
import { DataSource, LessThanOrEqual, Repository } from 'typeorm';
import { RecurringTransactionIntervalEnum } from './recurring-transaction-interval.enum';
import { RecurringTransaction } from './entities/recurring-transaction.entity';

const INTERVAL_MONTHS: Record<RecurringTransactionIntervalEnum, number> = {
  [RecurringTransactionIntervalEnum.monthly]: 1,
  [RecurringTransactionIntervalEnum.quarterly]: 3,
  [RecurringTransactionIntervalEnum.yearly]: 12,
};

@Injectable()
export class RecurringTransactionsScheduler {
  constructor(
    @InjectRepository(RecurringTransaction)
    private readonly repository: Repository<RecurringTransaction>,
    private readonly dataSource: DataSource,
    private readonly logger: LoggingService,
  ) {}

  @Cron(CronExpression.EVERY_DAY_AT_3AM)
  async runDueRecurringTransactions(): Promise<void> {
    const today = new Date().toISOString();
    const due = await this.repository.find({
      where: { active: true, nextRunDate: LessThanOrEqual(today) },
      relations: ['team', 'type'],
    });

    for (const definition of due) {
      await this.runOne(definition);
    }
  }

  private async runOne(definition: RecurringTransaction): Promise<void> {
    await this.dataSource.transaction(async (manager) => {
      const players = await manager
        .getRepository(Player)
        .find({ where: { team: { id: definition.team.id }, active: true } });

      const transactionRepository = manager.getRepository(Transaction);
      for (const player of players) {
        const transaction = transactionRepository.create({
          amount: definition.amount,
          date: new Date().toISOString(),
          note: definition.description,
          player,
          type: definition.type,
        });
        await transactionRepository.save(transaction);
      }

      const definitionRepository = manager.getRepository(RecurringTransaction);
      definition.nextRunDate = this.advance(
        definition.nextRunDate,
        definition.interval,
      );
      await definitionRepository.save(definition);

      await this.logger.info(
        {
          event: 'recurring_transaction_run',
          details: `recurringTransactionId=${definition.id} teamId=${definition.team.id} gebuchteSpieler=${players.length}`,
          userId: -1,
        },
        manager,
      );
    });
  }

  private advance(
    nextRunDate: string,
    interval: RecurringTransactionIntervalEnum,
  ): string {
    const date = new Date(nextRunDate);
    date.setUTCMonth(date.getUTCMonth() + INTERVAL_MONTHS[interval]);
    return date.toISOString();
  }
}
