import { Agregado, RankingService } from './ranking.service';

const p = (
  nome: string,
  points: number,
  exactScores: number,
  winners: number,
): Agregado => ({
  userId: nome,
  displayName: nome,
  points,
  exactScores,
  winners,
});

describe('RankingService.ordenar — desempate e posição', () => {
  it('ordena por pontos, do maior para o menor', () => {
    const r = RankingService.ordenar([
      p('B', 10, 1, 1),
      p('A', 30, 0, 3),
      p('C', 20, 2, 2),
    ]);
    expect(r.map((l) => l.displayName)).toEqual(['A', 'C', 'B']);
    expect(r.map((l) => l.position)).toEqual([1, 2, 3]);
  });

  it('empate em pontos é resolvido por placares exatos', () => {
    const r = RankingService.ordenar([p('A', 20, 1, 4), p('B', 20, 3, 4)]);
    expect(r[0].displayName).toBe('B');
    expect(r[0].position).toBe(1);
    expect(r[1].decidedBy).toBe('EXACT_SCORES');
    expect(r[1].sharedPosition).toBe(false);
  });

  it('quem lidera o empate também vê o critério que o colocou à frente', () => {
    // O AC de PR-5 é explícito: "estou empatado com alguém → vejo qual
    // critério está definindo a ordem entre nós". Vale para os dois lados —
    // quem está na frente é justamente quem é acusado de estar na frente.
    const r = RankingService.ordenar([p('A', 20, 1, 4), p('B', 20, 3, 4)]);
    expect(r[0].displayName).toBe('B');
    expect(r[0].decidedBy).toBe('EXACT_SCORES');
    expect(r[1].decidedBy).toBe('EXACT_SCORES');
  });

  it('líder isolado continua sendo decidido por pontos', () => {
    // Sem empate no topo não há critério a explicar: dizer "desempate" aqui
    // sugeriria uma disputa que não existe.
    const r = RankingService.ordenar([p('A', 30, 1, 4), p('B', 20, 3, 4)]);
    expect(r[0].decidedBy).toBe('PONTOS');
    expect(r[1].decidedBy).toBe('PONTOS');
  });

  it('empate no topo não contamina o critério de quem está fora dele', () => {
    const r = RankingService.ordenar([
      p('A', 20, 3, 4),
      p('B', 20, 1, 4),
      p('C', 10, 5, 9),
    ]);
    expect(r[0].decidedBy).toBe('EXACT_SCORES');
    expect(r[1].decidedBy).toBe('EXACT_SCORES');
    // C perdeu por pontos, não por desempate.
    expect(r[2].decidedBy).toBe('PONTOS');
  });

  it('empate em pontos e exatos é resolvido por acertos de vencedor', () => {
    const r = RankingService.ordenar([p('A', 20, 2, 3), p('B', 20, 2, 6)]);
    expect(r[0].displayName).toBe('B');
    expect(r[1].decidedBy).toBe('WINNERS');
  });

  it('empate que resiste a tudo divide a posição', () => {
    const r = RankingService.ordenar([p('A', 20, 2, 5), p('B', 20, 2, 5)]);
    expect(r[0].position).toBe(1);
    expect(r[1].position).toBe(1);
    expect(r.every((l) => l.sharedPosition)).toBe(true);
    expect(r[1].decidedBy).toBe('EMPATE_PERSISTE');
  });

  it('a posição seguinte pula pela quantidade de empatados', () => {
    // Dois em 1º → o próximo é 3º, não 2º. Errar isso produz "dois em 2º e
    // ninguém em 3º", que é o bug clássico de ranking com empate.
    const r = RankingService.ordenar([
      p('A', 20, 2, 5),
      p('B', 20, 2, 5),
      p('C', 10, 1, 3),
    ]);
    expect(r.map((l) => l.position)).toEqual([1, 1, 3]);
  });

  it('três ou mais empatados dividem a mesma posição', () => {
    const r = RankingService.ordenar([
      p('A', 20, 2, 5),
      p('B', 20, 2, 5),
      p('C', 20, 2, 5),
      p('D', 5, 0, 1),
    ]);
    expect(r.map((l) => l.position)).toEqual([1, 1, 1, 4]);
    expect(r.slice(0, 3).every((l) => l.sharedPosition)).toBe(true);
    expect(r[3].sharedPosition).toBe(false);
  });

  it('empate no meio da tabela também compartilha, sem afetar o topo', () => {
    const r = RankingService.ordenar([
      p('A', 30, 3, 6),
      p('B', 20, 2, 5),
      p('C', 20, 2, 5),
      p('D', 10, 1, 2),
    ]);
    expect(r.map((l) => l.position)).toEqual([1, 2, 2, 4]);
    expect(r[0].sharedPosition).toBe(false);
    expect(r[3].sharedPosition).toBe(false);
  });

  it('participante sem nenhum ponto entra no ranking, não desaparece', () => {
    const r = RankingService.ordenar([p('A', 10, 1, 2), p('Zerado', 0, 0, 0)]);
    expect(r).toHaveLength(2);
    expect(r[1].displayName).toBe('Zerado');
    expect(r[1].position).toBe(2);
  });

  it('bolão vazio devolve lista vazia sem quebrar', () => {
    expect(RankingService.ordenar([])).toEqual([]);
  });

  it('um participante só fica em 1º sem posição compartilhada', () => {
    const r = RankingService.ordenar([p('Único', 7, 0, 1)]);
    expect(r[0].position).toBe(1);
    expect(r[0].sharedPosition).toBe(false);
    expect(r[0].decidedBy).toBe('PONTOS');
  });
});
