import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { LoggingService } from 'src/database/logging/logging.service';
import { Player } from 'src/players/entities/player.entity';
import { RoleEnum } from 'src/roles/roles.enum';
import { TeamRolesEnum } from 'src/team-roles/team-roles.enum';
import { TeamAccessService } from 'src/teams/team-access.service';
import { User } from 'src/users/entities/user.entity';
import { Repository } from 'typeorm';
import { CreateTransactionDto } from './dto/create-transaction.dto';
import { TransactionType } from './entitites/transaction-type.entity';
import { Transaction } from './entitites/transaction.entity';
import { TransactionTypeEnum } from './transaction-type.enum';

@Injectable()
export class TransactionsService {
  constructor(
    @InjectRepository(Transaction)
    private transactionsRepository: Repository<Transaction>,
    @InjectRepository(Player)
    private playersRepository: Repository<Player>,
    @InjectRepository(TransactionType)
    private transactionTypesRepository: Repository<TransactionType>,
    @InjectRepository(User)
    private usersRepository: Repository<User>,
    private logger: LoggingService,
    private access: TeamAccessService,
  ) {}

  async createTransactions(data: CreateTransactionDto[], userId: string) {
    const res = [];
    for (const d of data) {
      const r = await this.create(d, userId);
      res.push(r);
    }
    return res;
  }
  async create(data: CreateTransactionDto, userId: string) {
    const player = await this.playersRepository.findOne({
      where: { id: data.playerId },
      relations: ['team'],
    });

    const creatingUser = await this.usersRepository.findOne({
      where: {
        id: Number(userId),
      },
    });

    const transactionType = await this.transactionTypesRepository.findOne({
      where: { id: TransactionTypeEnum[TransactionTypeEnum[data.type]] },
    });

    if (!player || !transactionType || !creatingUser) {
      await this.logger.warn({
        event: 'transaction_create_fail',
        details: `Player: ${data.playerId}, amount: ${data.amount}, typeEnum: ${data.type}`,
        userId: Number(userId),
      });
      throw new NotFoundException('Spieler oder Buchungstyp nicht gefunden.');
    }

    if (creatingUser.role.id != RoleEnum.admin) {
      await this.access.assertAtLeast(
        Number(userId),
        player.team.id,
        'transaction_create_min_role',
        TeamRolesEnum.scnd_treasurer,
      );
    }

    const transaction = this.transactionsRepository.create({
      amount: data.amount,
      date: data.date,
      player: player,
      note: data.note || '',
      type: transactionType,
    });

    await this.logger.info({
      event: 'transaction_create',
      details: `Player: ${transaction.player.id}, amount: ${transaction.amount}, type: ${transaction.type?.name}`,
      userId: Number(userId),
    });

    return this.transactionsRepository.save(transaction);
  }

  async reverse(transactionId: number, userId: string) {
    const original = await this.transactionsRepository.findOne({
      where: { id: transactionId },
      relations: ['player', 'type'],
    });

    if (!original) {
      throw new NotFoundException('Buchung nicht gefunden');
    }

    if (original.note?.startsWith('Stornierung von Buchung #')) {
      throw new BadRequestException(
        'Eine Stornobuchung kann nicht erneut storniert werden',
      );
    }

    const alreadyReversed = await this.transactionsRepository.findOne({
      where: {
        player: { id: original.player.id },
        note: `Stornierung von Buchung #${original.id}`,
      },
    });
    if (alreadyReversed) {
      throw new BadRequestException('Diese Buchung wurde bereits storniert');
    }

    const creatingUser = await this.usersRepository.findOne({
      where: { id: Number(userId) },
    });

    if (creatingUser.role.id != RoleEnum.admin) {
      await this.access.assertAtLeast(
        Number(userId),
        original.player.team.id,
        'transaction_reverse_min_role',
        TeamRolesEnum.scnd_treasurer,
      );
    }

    const originalAmount = Math.abs(Number(original.amount));
    let reversalType = original.type;
    let reversalAmount = -originalAmount;

    // Buchungstypen > 10 (z.B. Strafe/Umlage/Gebühr) ziehen im Entity-Hook
    // immer vom Guthaben ab, egal welches Vorzeichen der Betrag hat. Um sie
    // auszugleichen, wird die Gegenbuchung stattdessen als "credit" gebucht.
    if (original.type.id > 10) {
      reversalType = await this.transactionTypesRepository.findOne({
        where: { id: TransactionTypeEnum.credit },
      });
      reversalAmount = originalAmount;
    }

    const reversal = this.transactionsRepository.create({
      amount: reversalAmount,
      date: new Date().toISOString(),
      player: original.player,
      note: `Stornierung von Buchung #${original.id}`,
      type: reversalType,
    });

    const saved = await this.transactionsRepository.save(reversal);

    await this.logger.info({
      event: 'transaction_reverse',
      details: `Buchung #${original.id} storniert durch Buchung #${saved.id}, Player: ${original.player.id}, amount: ${original.amount}`,
      userId: Number(userId),
    });

    return saved;
  }
}
