import 'dart:async';
import 'dart:io';

import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'package:todos_repository_core/todos_repository_core.dart';
import 'package:todos_repository_local_storage/todos_repository_local_storage.dart';

import 'repository_test.mocks.dart';

@GenerateNiceMocks([
  MockSpec<FileStorage>(),
  MockSpec<WebClient>(),
])
void main() {
  group('TodosRepository', () {
    List<TodoEntity> createTodos() {
      return [TodoEntity('Task', '1', 'Hallo', false)];
    }

    test(
        'should load todos from File Storage if they exist without calling the web client',
        () {
      final fileStorage = MockFileStorage();
      final webClient = MockWebClient();
      final repository = LocalStorageRepository(
        localStorage: fileStorage,
        webClient: webClient,
      );
      final todos = createTodos();

      // We'll use our mock throughout the tests to set certain conditions. In
      // this first test, we want to mock out our file storage to return a
      // list of Todos that we define here in our test!
      when(fileStorage.loadTodos()).thenAnswer((_) => Future.value(todos));

      expect(repository.loadTodos(), completion(todos));
      verifyNever(webClient.loadTodos());
    });

    test(
        'should fetch todos from the Web Client if the file storage throws a synchronous error',
        () async {
      final fileStorage = MockFileStorage();
      final webClient = MockWebClient();
      final repository = LocalStorageRepository(
        localStorage: fileStorage,
        webClient: webClient,
      );
      final todos = createTodos();

      // In this instance, we'll ask our Mock to throw an error. When it does,
      // we expect the web client to be called instead.
      when(fileStorage.loadTodos()).thenThrow('Uh ohhhh');
      when(webClient.loadTodos()).thenAnswer((_) => Future.value(todos));

      // We check that the correct todos were returned, and that the
      // webClient.loadTodos method was in fact called!
      expect(await repository.loadTodos(), todos);
      verify(webClient.loadTodos());
    });

    test(
        'should fetch todos from the Web Client if the File storage returns an async error',
        () async {
      final fileStorage = MockFileStorage();
      final webClient = MockWebClient();
      final repository = LocalStorageRepository(
        localStorage: fileStorage,
        webClient: webClient,
      );
      final todos = createTodos();

      when(fileStorage.loadTodos()).thenThrow(Exception('Oh no.'));
      when(webClient.loadTodos()).thenAnswer((_) => Future.value(todos));

      expect(await repository.loadTodos(), todos);
      verify(webClient.loadTodos());
    });

    test('should persist the todos to local disk and the web client', () {
      final fileStorage = MockFileStorage();
      final webClient = MockWebClient();
      final repository = LocalStorageRepository(
        localStorage: fileStorage,
        webClient: webClient,
      );
      final todos = createTodos();

      when(fileStorage.saveTodos(todos))
          .thenAnswer((_) => Future.value(File('falsch')));
      when(webClient.saveTodos(todos)).thenAnswer((_) => Future.value(true));

      // In this case, we just want to verify we're correctly persisting to all
      // the storage mechanisms we care about.
      expect(repository.saveTodos(todos), completes);
      verify(fileStorage.saveTodos(todos));
      verify(webClient.saveTodos(todos));
    });
  });
}
