import 'package:bolao_mobile/core/api/api_client.dart';
import 'package:bolao_mobile/core/sessao/sessao_store.dart';
import 'package:bolao_mobile/features/boloes/data/boloes_repository.dart';
import 'package:bolao_mobile/features/boloes/data/participantes_repository.dart';
import 'package:bolao_mobile/features/boloes/presentation/editor_de_regras.dart';
import 'package:bolao_mobile/features/boloes/presentation/participantes_page.dart';
import 'package:bolao_mobile/features/boloes/presentation/regras_page.dart';
import 'package:bolao_mobile/features/boloes/presentation/rodada_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

class _RegrasRepo extends ParticipantesRepository {
  _RegrasRepo(this.valor) : super(ApiClient(SessaoStore()));

  Regras valor;

  @override
  Future<Regras> regras(String poolId) async => valor;
}

class _BoloesRepo extends BoloesRepository {
  _BoloesRepo({this.erro}) : super(ApiClient(SessaoStore()));

  final ApiError? erro;
  final List<({int exato, int saldo, int vencedor, List<String> ordem})>
  alteracoes = [];

  @override
  Future<void> alterarRegras(
    String poolId, {
    required int pontosExato,
    required int pontosVencedorSaldo,
    required int pontosVencedor,
    required List<String> ordemDesempate,
  }) async {
    if (erro != null) throw erro!;
    alteracoes.add((
      exato: pontosExato,
      saldo: pontosVencedorSaldo,
      vencedor: pontosVencedor,
      ordem: ordemDesempate,
    ));
  }
}

Regras _regras({bool travadas = false}) => Regras(
  pontosExato: 10,
  pontosVencedorSaldo: 5,
  pontosVencedor: 3,
  ordemDesempate: const ['EXACT_SCORES', 'WINNERS'],
  travadas: travadas,
  proximoPrazo: null,
);

Widget _app({
  required Regras regras,
  required bool souAdmin,
  _BoloesRepo? boloes,
}) => ProviderScope(
  overrides: [
    participantesRepositoryProvider.overrideWithValue(_RegrasRepo(regras)),
    boloesRepositoryProvider.overrideWithValue(boloes ?? _BoloesRepo()),
  ],
  child: MaterialApp(
    home: RegrasPage(poolId: 'p1', souAdmin: souAdmin),
  ),
);

void main() {
  // As telas gravam e leem cache local; sem o plugin falso o `getInstance`
  // nunca resolve e o `pumpAndSettle` estoura.
  setUp(() => SharedPreferences.setMockInitialValues({}));

  group('ValoresDeRegras', () {
    test('reconhece o padrão do PRD', () {
      expect(ValoresDeRegras.padrao.ehPadrao, isTrue);
      expect(ValoresDeRegras.padrao.copiarCom(exato: 12).ehPadrao, isFalse);
    });

    test('recusa tabela em que acertar mais vale menos', () {
      // Sem esta checagem o admin cria um bolão onde errar o saldo é melhor
      // que acertar, e a briga só aparece na apuração.
      final invertida = ValoresDeRegras.padrao.copiarCom(exato: 2);
      expect(invertida.inconsistencia, isNotNull);

      final semPonto = ValoresDeRegras.padrao.copiarCom(
        exato: 0,
        vencedorSaldo: 0,
        vencedor: 0,
      );
      expect(semPonto.inconsistencia, contains('mais que zero'));
    });

    test('tabela monótona diferente do padrão é válida', () {
      final custom = ValoresDeRegras.padrao.copiarCom(
        exato: 20,
        vencedorSaldo: 10,
        vencedor: 5,
      );
      expect(custom.inconsistencia, isNull);
      expect(custom.ehPadrao, isFalse);
    });

    test('mudar só a ordem do desempate já é sair do padrão', () {
      final outra = ValoresDeRegras.padrao.copiarCom(
        ordemDesempate: const ['WINNERS', 'EXACT_SCORES'],
      );
      expect(outra.ehPadrao, isFalse);
      expect(outra.inconsistencia, isNull);
    });
  });

  group('RegrasPage — edição', () {
    testWidgets('participante comum não recebe a ação de editar', (t) async {
      await t.pumpWidget(_app(regras: _regras(), souAdmin: false));
      await t.pumpAndSettle();

      // A autorização é do servidor (403); a UI não deve prometer.
      expect(find.byIcon(Icons.edit_outlined), findsNothing);
    });

    testWidgets('admin com regras travadas também não edita', (t) async {
      await t.pumpWidget(_app(regras: _regras(travadas: true), souAdmin: true));
      await t.pumpAndSettle();

      // Travado é 409: oferecer o botão seria oferecer uma recusa.
      expect(find.byIcon(Icons.edit_outlined), findsNothing);
    });

    testWidgets('admin com regras travadas recebe o caminho de saída', (
      t,
    ) async {
      await t.pumpWidget(_app(regras: _regras(travadas: true), souAdmin: true));
      await t.pumpAndSettle();
      await t.drag(find.byType(ListView), const Offset(0, -600));
      await t.pumpAndSettle();

      // Sem caminho, o admin tenta burlar — ou pede para mexerem no banco.
      expect(
        find.textContaining('encerrar este bolão e abrir outro'),
        findsOneWidget,
      );
    });

    testWidgets('admin edita e salva a nova tabela', (t) async {
      final boloes = _BoloesRepo();
      await t.pumpWidget(
        _app(regras: _regras(), souAdmin: true, boloes: boloes),
      );
      await t.pumpAndSettle();

      await t.tap(find.byIcon(Icons.edit_outlined));
      await t.pumpAndSettle();

      await t.enterText(find.byType(TextFormField).at(0), '20');
      await t.pumpAndSettle();
      await t.tap(find.text('Salvar regras'));
      await t.pumpAndSettle();

      expect(boloes.alteracoes.length, 1);
      expect(boloes.alteracoes.first.exato, 20);
    });

    testWidgets('tabela inconsistente é barrada antes de chamar o servidor', (
      t,
    ) async {
      final boloes = _BoloesRepo();
      await t.pumpWidget(
        _app(regras: _regras(), souAdmin: true, boloes: boloes),
      );
      await t.pumpAndSettle();

      await t.tap(find.byIcon(Icons.edit_outlined));
      await t.pumpAndSettle();

      // Exato passa a valer menos que "vencedor e saldo".
      await t.enterText(find.byType(TextFormField).at(0), '1');
      await t.pumpAndSettle();
      await t.tap(find.text('Salvar regras'));
      await t.pumpAndSettle();

      expect(boloes.alteracoes, isEmpty);
      expect(find.textContaining('precisa valer pelo menos'), findsWidgets);
    });

    testWidgets('travamento no meio da edição é fluxo esperado, não erro', (
      t,
    ) async {
      final boloes = _BoloesRepo(
        erro: ApiError('regras travadas pelo primeiro palpite', status: 409),
      );
      await t.pumpWidget(
        _app(regras: _regras(), souAdmin: true, boloes: boloes),
      );
      await t.pumpAndSettle();

      await t.tap(find.byIcon(Icons.edit_outlined));
      await t.pumpAndSettle();
      await t.enterText(find.byType(TextFormField).at(0), '15');
      await t.pumpAndSettle();
      await t.tap(find.text('Salvar regras'));
      await t.pumpAndSettle();

      // Alguém palpitou enquanto ele editava: o texto explica em vez de dar
      // "erro", **e sobrevive à volta para o modo leitura** — sem isso a
      // mensagem desaparecia no instante em que precisava ser lida.
      expect(
        find.textContaining('Alguém palpitou enquanto você editava'),
        findsOneWidget,
      );
      expect(find.text('Salvar regras'), findsNothing);
      expect(find.text('Pontuação'), findsOneWidget);
    });
  });
}
