import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';

export interface TabelaDePontos {
  pointsExact: number;
  pointsWinnerDiff: number;
  pointsWinner: number;
}

export type FaixaDePonto =
  'PLACAR_EXATO' | 'VENCEDOR_E_SALDO' | 'SO_VENCEDOR' | 'ERROU';

@Injectable()
export class ScoringService {
  private readonly log = new Logger(ScoringService.name);

  constructor(private readonly prisma: PrismaService) {}

  /**
   * Faixa de acerto de um palpite contra o placar oficial.
   *
   * Função pura de propósito: é a regra do produto e precisa ser testável sem
   * banco. "Saldo de gols" aqui é a diferença **do jogo**, não o acumulado do
   * campeonato.
   */
  static faixa(
    palpiteCasa: number,
    palpiteFora: number,
    oficialCasa: number,
    oficialFora: number,
  ): FaixaDePonto {
    if (palpiteCasa === oficialCasa && palpiteFora === oficialFora) {
      return 'PLACAR_EXATO';
    }

    const sinal = (a: number, b: number) => Math.sign(a - b);
    const acertouVencedor =
      sinal(palpiteCasa, palpiteFora) === sinal(oficialCasa, oficialFora);
    if (!acertouVencedor) return 'ERROU';

    const saldoIgual = palpiteCasa - palpiteFora === oficialCasa - oficialFora;
    // Empate acertado sem o placar exato cai aqui: o saldo é 0 nos dois lados,
    // mas o placar difere. O PRD decide que vale a faixa de "só vencedor".
    if (saldoIgual && oficialCasa !== oficialFora) return 'VENCEDOR_E_SALDO';

    return 'SO_VENCEDOR';
  }

  static pontos(faixa: FaixaDePonto, tabela: TabelaDePontos): number {
    switch (faixa) {
      case 'PLACAR_EXATO':
        return tabela.pointsExact;
      case 'VENCEDOR_E_SALDO':
        return tabela.pointsWinnerDiff;
      case 'SO_VENCEDOR':
        return tabela.pointsWinner;
      case 'ERROU':
        return 0;
    }
  }

  /**
   * Apura todos os palpites de um jogo, em todos os bolões que o contêm.
   *
   * A tabela de pontos vem **do bolão** (CC-2 BE-3), não de constante: dois
   * bolões com o mesmo jogo podem pontuar diferente.
   */
  async apurarJogo(matchId: string): Promise<number> {
    const jogo = await this.prisma.match.findUnique({
      where: { id: matchId },
      select: { status: true, homeScore: true, awayScore: true },
    });

    if (!jogo || jogo.status !== 'FINISHED') return 0;
    if (jogo.homeScore === null || jogo.awayScore === null) return 0;

    const palpites = await this.prisma.prediction.findMany({
      where: { matchId },
      select: {
        id: true,
        homeScore: true,
        awayScore: true,
        pool: {
          select: {
            pointsExact: true,
            pointsWinnerDiff: true,
            pointsWinner: true,
          },
        },
      },
    });

    let apurados = 0;
    for (const p of palpites) {
      const faixa = ScoringService.faixa(
        p.homeScore,
        p.awayScore,
        jogo.homeScore,
        jogo.awayScore,
      );
      const pontos = ScoringService.pontos(faixa, p.pool);

      await this.prisma.prediction.update({
        where: { id: p.id },
        data: {
          points: pontos,
          band: faixa,
          scoredHomeScore: jogo.homeScore,
          scoredAwayScore: jogo.awayScore,
          scoredAt: new Date(),
        },
      });
      apurados++;
    }

    if (apurados > 0) {
      this.log.log(`jogo ${matchId}: ${apurados} palpite(s) apurado(s)`);
    }
    return apurados;
  }

  /** Apura tudo que está pendente. Chamado pelo job de sincronização. */
  async apurarPendentes(): Promise<number> {
    const jogos = await this.prisma.match.findMany({
      where: {
        status: 'FINISHED',
        homeScore: { not: null },
        predictions: { some: { scoredAt: null } },
      },
      select: { id: true },
    });

    let total = 0;
    for (const j of jogos) total += await this.apurarJogo(j.id);
    return total;
  }
}
