import {
  ConflictException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { RegrasDto, RegrasResponse } from './dto/regras.dto';

@Injectable()
export class RegrasService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * Regras visíveis a qualquer participante (CC-6). O competitivo precisa
   * ler isso **antes** de palpitar, então não é rota de admin.
   */
  async ler(poolId: string, userId: string): Promise<RegrasResponse> {
    await this.exigirParticipante(poolId, userId);

    const pool = await this.prisma.pool.findUnique({
      where: { id: poolId },
      select: {
        pointsExact: true,
        pointsWinnerDiff: true,
        pointsWinner: true,
        tiebreakOrder: true,
        rulesLockedAt: true,
      },
    });
    if (!pool) throw new NotFoundException('bolão não encontrado');

    return { ...pool, nextDeadlineAt: await this.proximoPrazo(poolId) };
  }

  /** Alteração é só do admin, e só enquanto as regras não travaram (CC-3). */
  async alterar(
    poolId: string,
    userId: string,
    dto: RegrasDto,
  ): Promise<RegrasResponse> {
    await this.exigirAdmin(poolId, userId);

    const pool = await this.prisma.pool.findUnique({
      where: { id: poolId },
      select: { rulesLockedAt: true },
    });
    if (!pool) throw new NotFoundException('bolão não encontrado');

    if (pool.rulesLockedAt) {
      // O travamento é o que torna justa a participação do admin no próprio
      // bolão — sem ele, o sigilo de PP-7 não basta.
      throw new ConflictException(
        'as regras travaram no primeiro palpite e não podem mais mudar. Para jogar com regra nova, encerre este bolão e abra outro',
      );
    }

    await this.prisma.pool.update({
      where: { id: poolId },
      data: {
        pointsExact: dto.pointsExact,
        pointsWinnerDiff: dto.pointsWinnerDiff,
        pointsWinner: dto.pointsWinner,
        tiebreakOrder: dto.tiebreakOrder,
      },
    });

    return this.ler(poolId, userId);
  }

  /**
   * Próximo jogo com prazo aberto, medido pelo **horário do servidor**.
   * O relógio do device não entra nesta conta em nenhum ponto (PP-3/PP-5).
   */
  private async proximoPrazo(poolId: string): Promise<Date | null> {
    const proximo = await this.prisma.poolMatch.findFirst({
      where: { poolId, match: { kickoffAt: { gt: new Date() } } },
      orderBy: { match: { kickoffAt: 'asc' } },
      select: { match: { select: { kickoffAt: true } } },
    });
    return proximo?.match.kickoffAt ?? null;
  }

  private async exigirParticipante(
    poolId: string,
    userId: string,
  ): Promise<void> {
    const membro = await this.prisma.poolMember.findUnique({
      where: { poolId_userId: { poolId, userId } },
      select: { leftAt: true },
    });
    // Quem saiu perde o acesso: a saída é definitiva (CV-4).
    if (!membro || membro.leftAt) {
      throw new ForbiddenException('você não participa deste bolão');
    }
  }

  private async exigirAdmin(poolId: string, userId: string): Promise<void> {
    const membro = await this.prisma.poolMember.findUnique({
      where: { poolId_userId: { poolId, userId } },
      select: { role: true, leftAt: true },
    });
    if (!membro || membro.leftAt || membro.role !== 'ADMIN') {
      throw new ForbiddenException(
        'apenas o administrador do bolão pode fazer isso',
      );
    }
  }
}
