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

/**
 * Papel e saída — **portados** de `members.service.ts`.
 *
 * Todas passam por callable porque nenhuma é decidível olhando só o documento
 * que está sendo escrito: "não pode ficar sem admin" exige **contar** os admins,
 * e rule não conta coleção.
 */

async function membroAtivo(
  db: Firestore,
  poolId: string,
  alvo: string,
): Promise<{ role: string }> {
  const doc = await db.doc(`boloes/${poolId}/membros/${alvo}`).get();
  if (!doc.exists) {
    throw new HttpsError(
      'not-found',
      'participante não encontrado neste bolão',
    );
  }
  if (doc.data()!.leftAt != null) {
    throw new HttpsError(
      'failed-precondition',
      'este participante já saiu do bolão',
    );
  }
  return { role: doc.data()!.role as string };
}

async function quantosAdmins(db: Firestore, poolId: string): Promise<number> {
  const admins = await db
    .collection(`boloes/${poolId}/membros`)
    .where('role', '==', 'ADMIN')
    .where('leftAt', '==', null)
    .count()
    .get();
  // `count()` é agregação: cobra por resultado lido, não por documento. Contar
  // admin com `get()` gastaria uma leitura por participante toda vez.
  return admins.data().count;
}

/** Promove a admin. Mais de um admin é permitido e desejado (`CC-8`). */
export const promoverAdmin = onCall(async (request) =>
  promover(
    uidDe(request),
    (request.data?.poolId ?? '') as string,
    (request.data?.userId ?? '') as string,
  ),
);

export async function promover(
  uid: string,
  poolId: string,
  alvo: string,
  db: Firestore = getFirestore(),
): Promise<{ changed: boolean }> {
  await bolaoOuErro(db, poolId);
  await exigirAdmin(db, poolId, uid);
  const membro = await membroAtivo(db, poolId, alvo);
  if (membro.role === 'ADMIN') return { changed: false };

  await db.doc(`boloes/${poolId}/membros/${alvo}`).update({ role: 'ADMIN' });
  return { changed: true };
}

export const rebaixarAdmin = onCall(async (request) =>
  rebaixar(
    uidDe(request),
    (request.data?.poolId ?? '') as string,
    (request.data?.userId ?? '') as string,
  ),
);

export async function rebaixar(
  uid: string,
  poolId: string,
  alvo: string,
  db: Firestore = getFirestore(),
): Promise<{ changed: boolean }> {
  const bolao = await bolaoOuErro(db, poolId);
  await exigirAdmin(db, poolId, uid);
  await membroAtivo(db, poolId, alvo);

  // Decidido em 03/08: quem cria o bolão é admin permanente e não pode ser
  // rebaixado — nem por outro admin, nem por si mesmo.
  if (alvo === bolao.createdBy) {
    throw new HttpsError(
      'failed-precondition',
      'quem criou o bolão é administrador permanente e não pode ser rebaixado',
    );
  }
  if ((await quantosAdmins(db, poolId)) <= 1) {
    // Bolão sem admin é estado irrecuperável pelo app.
    throw new HttpsError(
      'failed-precondition',
      'o bolão não pode ficar sem administrador',
    );
  }

  await db.doc(`boloes/${poolId}/membros/${alvo}`).update({ role: 'MEMBER' });
  return { changed: true };
}

/**
 * Saída voluntária (`CV-4`). Definitiva: o vínculo é **encerrado, não apagado**
 * — os palpites já apurados ficam no histórico e o ranking de quem ficou não é
 * recalculado.
 */
export const sairDoBolao = onCall(async (request) =>
  sair(uidDe(request), (request.data?.poolId ?? '') as string),
);

export async function sair(
  uid: string,
  poolId: string,
  db: Firestore = getFirestore(),
): Promise<{ left: boolean }> {
  const bolao = await bolaoOuErro(db, poolId);
  const membro = await membroAtivo(db, poolId, uid);

  if (uid === bolao.createdBy) {
    // Consequência de "criador é admin permanente": ele nunca consegue sair.
    throw new HttpsError(
      'failed-precondition',
      'quem criou o bolão é administrador permanente e não pode sair dele',
    );
  }
  if (membro.role === 'ADMIN' && (await quantosAdmins(db, poolId)) <= 1) {
    throw new HttpsError(
      'failed-precondition',
      'promova outro administrador antes de sair — o bolão não pode ficar sem admin',
    );
  }

  await db.doc(`boloes/${poolId}/membros/${uid}`).update({ leftAt: new Date() });
  return { left: true };
}

/**
 * Remoção pelo admin (`CC-9`). Usa a **mesma** regra da saída voluntária: os
 * palpites do removido ficam no histórico, para o admin não poder apagar
 * histórico alheio.
 */
export const removerParticipante = onCall(async (request) =>
  remover(
    uidDe(request),
    (request.data?.poolId ?? '') as string,
    (request.data?.userId ?? '') as string,
  ),
);

export async function remover(
  uid: string,
  poolId: string,
  alvo: string,
  db: Firestore = getFirestore(),
): Promise<{ removed: boolean }> {
  const bolao = await bolaoOuErro(db, poolId);
  await exigirAdmin(db, poolId, uid);
  await membroAtivo(db, poolId, alvo);

  if (alvo === bolao.createdBy) {
    throw new HttpsError(
      'failed-precondition',
      'quem criou o bolão não pode ser removido',
    );
  }
  if (alvo === uid) {
    throw new HttpsError(
      'invalid-argument',
      'para sair do bolão use a ação de sair',
    );
  }

  await db.doc(`boloes/${poolId}/membros/${alvo}`).update({ leftAt: new Date() });
  return { removed: true };
}
