import { Agregado, classificar } from './classificacao';
import type { CriterioDesempate } from './classificacao';

/**
 * **Portado do backend NestJS em 07/08 (MG-5).** Mesmos IDs de caso, mesma
 * regra: é o que permite comparar a migração linha a linha com a execução
 * anterior. Se um caso mudar aqui, mudou a regra — e isso é defeito de migração.
 *
 * O `!` depois de `[0]` é adaptação ao `noUncheckedIndexedAccess` deste projeto,
 * que o backend antigo não usava. Muda o compilador, não o que o caso afirma.
 *
 * Execução do plano `docs/testes/PR-5-plano.md`. Os nomes carregam o ID do caso
 * para a reabertura de task ser greppável no código.
 */
const p = (
  displayName: string,
  points: number,
  exactScores: number,
  winners: number,
): Agregado => ({
  userId: displayName.toLowerCase(),
  displayName,
  points,
  exactScores,
  winners,
});

const INVERTIDA: CriterioDesempate[] = ['WINNERS', 'EXACT_SCORES'];

describe('Plano PR-5 — desempate', () => {
  it('PR-5-01 empate resolvido por placares exatos, visível nos dois lados', () => {
    const r = classificar([p('Ana', 22, 1, 5), p('Bruno', 22, 0, 6)]);

    expect(r[0]!.displayName).toBe('Ana');
    expect(r[0]!.decidedBy).toBe('EXACT_SCORES');
    expect(r[1]!.decidedBy).toBe('EXACT_SCORES');
  });

  it('PR-5-02 empate resolvido por acertos de vencedor', () => {
    const r = classificar([p('Ana', 20, 2, 3), p('Bruno', 20, 2, 6)]);

    expect(r[0]!.displayName).toBe('Bruno');
    expect(r[0]!.decidedBy).toBe('WINNERS');
    expect(r[1]!.decidedBy).toBe('WINNERS');
  });

  it('PR-5-03 empate persistente divide a posição', () => {
    const r = classificar([p('Ana', 20, 2, 5), p('Bruno', 20, 2, 5)]);

    expect(r.map((l) => l.position)).toEqual([1, 1]);
    expect(r.every((l) => l.sharedPosition)).toBe(true);
    expect(r.every((l) => l.decidedBy === 'EMPATE_PERSISTE')).toBe(true);
  });

  it('PR-5-05 quem perdeu por pontos não recebe critério de desempate', () => {
    const r = classificar([p('Ana', 30, 1, 4), p('Bruno', 20, 3, 6)]);
    expect(r.every((l) => l.decidedBy === 'PONTOS')).toBe(true);
  });

  it('PR-5-06 três empatados dividem a mesma posição', () => {
    const r = classificar([
      p('Ana', 15, 1, 3),
      p('Bruno', 15, 1, 3),
      p('Carla', 15, 1, 3),
    ]);

    expect(r.map((l) => l.position)).toEqual([1, 1, 1]);
    expect(r.every((l) => l.sharedPosition)).toBe(true);
  });

  it('PR-5-07 a posição seguinte pula pela quantidade de empatados', () => {
    const r = classificar([
      p('Ana', 20, 2, 5),
      p('Bruno', 20, 2, 5),
      p('Carla', 10, 0, 2),
    ]);

    // 1º, 1º, 3º — nunca 1º, 1º, 2º.
    expect(r.map((l) => l.position)).toEqual([1, 1, 3]);
  });

  it('PR-5-08 a ordem configurada no bolão muda a classificação', () => {
    const agregados = [p('Ana', 20, 3, 4), p('Bruno', 20, 1, 6)];

    // Padrão: exatos primeiro → Ana na frente.
    const padrao = classificar(agregados);
    expect(padrao[0]!.displayName).toBe('Ana');
    expect(padrao[0]!.decidedBy).toBe('EXACT_SCORES');

    // Invertida: vencedores primeiro → Bruno na frente, e o critério exibido
    // tem que ser WINNERS. Dizer "placares exatos" aqui seria explicar a
    // classificação pela regra errada.
    const invertida = classificar(agregados, INVERTIDA);
    expect(invertida[0]!.displayName).toBe('Bruno');
    expect(invertida[0]!.decidedBy).toBe('WINNERS');
    expect(invertida[1]!.decidedBy).toBe('WINNERS');
  });

  it('PR-5-08b ordem invertida também muda quem divide a posição', () => {
    // Com a ordem padrão, Ana e Bruno se separam por exatos. Com a invertida,
    // eles empatam em vencedores **e** em exatos? Não: aqui só um critério
    // separa, e a ordem decide qual.
    const agregados = [p('Ana', 12, 2, 4), p('Bruno', 12, 1, 4)];

    const padrao = classificar(agregados);
    expect(padrao[0]!.displayName).toBe('Ana');
    expect(padrao.every((l) => l.sharedPosition)).toBe(false);

    // Invertida: empatam em vencedores, então cai no critério seguinte
    // (exatos) — Ana continua na frente, mas pelo segundo critério.
    const invertida = classificar(agregados, INVERTIDA);
    expect(invertida[0]!.displayName).toBe('Ana');
    expect(invertida[0]!.decidedBy).toBe('EXACT_SCORES');
  });

  it('PR-5-08c ordem vazia ou inválida cai no padrão, sem ficar sem desempate', () => {
    const agregados = [p('Ana', 20, 3, 4), p('Bruno', 20, 1, 6)];

    for (const ordem of [
      [] as CriterioDesempate[],
      ['NAO_EXISTE'] as unknown as CriterioDesempate[],
    ]) {
      const r = classificar(agregados, ordem);
      expect(r[0]!.displayName).toBe('Ana');
      expect(r[0]!.decidedBy).toBe('EXACT_SCORES');
      expect(r.every((l) => l.sharedPosition)).toBe(false);
    }
  });

  it('PR-5-09 ranking de um só participante', () => {
    const r = classificar([p('Ana', 7, 0, 1)]);
    expect(r[0]!.position).toBe(1);
    expect(r[0]!.sharedPosition).toBe(false);
    expect(r[0]!.decidedBy).toBe('PONTOS');
  });
});
