// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:local_auth/local_auth.dart';
import 'package:local_auth_android/local_auth_android.dart';
import 'package:local_auth_darwin/local_auth_darwin.dart';
import 'package:local_auth_platform_interface/local_auth_platform_interface.dart';
import 'package:local_auth_windows/local_auth_windows.dart';
import 'package:mockito/mockito.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  late LocalAuthentication localAuthentication;
  late MockLocalAuthPlatform mockLocalAuthPlatform;

  setUp(() {
    localAuthentication = LocalAuthentication();
    mockLocalAuthPlatform = MockLocalAuthPlatform();
    LocalAuthPlatform.instance = mockLocalAuthPlatform;
  });

  test('authenticate calls platform implementation', () {
    when(mockLocalAuthPlatform.authenticate(
      localizedReason: anyNamed('localizedReason'),
      authMessages: anyNamed('authMessages'),
      options: anyNamed('options'),
    )).thenAnswer((_) async => true);
    localAuthentication.authenticate(localizedReason: 'Test Reason');
    verify(mockLocalAuthPlatform.authenticate(
      localizedReason: 'Test Reason',
      authMessages: <AuthMessages>[
        const IOSAuthMessages(),
        const AndroidAuthMessages(),
        const WindowsAuthMessages(),
      ],
    )).called(1);
  });

  test('isDeviceSupported calls platform implementation', () {
    when(mockLocalAuthPlatform.isDeviceSupported())
        .thenAnswer((_) async => true);
    localAuthentication.isDeviceSupported();
    verify(mockLocalAuthPlatform.isDeviceSupported()).called(1);
  });

  test('getEnrolledBiometrics calls platform implementation', () {
    when(mockLocalAuthPlatform.getEnrolledBiometrics())
        .thenAnswer((_) async => <BiometricType>[]);
    localAuthentication.getAvailableBiometrics();
    verify(mockLocalAuthPlatform.getEnrolledBiometrics()).called(1);
  });

  test('stopAuthentication calls platform implementation', () {
    when(mockLocalAuthPlatform.stopAuthentication())
        .thenAnswer((_) async => true);
    localAuthentication.stopAuthentication();
    verify(mockLocalAuthPlatform.stopAuthentication()).called(1);
  });

  test('canCheckBiometrics returns correct result', () async {
    when(mockLocalAuthPlatform.deviceSupportsBiometrics())
        .thenAnswer((_) async => false);
    bool? result;
    result = await localAuthentication.canCheckBiometrics;
    expect(result, false);
    when(mockLocalAuthPlatform.deviceSupportsBiometrics())
        .thenAnswer((_) async => true);
    result = await localAuthentication.canCheckBiometrics;
    expect(result, true);
    verify(mockLocalAuthPlatform.deviceSupportsBiometrics()).called(2);
  });
}

class MockLocalAuthPlatform extends Mock
    with MockPlatformInterfaceMixin
    implements LocalAuthPlatform {
  MockLocalAuthPlatform() {
    throwOnMissingStub(this);
  }

  @override
  Future<bool> authenticate({
    required String? localizedReason,
    required Iterable<AuthMessages>? authMessages,
    AuthenticationOptions? options = const AuthenticationOptions(),
  }) =>
      super.noSuchMethod(
          Invocation.method(#authenticate, <Object>[], <Symbol, Object?>{
            #localizedReason: localizedReason,
            #authMessages: authMessages,
            #options: options,
          }),
          returnValue: Future<bool>.value(false)) as Future<bool>;

  @override
  Future<List<BiometricType>> getEnrolledBiometrics() =>
      super.noSuchMethod(Invocation.method(#getEnrolledBiometrics, <Object>[]),
              returnValue: Future<List<BiometricType>>.value(<BiometricType>[]))
          as Future<List<BiometricType>>;

  @override
  Future<bool> isDeviceSupported() =>
      super.noSuchMethod(Invocation.method(#isDeviceSupported, <Object>[]),
          returnValue: Future<bool>.value(false)) as Future<bool>;

  @override
  Future<bool> stopAuthentication() =>
      super.noSuchMethod(Invocation.method(#stopAuthentication, <Object>[]),
          returnValue: Future<bool>.value(false)) as Future<bool>;

  @override
  Future<bool> deviceSupportsBiometrics() => super.noSuchMethod(
      Invocation.method(#deviceSupportsBiometrics, <Object>[]),
      returnValue: Future<bool>.value(false)) as Future<bool>;
}
