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/sessao_controller.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';

/// Store em memória, no lugar do armazenamento seguro do device.
class _StoreFalso extends SessaoStore {
  _StoreFalso({this.token, Map<String, dynamic>? contaSalva})
    : _conta = contaSalva;

  final String? token;
  Map<String, dynamic>? _conta;
  bool limpou = false;

  @override
  Future<String?> get accessToken async => limpou ? null : token;

  @override
  Future<Map<String, dynamic>?> get conta async => limpou ? null : _conta;

  @override
  Future<void> salvarConta(Map<String, dynamic> conta) async => _conta = conta;

  @override
  Future<void> limpar() async {
    limpou = true;
    _conta = null;
  }
}

class _RepoFalso extends AuthRepository {
  _RepoFalso(SessaoStore store, {this.falha}) : super(ApiClient(store), store);

  /// `null` = a chamada funciona.
  final ApiError? falha;

  @override
  Future<Conta> minhaConta() async {
    if (falha != null) throw falha!;
    return const Conta(
      id: 'ana',
      email: 'ana@teste.com',
      displayName: 'Ana do servidor',
    );
  }
}

EstadoSessao _estadoCom(_StoreFalso store, {ApiError? falha}) {
  final container = ProviderContainer(
    overrides: [
      sessaoStoreProvider.overrideWithValue(store),
      authRepositoryProvider.overrideWithValue(_RepoFalso(store, falha: falha)),
    ],
  );
  addTearDown(container.dispose);
  return container.read(sessaoProvider);
}

Future<EstadoSessao> _restaurar(_StoreFalso store, {ApiError? falha}) async {
  final container = ProviderContainer(
    overrides: [
      sessaoStoreProvider.overrideWithValue(store),
      authRepositoryProvider.overrideWithValue(_RepoFalso(store, falha: falha)),
    ],
  );
  addTearDown(container.dispose);
  container.read(sessaoProvider);
  await container.read(sessaoProvider.notifier).restaurar();
  return container.read(sessaoProvider);
}

void main() {
  group('Restauração da sessão', () {
    test('começa carregando, para não piscar a tela de login', () {
      final estado = _estadoCom(_StoreFalso(token: 't'));
      expect(estado, isA<SessaoCarregando>());
    });

    test('sem token guardado, vai para o login', () async {
      final estado = await _restaurar(_StoreFalso());
      expect(estado, isA<SemSessao>());
    });

    test('com rede, entra com a conta do servidor e guarda em cache', () async {
      final store = _StoreFalso(token: 't');
      final estado = await _restaurar(store);

      expect(estado, isA<Autenticado>());
      expect((estado as Autenticado).conta.displayName, 'Ana do servidor');
      // O cache é o que salva o cold start offline seguinte.
      expect((await store.conta)?['displayName'], 'Ana do servidor');
    });

    test('sem conexão, mantém a sessão com a conta em cache', () async {
      final store = _StoreFalso(
        token: 't',
        contaSalva: const {
          'id': 'ana',
          'email': 'ana@teste.com',
          'displayName': 'Ana',
        },
      );

      final estado = await _restaurar(
        store,
        falha: ApiError('sem conexão com o servidor', semConexao: true),
      );

      // Abrir o app no ônibus não pode custar a sessão: sem recuperação de
      // senha, deslogar por rede ruim pode custar a conta e o histórico.
      expect(estado, isA<Autenticado>());
      expect((estado as Autenticado).conta.displayName, 'Ana');
      expect(store.limpou, isFalse);
    });

    test('sem conexão e sem cache, não inventa sessão', () async {
      final estado = await _restaurar(
        _StoreFalso(token: 't'),
        falha: ApiError('sem conexão com o servidor', semConexao: true),
      );
      expect(estado, isA<SemSessao>());
    });

    test('token inválido limpa tudo e volta ao login', () async {
      final store = _StoreFalso(
        token: 't',
        contaSalva: const {
          'id': 'ana',
          'email': 'ana@teste.com',
          'displayName': 'Ana',
        },
      );

      final estado = await _restaurar(
        store,
        falha: ApiError('token inválido', status: 401),
      );

      // Sessão morta é diferente de rede ruim: aqui a credencial tem que sair
      // do aparelho, cache incluído.
      expect(estado, isA<SemSessao>());
      expect(store.limpou, isTrue);
      expect(await store.conta, isNull);
    });
  });
}
