import 'package:bolao_mobile/core/api/api_client.dart';
import 'package:bolao_mobile/core/sessao/sessao_store.dart';
import 'package:bolao_mobile/features/auth/data/auth_repository.dart';
import 'package:bolao_mobile/features/auth/presentation/cadastro_page.dart';
import 'package:bolao_mobile/features/auth/presentation/login_page.dart';
import 'package:bolao_mobile/features/auth/presentation/sessao_controller.dart';
import 'package:bolao_mobile/features/boloes/data/boloes_repository.dart';
import 'package:bolao_mobile/features/boloes/data/convite_pendente.dart';
import 'package:bolao_mobile/features/boloes/data/participantes_repository.dart';
import 'package:bolao_mobile/features/boloes/data/rascunho_store.dart';
import 'package:bolao_mobile/features/boloes/presentation/participantes_page.dart';
import 'package:bolao_mobile/features/boloes/presentation/rodada_page.dart';
import 'package:bolao_mobile/features/home/presentation/home_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 de `CC-5-02` e `CC-5-12` do plano `docs/testes/CC-5-plano.md`, que
/// antes não tinham o que executar.
class _BoloesFalso extends BoloesRepository {
  _BoloesFalso() : super(ApiClient(SessaoStore()));

  final List<String> tokensUsados = [];
  bool entrou = false;

  @override
  Future<List<BolaoResumo>> meus() async => entrou
      ? const [
          BolaoResumo(
            id: 'p1',
            name: 'Bolão do setor',
            matchdayFrom: 20,
            matchdayTo: 22,
            totalMembers: 3,
            isAdmin: false,
            encerrado: false,
          ),
        ]
      : const [];

  @override
  Future<({bool jaParticipava, String poolId, String poolName})>
  entrarPorConvite(String token) async {
    tokensUsados.add(token);
    entrou = true;
    return (poolId: 'p1', poolName: 'Bolão do setor', jaParticipava: false);
  }

  @override
  Future<Rodada> rodada(String poolId, int matchday) async =>
      Rodada(serverTime: DateTime.utc(2026, 8, 6, 12), jogos: const []);
}

class _RegrasFalso extends ParticipantesRepository {
  _RegrasFalso() : super(ApiClient(SessaoStore()));

  @override
  Future<Regras> regras(String poolId) async => Regras(
    pontosExato: 10,
    pontosVencedorSaldo: 5,
    pontosVencedor: 3,
    ordemDesempate: const ['EXACT_SCORES', 'WINNERS'],
    travadas: false,
    proximoPrazo: DateTime.now().add(const Duration(hours: 5)),
  );
}

class _StoreFalso extends RascunhoStore {
  @override
  Future<List<PalpiteRascunho>> ler(String poolId, int matchday) async =>
      const [];

  @override
  Future<void> salvar(
    String poolId,
    int matchday,
    List<PalpiteRascunho> palpites,
  ) async {}
}

class _SessaoFalsa extends SessaoNotifier {
  @override
  EstadoSessao build() => const Autenticado(
    Conta(id: 'ana', email: 'ana@teste.com', displayName: 'Ana'),
  );
}

ProviderContainer? _container;

Widget _app(Widget tela, _BoloesFalso boloes, {String? convitePendente}) {
  final container = ProviderContainer(
    overrides: [
      boloesRepositoryProvider.overrideWithValue(boloes),
      participantesRepositoryProvider.overrideWithValue(_RegrasFalso()),
      rascunhoStoreProvider.overrideWithValue(_StoreFalso()),
      sessaoProvider.overrideWith(_SessaoFalsa.new),
    ],
  );
  if (convitePendente != null) {
    container.read(convitePendenteProvider.notifier).guardar(convitePendente);
  }
  _container = container;
  addTearDown(container.dispose);
  return UncontrolledProviderScope(
    container: container,
    child: MaterialApp(home: tela),
  );
}

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

  group('Funil de entrada por convite', () {
    testWidgets('CC-5-02 convite pendente é usado sozinho ao chegar na home', (
      t,
    ) async {
      final boloes = _BoloesFalso();
      await t.pumpWidget(
        _app(const HomePage(), boloes, convitePendente: 'token-do-deep-link'),
      );
      await t.pumpAndSettle();

      // Sem isto o token do deep link ficaria guardado sem nunca ser aplicado, e
      // o convidado voltaria ao WhatsApp para copiar o código de novo.
      expect(boloes.tokensUsados, ['token-do-deep-link']);
      // Uso único: não tenta de novo a cada volta à tela inicial.
      expect(_container!.read(convitePendenteProvider), isNull);
    });

    testWidgets('CC-5-02 entra direto na tela do bolão, sem passo extra', (
      t,
    ) async {
      final boloes = _BoloesFalso();
      await t.pumpWidget(
        _app(const HomePage(), boloes, convitePendente: 'tok'),
      );
      await t.pumpAndSettle();

      // Quem acabou de aceitar um convite quer palpitar, não navegar.
      expect(find.text('Bolão do setor'), findsWidgets);
      expect(find.text('Palpites'), findsOneWidget);
    });

    testWidgets('CC-5-12 mostra regras e próximo prazo logo depois de entrar', (
      t,
    ) async {
      final boloes = _BoloesFalso();
      await t.pumpWidget(
        _app(const HomePage(), boloes, convitePendente: 'tok'),
      );
      await t.pumpAndSettle();

      expect(find.text('Você está no bolão'), findsOneWidget);
      expect(find.text('10 pontos — placar exato'), findsOneWidget);
      // Prazo no fuso local: é o que decide se ele palpita agora ou depois.
      expect(find.textContaining('O próximo jogo fecha em'), findsOneWidget);
      expect(find.textContaining('UTC'), findsOneWidget);
    });

    testWidgets('sem convite pendente, a home não tenta entrar em nada', (
      t,
    ) async {
      final boloes = _BoloesFalso();
      await t.pumpWidget(_app(const HomePage(), boloes));
      await t.pumpAndSettle();

      expect(boloes.tokensUsados, isEmpty);
      expect(find.text('Você ainda não está em nenhum bolão'), findsOneWidget);
    });

    testWidgets('AC-1 FE-4 o cadastro avisa que o convite continua valendo', (
      t,
    ) async {
      await t.pumpWidget(
        _app(const CadastroPage(), _BoloesFalso(), convitePendente: 'tok'),
      );
      await t.pumpAndSettle();

      // Sem o aviso, o convidado desiste no meio do cadastro achando que
      // perdeu o convite.
      expect(
        find.textContaining('Você foi convidado para um bolão'),
        findsOneWidget,
      );
    });

    testWidgets('o login também avisa, para quem já tem conta', (t) async {
      await t.pumpWidget(
        _app(const LoginPage(), _BoloesFalso(), convitePendente: 'tok'),
      );
      await t.pumpAndSettle();

      expect(
        find.textContaining('Você foi convidado para um bolão'),
        findsOneWidget,
      );
    });

    testWidgets('a folha de boas-vindas rola em tela baixa e fonte grande', (
      t,
    ) async {
      // Fonte a 2× numa viewport baixa: é onde a folha estourava (06/08) e
      // engolia o botão de fechar atrás da listra de overflow.
      t.platformDispatcher.textScaleFactorTestValue = 2.0;
      addTearDown(t.platformDispatcher.clearTextScaleFactorTestValue);

      await t.pumpWidget(
        _app(const HomePage(), _BoloesFalso(), convitePendente: 'tok'),
      );
      await t.pumpAndSettle();

      expect(t.takeException(), isNull);
      expect(find.byType(SingleChildScrollView), findsWidgets);
    });

    testWidgets('sem convite pendente o cadastro não inventa aviso', (t) async {
      await t.pumpWidget(_app(const CadastroPage(), _BoloesFalso()));
      await t.pumpAndSettle();

      expect(find.textContaining('Você foi convidado'), findsNothing);
    });
  });

  group('ConvitePendente', () {
    test('consumir devolve e limpa; token vazio é ignorado', () {
      final container = ProviderContainer();
      addTearDown(container.dispose);
      final notifier = container.read(convitePendenteProvider.notifier);

      notifier.guardar('   ');
      expect(container.read(convitePendenteProvider), isNull);

      notifier.guardar('  abc  ');
      expect(container.read(convitePendenteProvider), 'abc');
      expect(notifier.consumir(), 'abc');
      expect(notifier.consumir(), isNull);
    });
  });
}
