import { getFirestore, type Firestore } from 'firebase-admin/firestore';
import { HttpsError, onCall } from 'firebase-functions/v2/https';
import { bolaoOuErro, exigirAdmin, uidDe } from './acesso';

/**
 * Correção manual de placar pelo admin (`PR-10`) — **portada** de
 * `correcao.service.ts`.
 *
 * Ela não apura nem avisa ninguém: escreve o placar e a trilha de auditoria, e
 * quem reage é o gatilho de apuração (`MG-5`), que é o mesmo caminho da correção
 * vinda da fonte oficial. Dois caminhos para a mesma regra fariam um deles
 * envelhecer — e seria justamente o menos usado, o manual, que é o que precisa
 * de mais confiança.
 *
 * A trilha de auditoria é o que sustenta `PR-6`: **ponto que cai sem explicação
 * é indistinguível de fraude.** O extrato lê daqui para dizer qual era o placar
 * antes e que foi o administrador quem mexeu.
 */

export const corrigirPlacar = onCall(async (request) =>
  corrigir(
    uidDe(request),
    (request.data?.poolId ?? '') as string,
    (request.data?.matchId ?? '') as string,
    request.data?.homeScore as number,
    request.data?.awayScore as number,
  ),
);

export async function corrigir(
  uid: string,
  poolId: string,
  matchId: string,
  casa: number,
  fora: number,
  db: Firestore = getFirestore(),
): Promise<{ afetados: number; mudou: boolean }> {
  const bolao = await bolaoOuErro(db, poolId);
  await exigirAdmin(db, poolId, uid);

  if (
    !Number.isInteger(casa) ||
    !Number.isInteger(fora) ||
    casa < 0 ||
    fora < 0 ||
    casa > 99 ||
    fora > 99
  ) {
    throw new HttpsError('invalid-argument', 'placar inválido');
  }

  const ref = db.doc(`jogos/${matchId}`);
  const jogo = (await ref.get()).data();
  if (!jogo) throw new HttpsError('not-found', 'jogo não encontrado');

  // O jogo é global; pertencer ao bolão é estar no campeonato e na faixa de
  // rodadas dele. Sem esta checagem, o admin de um bolão corrigiria o placar
  // que vale para todos os outros.
  const doBolao =
    jogo.competitionCode === bolao.competitionCode &&
    jogo.matchday >= bolao.matchdayFrom &&
    jogo.matchday <= bolao.matchdayTo;
  if (!doBolao) {
    throw new HttpsError('not-found', 'este jogo não pertence ao bolão');
  }

  // Permitido depois do prazo mesmo sem `FINISHED`: é assim que um W.O. — que
  // nunca é reagendado — deixa de manter o bolão pendente para sempre.
  const prazoEncerrado = jogo.kickoffAt.toMillis() <= Date.now();
  if (!prazoEncerrado && jogo.status !== 'FINISHED') {
    throw new HttpsError(
      'failed-precondition',
      'não é possível informar placar de jogo que ainda não começou',
      { motivo: 'NAO_COMECOU' },
    );
  }

  if (jogo.homeScore === casa && jogo.awayScore === fora) {
    // Nada mudou: não reapura e não avisa ninguém (`PR-6`).
    return { afetados: 0, mudou: false };
  }

  const palpites = await db
    .collection(`boloes/${poolId}/palpites`)
    .where('matchId', '==', matchId)
    .count()
    .get();

  const lote = db.batch();
  lote.set(db.collection(`jogos/${matchId}/correcoes`).doc(), {
    // Repetido no documento para o extrato buscar por consulta de grupo: sem
    // ele seria uma consulta por jogo da rodada.
    matchId,
    previousHome: jogo.homeScore ?? null,
    previousAway: jogo.awayScore ?? null,
    newHome: casa,
    newAway: fora,
    source: 'MANUAL',
    correctedBy: uid,
    poolId,
    createdAt: new Date(),
  });
  lote.update(ref, {
    homeScore: casa,
    awayScore: fora,
    status: 'FINISHED',
    lastCorrectionSource: 'MANUAL',
  });
  await lote.commit();

  // **O número de avisos não volta daqui.** No NestJS a correção apurava em
  // seguida e sabia quantos mudaram de posição; aqui quem apura é gatilho, e
  // esperar por ele seria segurar o admin numa tela por causa de um número.
  // O que a tela pode dizer com honestidade é quantos palpites serão reapurados.
  return { afetados: palpites.data().count, mudou: true };
}
