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/prazo.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-5-plano.md` que vivem no app.
/// Os nomes carregam o ID do caso para a reabertura de task ser greppável.
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({required this.serverTime, required this.kickoff})
    : super(ApiClient(SessaoStore()));

  final DateTime serverTime;
  final DateTime kickoff;
  int envios = 0;

  @override
  Future<Rodada> rodada(String poolId, int matchday) async => Rodada(
    serverTime: serverTime,
    jogos: [
      JogoDaRodada(
        matchId: 'm1',
        homeTeam: 'Flamengo',
        awayTeam: 'Vasco',
        kickoffAt: kickoff,
        deadlineOpen: kickoff.isAfter(serverTime),
      ),
    ],
  );

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

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

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

  /// O `serverTime` da resposta é a única fonte do prazo. Estes dois casos
  /// simulam o desvio do aparelho **pelo mesmo caminho que o app usa**: o
  /// offset entre o horário do servidor e o `DateTime.now()` do device.
  group('PP-5-05 / PP-5-06 — desvio do relógio do aparelho', () {
    test('PP-5-05 device 3h à frente não abre prazo fechado', () {
      final agoraDoDevice = DateTime.utc(2026, 8, 6, 15); // 3h à frente
      final relogio = RelogioDoServidor(
        serverTime: DateTime.utc(2026, 8, 6, 12),
        relogioLocal: () => agoraDoDevice,
      );

      // Jogo começou 1h atrás **na hora do servidor**.
      final kickoff = DateTime.utc(2026, 8, 6, 11);
      expect(relogio.aberto(kickoff), isFalse);
    });

    test('PP-5-06 device 5h atrás não fecha prazo aberto', () {
      final agoraDoDevice = DateTime.utc(2026, 8, 6, 7); // 5h atrás
      final relogio = RelogioDoServidor(
        serverTime: DateTime.utc(2026, 8, 6, 12),
        relogioLocal: () => agoraDoDevice,
      );

      // Jogo começa em 30 min na hora do servidor.
      final kickoff = DateTime.utc(2026, 8, 6, 12, 30);
      expect(relogio.aberto(kickoff), isTrue);
      // E o tempo restante é o real, não inflado pelo desvio.
      expect(relogio.restanteAte(kickoff).inMinutes, 30);
    });
  });

  group('PP-5-08 / PP-5-09 — tela de jogo fechado', () {
    testWidgets('PP-5-08 jogo fechado mostra o motivo junto do campo', (
      t,
    ) async {
      await t.pumpWidget(
        _app(
          _RepoFalso(
            serverTime: DateTime.utc(2026, 8, 6, 12),
            kickoff: DateTime.utc(2026, 8, 6, 10),
          ),
        ),
      );
      await t.pumpAndSettle();

      expect(find.text('prazo fechado'), findsOneWidget);
      final campos = t.widgetList<TextFormField>(find.byType(TextFormField));
      expect(campos, isNotEmpty);
      for (final c in campos) {
        expect(c.enabled, isFalse);
      }
    });

    testWidgets('PP-5-09 app não monta envio para jogo fechado', (t) async {
      final repo = _RepoFalso(
        serverTime: DateTime.utc(2026, 8, 6, 12),
        kickoff: DateTime.utc(2026, 8, 6, 10),
      );
      await t.pumpWidget(_app(repo));
      await t.pumpAndSettle();

      await t.tap(find.text('Enviar palpites'));
      await t.pumpAndSettle();

      // Tentar e falhar seria fingir que ainda dava.
      expect(repo.envios, 0);
      expect(find.textContaining('Nada para enviar'), findsOneWidget);
    });
  });

  group('PP-5-10 — prazo fecha com a tela aberta', () {
    testWidgets('PP-5-10 a contagem virando fecha o cartão sem recarregar', (
      t,
    ) async {
      // Kickoff 20s à frente do horário do servidor: o tique de 30s do app
      // recalcula o estado sem nova chamada de rede.
      final repo = _RepoFalso(
        serverTime: DateTime.utc(2026, 8, 6, 12),
        kickoff: DateTime.utc(2026, 8, 6, 12, 0, 20),
      );

      // Relógio do device controlado pelo teste: o app calcula o desvio contra
      // ele, então avançá-lo é o equivalente ao tempo passando de verdade.
      var agoraDoDevice = DateTime.utc(2026, 8, 6, 12);
      await t.pumpWidget(_app(repo, relogioLocal: () => agoraDoDevice));
      await t.pumpAndSettle();

      // Antes: aberto, com contagem em segundos.
      expect(find.textContaining('fecha em'), findsOneWidget);

      // Passam 35s: o tique de 30s do app recalcula sem nova chamada de rede.
      agoraDoDevice = agoraDoDevice.add(const Duration(seconds: 35));
      await t.pump(const Duration(seconds: 35));
      await t.pumpAndSettle();

      expect(find.text('prazo fechado'), findsOneWidget);
      final campos = t.widgetList<TextFormField>(find.byType(TextFormField));
      for (final c in campos) {
        expect(c.enabled, isFalse);
      }
    });
  });
}
