import { initializeApp } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
import { getFirestore } from 'firebase-admin/firestore';

/**
 * `MG-4` — autenticação no Firebase Auth.
 *
 * Aqui o que se verifica é **o que o Auth faz por nós e o que ele deixa de fazer**.
 * O backend antigo garantia três coisas na mão: mínimo de senha, resposta genérica
 * para credencial inválida e limite de tentativas. Nenhuma delas é automática, e o
 * teste é o que separa "o Firebase cuida disso" de "achamos que cuidava".
 */
process.env.FIRESTORE_EMULATOR_HOST ??= '127.0.0.1:8080';
process.env.FIREBASE_AUTH_EMULATOR_HOST ??= '127.0.0.1:9099';

jest.setTimeout(30_000);

const app = initializeApp({ projectId: 'demo-bolao' }, `auth-${Date.now()}`);
const auth = getAuth(app);
const db = getFirestore(app);

async function esperar(cond: () => Promise<boolean>, limiteMs = 15000) {
  const fim = Date.now() + limiteMs;
  while (Date.now() < fim) {
    if (await cond()) return true;
    await new Promise((r) => setTimeout(r, 400));
  }
  return false;
}

describe('MG-4 — Auth e perfil', () => {
  const email = `perfil-${Date.now()}@teste.com`;

  afterAll(async () => {
    const lista = await auth.listUsers(100);
    for (const u of lista.users) await auth.deleteUser(u.uid);
  });

  it('MG-4-01 conta nova ganha perfil sem o app precisar criar', async () => {
    // Se a criação do perfil dependesse do app, uma falha de rede logo depois do
    // cadastro deixaria a pessoa sem nome no ranking.
    const criado = await auth.createUser({
      email,
      password: 'senhaSegura123',
      displayName: 'Ana Maria',
    });

    const temPerfil = await esperar(async () =>
      (await db.doc(`usuarios/${criado.uid}`).get()).exists,
    );
    expect(temPerfil).toBe(true);

    const perfil = (await db.doc(`usuarios/${criado.uid}`).get()).data();
    expect(perfil?.displayName).toBe('Ana Maria');
    expect(perfil?.email).toBe(email);
  });

  it('MG-4-02 conta sem nome não fica sem nome no ranking', async () => {
    // Cadastro sem `displayName` acontece: o Auth não exige. Gravar vazio deixaria
    // uma linha de ranking que ninguém identifica.
    const criado = await auth.createUser({
      email: `sem-nome-${Date.now()}@teste.com`,
      password: 'senhaSegura123',
    });

    const temPerfil = await esperar(async () =>
      (await db.doc(`usuarios/${criado.uid}`).get()).exists,
    );
    expect(temPerfil).toBe(true);
    const perfil = (await db.doc(`usuarios/${criado.uid}`).get()).data();
    expect(perfil?.displayName).toBeTruthy();
  });

  it('MG-4-03 o Auth aceita senha de 6 — o mínimo de 8 é nosso, e é do app', async () => {
    // **Descoberta registrada, não contornada.** A política do produto é 8
    // caracteres (`AC-1`), e o Firebase Auth puro aceita 6. O app valida 8 antes de
    // chamar, mas quem falar direto com a API cria com 6.
    //
    // Fechar isso exige política de senha do Identity Platform ou uma *blocking
    // function* — as duas são upgrade de plano de Auth. Fica como decisão do dono
    // em `MG-8`, com o número real registrado em vez de suposto.
    const seisCaracteres = auth.createUser({
      email: `curta-${Date.now()}@teste.com`,
      password: 'abc123',
    });
    await expect(seisCaracteres).resolves.toBeTruthy();

    const cincoCaracteres = auth.createUser({
      email: `curtissima-${Date.now()}@teste.com`,
      password: 'abc12',
    });
    await expect(cincoCaracteres).rejects.toThrow();
  });

  it('MG-4-04 e-mail duplicado é recusado pelo Auth', async () => {
    // Auto-contido de propósito: a primeira versão dependia da conta criada em
    // `MG-4-01` e passava só quando a suíte rodava inteira, na ordem. Teste que
    // depende de outro teste mente em execução isolada — e execução isolada é
    // justamente como se investiga uma falha.
    const duplicado = `dup-${Date.now()}@teste.com`;
    await auth.createUser({ email: duplicado, password: 'senhaSegura123' });

    // Asserção pelo **código** do erro, não pela mensagem: a primeira versão
    // esperava /already exists/ e a mensagem real é "already in use". Texto em
    // inglês vindo do fornecedor muda sem avisar; código não.
    await expect(
      auth.createUser({ email: duplicado, password: 'outraSenha123' }),
    ).rejects.toMatchObject({ code: 'auth/email-already-exists' });
  });

  it('AC-1-01 cadastro válido cria a conta e a identifica', async () => {
    const email = `ac1-${Date.now()}@teste.com`;
    const conta = await auth.createUser({ email, password: 'senhaforte123' });

    // Identificar é ter uid estável e e-mail associado — o resto do produto
    // pendura tudo nisso.
    expect(conta.uid).toBeTruthy();
    expect(conta.email).toBe(email);
  });

  it('AC-1-07 o e-mail é normalizado para minúsculas', async () => {
    const email = `AC1Maiusculo-${Date.now()}@Teste.com`;
    const conta = await auth.createUser({ email, password: 'senhaforte123' });

    // Sem isso, "Ana@x.com" e "ana@x.com" viram duas contas, e a segunda parece
    // ter perdido o histórico da primeira.
    expect(conta.email).toBe(email.toLowerCase());
  });

  it('AC-1-04 e-mail malformado é recusado', async () => {
    await expect(
      auth.createUser({ email: 'sem-arroba', password: 'senhaforte123' }),
    ).rejects.toMatchObject({ code: 'auth/invalid-email' });
  });

  it('AC-3-11 conta apagada perde o acesso na hora', async () => {
    const email = `ac3-${Date.now()}@teste.com`;
    const conta = await auth.createUser({ email, password: 'senhaforte123' });
    await auth.deleteUser(conta.uid);

    // O token continua parecendo válido até expirar, mas o uid não existe mais:
    // qualquer callable que verifique o perfil cai fora, e o SDK derruba a sessão
    // na primeira renovação.
    await expect(auth.getUser(conta.uid)).rejects.toMatchObject({
      code: 'auth/user-not-found',
    });
  });
});
