import 'package:{{ name }}/bloc/Authentication/AuthenticationBloc.dart';
import 'package:{{ name }}/bloc/SnackBar/snackbar_bloc.dart';
import 'package:{{ name }}/models/User/User.dart';
import 'package:{{ name }}/repository/AuthenticationRepository.dart';
import 'package:{{ name }}/repository/TokenRepository.dart';
import 'package:{{ name }}/repository/UserProfileRepository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

class MockAuthenticationRepository extends Mock
    implements AuthenticationRepository {}

class MockTokenRepository extends Mock implements TokenRepository {}

class MockUserProfileRepository extends Mock implements UserProfileRepository {}

class MockSnackbarBloc extends Mock implements SnackbarBloc {}

void main() {
  User user = User('example', 'user@example.com', null, 1, 1, '', null, null);
  AuthenticationRepository authenticationRepository;
  TokenRepository tokenRepository;
  UserProfileRepository userProfileRepository;
  // ignore: close_sinks
  SnackbarBloc snackbarBloc;

  setUp(() {
    authenticationRepository = MockAuthenticationRepository();
    tokenRepository = MockTokenRepository();
    userProfileRepository = MockUserProfileRepository();
    snackbarBloc = MockSnackbarBloc();
  });

  group('AuthenticationBloc', () {
    test('throws when authenticationRepository is null', () {
      expect(
        () => AuthenticationBloc(
          authenticationRepository: null,
          snackbarBloc: snackbarBloc,
          tokenRepository: tokenRepository,
          userProfileRepository: userProfileRepository
        ),
        throwsAssertionError,
      );
    });

    test('throws when tokenRepository is null', () {
      expect(
        () => AuthenticationBloc(
          authenticationRepository: authenticationRepository,
          snackbarBloc: snackbarBloc,
          tokenRepository: null,
          userProfileRepository: userProfileRepository
        ),
        throwsAssertionError,
      );
    });

    test('throws when snackbarBloc is null', () {
      expect(
        () => AuthenticationBloc(
          authenticationRepository: authenticationRepository,
          snackbarBloc: null,
          tokenRepository: tokenRepository,
          userProfileRepository: userProfileRepository
        ),
        throwsAssertionError,
      );
    });

    test('throws when userProfileRepository is null', () {
      expect(
        () => AuthenticationBloc(
          authenticationRepository: authenticationRepository,
          snackbarBloc: snackbarBloc,
          tokenRepository: tokenRepository,
          userProfileRepository: null
        ),
        throwsAssertionError,
      );
    });

    test('initial state is AuthenticationUninitialized', () {
      final authenticationBloc = AuthenticationBloc(
        authenticationRepository: authenticationRepository,
        snackbarBloc: snackbarBloc,
        tokenRepository: tokenRepository,
        userProfileRepository: userProfileRepository
      );
      expect(authenticationBloc.initialState, AuthenticationUninitialized());
      authenticationBloc.close();
    });

  });

  group('AppStarted', () {
    test('emits [uninitialized, unauthenticated] for invalid token', () {

      final authenticationBloc = AuthenticationBloc(
        authenticationRepository: authenticationRepository,
        snackbarBloc: snackbarBloc,
        tokenRepository: tokenRepository,
        userProfileRepository: userProfileRepository
      );

      final expectedResponse = [
        AuthenticationUnauthenticated()
      ];

      when(tokenRepository.hasToken()).thenAnswer((_) => Future.value(false));

      expectLater(
        authenticationBloc,
        emitsInOrder(expectedResponse),
      );

      authenticationBloc.add(AppLoaded());
    });
  });

  group('LoggedIn', () {
    test(
        'emits [uninitialized, loading, authenticated] when token is persisted',
        () {
      final authenticationBloc = AuthenticationBloc(
        authenticationRepository: authenticationRepository,
        snackbarBloc: snackbarBloc,
        tokenRepository: tokenRepository,
        userProfileRepository: userProfileRepository
      );

      when(authenticationRepository.getUser()).thenAnswer((_) async => user);

      final expectedResponse = [
        AuthenticationLoading(),
        AuthenticationAuthenticated(user: user),
      ];

      expectLater(
        authenticationBloc,
        emitsInOrder(expectedResponse),
      );

      authenticationBloc.add(UserLoggedIn(
        token: 'instance.token',
      ));
    });
  });

  group('LoggedOut', () {
    test(
        'emits [uninitialized, loading, unauthenticated] when token is deleted',
        () {
      final authenticationBloc = AuthenticationBloc(
        authenticationRepository: authenticationRepository,
        snackbarBloc: snackbarBloc,
        tokenRepository: tokenRepository,
        userProfileRepository: userProfileRepository
      );

      final expectedResponse = [
        AuthenticationLoading(),
        AuthenticationUnauthenticated(),
      ];

      when(authenticationRepository.logout()).thenAnswer((_) async => '');

      expectLater(
        authenticationBloc,
        emitsInOrder(expectedResponse),
      );

      authenticationBloc.add(UserLoggedOut());
    });
  });

}