import 'package:bolao_mobile/core/api/api_client.dart';
import 'package:bolao_mobile/core/cache/cache_local.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/sessao_controller.dart';
import 'package:bolao_mobile/features/boloes/data/boloes_repository.dart';
import 'package:bolao_mobile/features/boloes/data/boloes_repository_http.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';

class _RepoFalso extends BoloesRepositoryHttp {
  _RepoFalso({this.resposta = const [], this.erro})
    : super(ApiClient(SessaoStore()));

  final List<BolaoResumo> resposta;
  final ApiError? erro;

  @override
  Future<List<BolaoResumo>> meus() async {
    if (erro != null) throw erro!;
    return resposta;
  }
}

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

BolaoResumo _bolao({
  String id = 'p1',
  String name = 'Bolão do setor',
  bool arquivado = false,
  bool encerrado = false,
}) => BolaoResumo(
  id: id,
  name: name,
  matchdayFrom: 20,
  matchdayTo: 22,
  totalMembers: 4,
  isAdmin: false,
  encerrado: encerrado,
  arquivado: arquivado,
);

Widget _app(BoloesRepository repo) => ProviderScope(
  overrides: [
    boloesRepositoryProvider.overrideWithValue(repo),
    sessaoProvider.overrideWith(_SessaoFalsa.new),
  ],
  child: const MaterialApp(home: HomePage()),
);

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

  group('CacheLocal', () {
    test('grava, lê e devolve a idade', () async {
      final prefs = await SharedPreferences.getInstance();
      final cache = CacheLocal(prefs);

      await cache.gravar('x', {'a': 1});
      final lido = await cache.ler('x');

      expect((lido!.valor as Map)['a'], 1);
      // Recém-gravado: idade praticamente zero.
      expect(lido.idade.inSeconds, lessThan(5));
    });

    test('chave inexistente devolve nulo, sem lançar', () async {
      final cache = CacheLocal(await SharedPreferences.getInstance());
      expect(await cache.ler('nao-existe'), isNull);
    });

    test(
      'conteúdo corrompido é descartado em vez de derrubar a tela',
      () async {
        SharedPreferences.setMockInitialValues({
          'bolao.cache.x': 'isto não é json',
        });
        final cache = CacheLocal(await SharedPreferences.getInstance());

        expect(await cache.ler('x'), isNull);
        // E a chave suja sai, para não tentar decodificar a cada abertura.
        await cache.gravar('x', [1, 2]);
        expect((await cache.ler('x'))!.valor, [1, 2]);
      },
    );

    test(
      'nada expira sozinho — quem lê decide o que fazer com a idade',
      () async {
        final prefs = await SharedPreferences.getInstance();
        final cache = CacheLocal(prefs);
        await cache.gravar('x', 'valor');

        // Apagar o único dado disponível offline porque ele é velho deixaria o
        // participante sem nada — pior que mostrar dado antigo **rotulado**.
        expect(await cache.ler('x'), isNotNull);
      },
    );
  });

  group('descreverIdade', () {
    test('fala em minutos, horas e dias', () {
      expect(descreverIdade(const Duration(seconds: 30)), 'agora mesmo');
      expect(descreverIdade(const Duration(minutes: 20)), 'de 20 min atrás');
      expect(descreverIdade(const Duration(hours: 3)), 'de 3 h atrás');
      expect(descreverIdade(const Duration(days: 2)), 'de 2 dia(s) atrás');
    });
  });

  group('Home sem conexão', () {
    testWidgets('mostra a lista guardada, dizendo que é antiga', (t) async {
      // Primeira abertura com rede: grava o cache.
      await t.pumpWidget(_app(_RepoFalso(resposta: [_bolao()])));
      await t.pumpAndSettle();
      expect(find.text('Bolão do setor'), findsOneWidget);

      // Segunda abertura sem rede: o cache é a diferença entre um app inútil e
      // um app que ainda deixa abrir o bolão.
      await t.pumpWidget(const MaterialApp(home: SizedBox.shrink()));
      await t.pumpAndSettle();
      await t.pumpWidget(
        _app(_RepoFalso(erro: ApiError('sem conexão', semConexao: true))),
      );
      await t.pumpAndSettle();

      expect(find.text('Bolão do setor'), findsOneWidget);
      // Rotulado como antigo: sem isso o participante lê dado velho como atual.
      expect(
        find.textContaining('mostrando a lista guardada no aparelho'),
        findsOneWidget,
      );
    });

    testWidgets('sem cache e sem rede, mostra o motivo com retry', (t) async {
      await t.pumpWidget(
        _app(
          _RepoFalso(
            erro: ApiError('sem conexão com o servidor', semConexao: true),
          ),
        ),
      );
      await t.pumpAndSettle();

      expect(find.text('sem conexão com o servidor'), findsOneWidget);
      expect(find.text('Tentar de novo'), findsOneWidget);
    });
  });

  group('Arquivados na lista', () {
    testWidgets('CV-6 FE-1 ficam recolhidos, com contagem', (t) async {
      await t.pumpWidget(
        _app(
          _RepoFalso(
            resposta: [
              _bolao(),
              _bolao(
                id: 'p2',
                name: 'Bolão velho',
                arquivado: true,
                encerrado: true,
              ),
            ],
          ),
        ),
      );
      await t.pumpAndSettle();

      // Arquivado é histórico: não some, mas também não disputa espaço com o
      // bolão que a pessoa abre todo dia.
      expect(find.text('Bolão do setor'), findsOneWidget);
      expect(find.text('Bolão velho'), findsNothing);
      expect(find.text('Ver 1 bolão(ões) arquivado(s)'), findsOneWidget);
    });

    testWidgets('abrir a seção revela o arquivado, marcado', (t) async {
      await t.pumpWidget(
        _app(
          _RepoFalso(
            resposta: [
              _bolao(),
              _bolao(
                id: 'p2',
                name: 'Bolão velho',
                arquivado: true,
                encerrado: true,
              ),
            ],
          ),
        ),
      );
      await t.pumpAndSettle();

      await t.tap(find.text('Ver 1 bolão(ões) arquivado(s)'));
      await t.pumpAndSettle();

      expect(find.text('Bolão velho'), findsOneWidget);
      expect(find.textContaining('arquivado'), findsWidgets);
    });

    testWidgets('sem arquivados, nenhuma seção extra aparece', (t) async {
      await t.pumpWidget(_app(_RepoFalso(resposta: [_bolao()])));
      await t.pumpAndSettle();

      expect(find.textContaining('arquivado'), findsNothing);
    });
  });
}
