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/rascunho_store.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';

/// Execução dos casos do plano `docs/testes/PP-1-plano.md` que são de tela.
/// Os de servidor vivem em `bolao-backend/test/palpite.e2e-spec.ts`.

class _StoreFalso extends RascunhoStore {
  final Map<String, List<PalpiteRascunho>> _dados = {};

  @override
  Future<List<PalpiteRascunho>> ler(String poolId, int matchday) async =>
      _dados['$poolId.$matchday'] ?? const [];

  @override
  Future<void> salvar(
    String poolId,
    int matchday,
    List<PalpiteRascunho> palpites,
  ) async {
    _dados['$poolId.$matchday'] = palpites;
  }
}

class _RepoFalso extends BoloesRepository {
  _RepoFalso({this.jogos, this.erro}) : super(ApiClient(SessaoStore()));

  final List<JogoDaRodada>? jogos;
  final ApiError? erro;

  int cargas = 0;
  List<({int casa, int fora, String matchId})>? enviado;

  static final agora = DateTime.utc(2026, 8, 7, 12);

  @override
  Future<Rodada> rodada(String poolId, int matchday) async {
    cargas++;
    if (erro != null) throw erro!;
    return Rodada(
      serverTime: agora,
      jogos:
          jogos ??
          [
            JogoDaRodada(
              matchId: 'm1',
              homeTeam: 'Flamengo',
              awayTeam: 'Vasco',
              kickoffAt: agora.add(const Duration(days: 1)),
              deadlineOpen: true,
            ),
          ],
    );
  }

  @override
  Future<ResultadoEnvio> palpitar(
    String poolId,
    List<({int casa, int fora, String matchId})> palpites,
  ) async {
    enviado = palpites;
    return ResultadoEnvio(
      salvos: palpites.length,
      recusados: 0,
      motivos: const {},
    );
  }
}

Widget _app(_RepoFalso repo) => ProviderScope(
  overrides: [
    boloesRepositoryProvider.overrideWithValue(repo),
    rascunhoStoreProvider.overrideWithValue(_StoreFalso()),
  ],
  child: const MaterialApp(home: RodadaPage(poolId: 'p1', matchday: 20)),
);

void main() {
  setUp(() => SharedPreferences.setMockInitialValues({}));

  group('PP-1 tela de palpite', () {
    testWidgets('PP-1-01 cada jogo traz dois campos, com teclado numérico', (
      t,
    ) async {
      await t.pumpWidget(_app(_RepoFalso()));
      await t.pumpAndSettle();

      expect(find.textContaining('Flamengo'), findsOneWidget);
      final campos = t
          .widgetList<TextFormField>(find.byType(TextFormField))
          .toList();
      expect(campos, hasLength(2));
      // O casual digita com pressa: teclado de texto aqui custa palpite.
      for (final c in campos) {
        expect(c.enabled, isTrue);
      }
    });

    testWidgets('PP-1-05 o campo não aceita letra, sinal nem 3 dígitos', (
      t,
    ) async {
      final repo = _RepoFalso();
      await t.pumpWidget(_app(repo));
      await t.pumpAndSettle();

      final casa = find.byType(TextFormField).first;
      await t.enterText(casa, '-2');
      await t.pumpAndSettle();
      expect(find.text('-2'), findsNothing);
      // Só dígitos passam pelo formatador.
      expect(find.text('2'), findsOneWidget);

      await t.enterText(casa, 'ab');
      await t.pumpAndSettle();
      expect(find.text('ab'), findsNothing);

      await t.enterText(casa, '123');
      await t.pumpAndSettle();
      // Limite de 2 dígitos: o mesmo teto do servidor, para a recusa não chegar
      // sem explicação.
      expect(find.text('123'), findsNothing);
      expect(find.text('12'), findsOneWidget);
    });

    testWidgets('PP-1-11 envio confirma que salvou', (t) async {
      final repo = _RepoFalso();
      await t.pumpWidget(_app(repo));
      await t.pumpAndSettle();

      await t.enterText(find.byType(TextFormField).at(0), '2');
      await t.enterText(find.byType(TextFormField).at(1), '1');
      await t.pumpAndSettle();
      await t.tap(find.text('Enviar palpites'));
      await t.pumpAndSettle();

      expect(repo.enviado, hasLength(1));
      expect(repo.enviado!.first.casa, 2);
      // Sem confirmação explícita o casual reenvia por insegurança — foi o que
      // motivou a fila offline de PP-4.
      expect(find.textContaining('palpite(s) salvo'), findsOneWidget);
    });

    testWidgets('PP-1-12 rodada sem jogo tem estado próprio', (t) async {
      await t.pumpWidget(_app(_RepoFalso(jogos: const [])));
      await t.pumpAndSettle();

      expect(find.text('Nenhum jogo nesta rodada'), findsOneWidget);
      // Tela vazia sem texto pareceria falha de carga.
      expect(find.textContaining('Escolha outra rodada'), findsOneWidget);
    });

    testWidgets('PP-1-13 falha de carga mostra motivo e permite retry', (
      t,
    ) async {
      final repo = _RepoFalso(erro: ApiError('não foi possível conectar'));
      await t.pumpWidget(_app(repo));
      await t.pumpAndSettle();

      expect(find.text('não foi possível conectar'), findsOneWidget);
      expect(find.text('Não foi possível carregar'), findsOneWidget);
      // Lista vazia por erro de rede seria lida como "o bolão não tem jogos".
      expect(find.text('Nenhum jogo nesta rodada'), findsNothing);

      await t.tap(find.text('Tentar de novo'));
      await t.pumpAndSettle();
      expect(repo.cargas, 2);
    });
  });
}
