import type { Firestore } from 'firebase-admin/firestore';
import { HttpsError, type CallableRequest } from 'firebase-functions/v2/https';

/**
 * Verificações de acesso compartilhadas pelas callables.
 *
 * As security rules já barram leitura e escrita direta; isto aqui é a mesma
 * regra do lado de quem escreve com o Admin SDK, que **ignora rules**. Function
 * que esquece de checar papel é um furo tão grande quanto rule aberta — e mais
 * silencioso, porque nenhum teste de rule o pega.
 */

/** Uid do chamador, ou erro de não autenticado. */
export function uidDe(request: CallableRequest): string {
  const uid = request.auth?.uid;
  if (!uid) {
    throw new HttpsError('unauthenticated', 'faça login para continuar');
  }
  return uid;
}

export interface DadosDoBolao {
  name: string;
  competitionCode: string;
  matchdayFrom: number;
  matchdayTo: number;
  createdBy: string;
  tiebreakOrder: ('EXACT_SCORES' | 'WINNERS')[];
  closedAt: FirebaseFirestore.Timestamp | null;
  archivedAt: FirebaseFirestore.Timestamp | null;
  activeMembers: number;
}

export async function bolaoOuErro(
  db: Firestore,
  poolId: string,
): Promise<DadosDoBolao> {
  const doc = await db.doc(`boloes/${poolId}`).get();
  if (!doc.exists) {
    throw new HttpsError('not-found', 'bolão não encontrado');
  }
  return doc.data() as DadosDoBolao;
}

export async function exigirMembroAtivo(
  db: Firestore,
  poolId: string,
  uid: string,
): Promise<{ role: string }> {
  const membro = await db.doc(`boloes/${poolId}/membros/${uid}`).get();
  const dados = membro.data();
  if (!membro.exists || dados?.leftAt != null) {
    throw new HttpsError('permission-denied', 'você não participa deste bolão');
  }
  return { role: dados!.role as string };
}

export async function exigirAdmin(
  db: Firestore,
  poolId: string,
  uid: string,
): Promise<void> {
  const membro = await db.doc(`boloes/${poolId}/membros/${uid}`).get();
  const dados = membro.data();
  if (!membro.exists || dados?.leftAt != null || dados?.role !== 'ADMIN') {
    throw new HttpsError(
      'permission-denied',
      'apenas o administrador do bolão pode fazer isso',
    );
  }
}

/**
 * Início do primeiro jogo do bolão — a hora em que a **entrada fecha**.
 *
 * Recalculado a cada checagem, em vez de guardado no bolão: jogo adiado muda o
 * horário (`PP-6`), e um `firstKickoffAt` congelado manteria a entrada fechada
 * por causa de um jogo que nem vai mais acontecer naquele horário.
 *
 * O preço é ler os jogos das rodadas do bolão. `select()` não reduz o número de
 * documentos cobrados, mas reduz o tráfego — e entrar em bolão é evento raro,
 * ao contrário de palpitar, que é o caminho que não pode gastar leitura.
 */
export async function primeiroInicio(
  db: Firestore,
  bolao: Pick<
    DadosDoBolao,
    'competitionCode' | 'matchdayFrom' | 'matchdayTo'
  >,
): Promise<Date | null> {
  const jogos = await db
    .collection('jogos')
    .where('competitionCode', '==', bolao.competitionCode)
    .where('matchday', '>=', bolao.matchdayFrom)
    .where('matchday', '<=', bolao.matchdayTo)
    .select('kickoffAt')
    .get();

  if (jogos.empty) return null;
  const inicios = jogos.docs.map((d) =>
    (d.data().kickoffAt as FirebaseFirestore.Timestamp).toMillis(),
  );
  return new Date(Math.min(...inicios));
}

/**
 * A entrada fecha no início do primeiro jogo — e é a **mesma** verificação para
 * gerar convite e para entrar por ele.
 *
 * Gerar o link de um bolão já começado entregava ao admin um código que nunca
 * funcionaria: ele mandava no grupo e o convidado é que descobria.
 */
export function exigirEntradaAberta(primeiro: Date | null): void {
  if (primeiro !== null && primeiro.getTime() <= Date.now()) {
    throw new HttpsError(
      'failed-precondition',
      'este bolão já começou e não aceita mais participantes',
      { motivo: 'JA_COMECOU' },
    );
  }
}

export function exigirBolaoAberto(bolao: DadosDoBolao): void {
  if (bolao.closedAt != null) {
    throw new HttpsError(
      'failed-precondition',
      'este bolão já foi encerrado',
      { motivo: 'ENCERRADO' },
    );
  }
}
