import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Player } from 'src/players/entities/player.entity';
import { Team } from 'src/teams/entities/team.entity';
import { Notification } from './entities/notification.entity';
import { NotificationRecipient } from './entities/notification-recipient.entity';
import { NOTIFICATION_EVENT } from './model/notification-event.type';

export interface NotificationDto {
  id: number;
  event: NOTIFICATION_EVENT;
  actorUserId: number;
  payload: Record<string, unknown>;
  read: boolean;
  createdAt: Date;
}

export interface NotificationPage {
  data: NotificationDto[];
  page: number;
  limit: number;
  total: number;
  hasNextPage: boolean;
}

@Injectable()
export class NotificationsService {
  constructor(
    @InjectRepository(Notification)
    private readonly notificationRepository: Repository<Notification>,
    @InjectRepository(NotificationRecipient)
    private readonly recipientRepository: Repository<NotificationRecipient>,
    @InjectRepository(Player)
    private readonly playerRepository: Repository<Player>,
  ) {}

  async create(params: {
    teamId: number;
    event: NOTIFICATION_EVENT;
    actorUserId: number;
    payload: Record<string, unknown>;
  }): Promise<void> {
    const recipientUserIds = await this.resolveRecipients(params.teamId, params.actorUserId);
    if (recipientUserIds.length === 0) return;

    const notification = await this.notificationRepository.save(
      this.notificationRepository.create({
        team: { id: params.teamId } as Team,
        event: params.event,
        actorUserId: params.actorUserId,
        payload: JSON.stringify(params.payload),
      }),
    );

    await this.recipientRepository.insert(
      recipientUserIds.map((userId) => ({
        notification: { id: notification.id } as Notification,
        userId,
      })),
    );
  }

  async listForUser(
    userId: number,
    teamId: number,
    page: number,
    limit: number,
  ): Promise<NotificationPage> {
    const builder = this.recipientRepository
      .createQueryBuilder('recipient')
      .innerJoinAndSelect('recipient.notification', 'notification')
      .where('recipient.userId = :userId', { userId })
      .andWhere('notification.teamId = :teamId', { teamId })
      .orderBy('notification.createdAt', 'DESC');

    const total = await builder.getCount();
    const rows = await builder.offset((page - 1) * limit).limit(limit).getMany();

    return {
      data: rows.map((row) => this.toDto(row)),
      page,
      limit,
      total,
      hasNextPage: page * limit < total,
    };
  }

  async getUnreadCount(userId: number, teamId: number): Promise<number> {
    return this.recipientRepository
      .createQueryBuilder('recipient')
      .innerJoin('recipient.notification', 'notification')
      .where('recipient.userId = :userId', { userId })
      .andWhere('notification.teamId = :teamId', { teamId })
      .andWhere('recipient.read = false')
      .getCount();
  }

  async markRead(recipientId: number, userId: number): Promise<void> {
    const recipient = await this.recipientRepository.findOne({ where: { id: recipientId } });
    if (!recipient || recipient.userId !== userId) {
      throw new NotFoundException('Benachrichtigung nicht gefunden.');
    }
    if (recipient.read) return;
    recipient.read = true;
    recipient.readAt = new Date();
    await this.recipientRepository.save(recipient);
  }

  async markAllRead(userId: number, teamId: number): Promise<void> {
    const rows = await this.recipientRepository
      .createQueryBuilder('recipient')
      .innerJoin('recipient.notification', 'notification')
      .where('recipient.userId = :userId', { userId })
      .andWhere('notification.teamId = :teamId', { teamId })
      .andWhere('recipient.read = false')
      .select('recipient.id', 'id')
      .getRawMany<{ id: number }>();

    if (rows.length === 0) return;

    await this.recipientRepository.update(rows.map((row) => row.id), {
      read: true,
      readAt: new Date(),
    });
  }

  private async resolveRecipients(teamId: number, actorUserId: number): Promise<number[]> {
    const rows = await this.playerRepository
      .createQueryBuilder('player')
      .where('player.teamId = :teamId', { teamId })
      .andWhere('player.active = :active', { active: true })
      .andWhere('player.userId IS NOT NULL')
      .andWhere('player.userId != :actorUserId', { actorUserId })
      .select('DISTINCT player.userId', 'userId')
      .getRawMany<{ userId: number }>();
    return rows.map((row) => row.userId);
  }

  private toDto(recipient: NotificationRecipient): NotificationDto {
    return {
      id: recipient.id,
      event: recipient.notification.event,
      actorUserId: recipient.notification.actorUserId,
      payload: JSON.parse(recipient.notification.payload),
      read: recipient.read,
      createdAt: recipient.notification.createdAt,
    };
  }
}
