import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { LoggingService } from 'src/database/logging/logging.service';
import { TeamRolesEnum } from 'src/team-roles/team-roles.enum';
import { Team } from 'src/teams/entities/team.entity';
import { TeamAccessService } from 'src/teams/team-access.service';
import { TransactionType } from 'src/transactions/entitites/transaction-type.entity';
import { DataSource, EntityManager, Repository } from 'typeorm';
import { CreateRecurringTransactionDTO } from './dto/create-recurring-transaction.dto';
import { RecurringTransactionResponseDTO } from './dto/recurring-transaction-response.dto';
import { UpdateRecurringTransactionDTO } from './dto/update-recurring-transaction.dto';
import { RecurringTransaction } from './entities/recurring-transaction.entity';

@Injectable()
export class RecurringTransactionsService {
  constructor(
    @InjectRepository(RecurringTransaction)
    private readonly repository: Repository<RecurringTransaction>,
    @InjectRepository(Team)
    private readonly teamRepository: Repository<Team>,
    private readonly dataSource: DataSource,
    private readonly access: TeamAccessService,
    private readonly logger: LoggingService,
  ) {}

  async getTeamRecurringTransactions(
    userId: number,
    teamId: number,
  ): Promise<RecurringTransactionResponseDTO[]> {
    const team = await this.teamRepository.findOne({ where: { id: teamId } });
    if (!team) throw new NotFoundException('Team nicht gefunden.');
    await this.access.assertMember(userId, teamId);
    const entries = await this.repository.find({
      where: { team: { id: teamId } },
      order: { description: 'ASC' },
    });
    return entries.map((entry) => this.toResponse(entry));
  }

  createRecurringTransaction(
    dto: CreateRecurringTransactionDTO,
    userId: number,
  ): Promise<RecurringTransactionResponseDTO> {
    return this.dataSource.transaction(async (manager) => {
      const team = await this.lockTeam(manager, dto.teamId);
      await this.access.assertAtLeast(
        userId,
        team.id,
        'transaction_create_min_role',
        TeamRolesEnum.scnd_treasurer,
        manager,
      );
      const type = await this.findType(manager, dto.type);
      const repository = manager.getRepository(RecurringTransaction);
      const saved = await repository.save(
        repository.create({
          team,
          description: dto.description.trim(),
          amount: dto.amount,
          type,
          interval: dto.interval,
          nextRunDate: dto.startDate,
          active: true,
        }),
      );
      await this.logger.info(
        {
          event: 'recurring_transaction_create',
          details: `teamId=${team.id} recurringTransactionId=${saved.id} action=create`,
          userId,
        },
        manager,
      );
      return this.toResponse(saved);
    });
  }

  async updateRecurringTransaction(
    id: number,
    dto: UpdateRecurringTransactionDTO,
    userId: number,
  ): Promise<RecurringTransactionResponseDTO> {
    const owner = await this.findOwner(id);
    return this.dataSource.transaction(async (manager) => {
      const team = await this.lockTeam(manager, owner.team.id);
      await this.access.assertAtLeast(
        userId,
        team.id,
        'transaction_create_min_role',
        TeamRolesEnum.scnd_treasurer,
        manager,
      );
      const type = await this.findType(manager, dto.type);
      const repository = manager.getRepository(RecurringTransaction);
      const entry = await this.findTransactionalEntry(repository, id, team.id);
      entry.description = dto.description.trim();
      entry.amount = dto.amount;
      entry.type = type;
      entry.interval = dto.interval;
      entry.active = dto.active;
      const saved = await repository.save(entry);
      await this.logger.info(
        {
          event: 'recurring_transaction_update',
          details: `teamId=${team.id} recurringTransactionId=${id} action=update`,
          userId,
        },
        manager,
      );
      return this.toResponse(saved);
    });
  }

  async deleteRecurringTransaction(id: number, userId: number): Promise<void> {
    const owner = await this.findOwner(id);
    await this.dataSource.transaction(async (manager) => {
      const team = await this.lockTeam(manager, owner.team.id);
      await this.access.assertAtLeast(
        userId,
        team.id,
        'transaction_create_min_role',
        TeamRolesEnum.scnd_treasurer,
        manager,
      );
      const repository = manager.getRepository(RecurringTransaction);
      const entry = await this.findTransactionalEntry(repository, id, team.id);
      await repository.remove(entry);
      await this.logger.info(
        {
          event: 'recurring_transaction_delete',
          details: `teamId=${team.id} recurringTransactionId=${id} action=delete`,
          userId,
        },
        manager,
      );
    });
  }

  private async findOwner(id: number): Promise<RecurringTransaction> {
    const entry = await this.repository.findOne({
      where: { id },
      relations: ['team'],
    });
    if (!entry?.team)
      throw new NotFoundException('Wiederkehrende Buchung nicht gefunden.');
    return entry;
  }

  private async lockTeam(
    manager: EntityManager,
    teamId: number,
  ): Promise<Team> {
    const team = await manager
      .getRepository(Team)
      .createQueryBuilder('team')
      .where('team.id = :teamId', { teamId })
      .setLock('pessimistic_write')
      .getOne();
    if (!team) throw new NotFoundException('Team nicht gefunden.');
    return team;
  }

  private async findType(
    manager: EntityManager,
    typeId: number,
  ): Promise<TransactionType> {
    const type = await manager
      .getRepository(TransactionType)
      .findOne({ where: { id: typeId } });
    if (!type) throw new NotFoundException('Buchungstyp nicht gefunden.');
    return type;
  }

  private async findTransactionalEntry(
    repository: Repository<RecurringTransaction>,
    id: number,
    teamId: number,
  ): Promise<RecurringTransaction> {
    const entry = await repository.findOne({
      where: { id, team: { id: teamId } },
      relations: ['team'],
    });
    if (!entry)
      throw new NotFoundException('Wiederkehrende Buchung nicht gefunden.');
    return entry;
  }

  private toResponse(
    entry: RecurringTransaction,
  ): RecurringTransactionResponseDTO {
    return {
      id: entry.id,
      description: entry.description,
      amount: Number(entry.amount),
      type: entry.type.id,
      interval: entry.interval,
      nextRunDate: entry.nextRunDate,
      active: entry.active,
      createdAt: entry.createdAt,
    };
  }
}
