// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/utils/forms.dart';
import 'package:wyatt_authentication_bloc/src/domain/domain.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';

/// {@template authentication_repository_impl}
/// The default implementation of [AuthenticationRepository].
/// {@endtemplate}
class AuthenticationRepositoryImpl<Data extends Object>
    extends AuthenticationRepository<Data> {
  /// {@macro authentication_repository_impl}
  AuthenticationRepositoryImpl({
    required this.authenticationRemoteDataSource,
    required this.authenticationCacheDataSource,
    required this.authenticationSessionDataSource,
    FormRepository? formRepository,
    // ignore: strict_raw_type
    List<FormInput>? extraSignUpInputs,
    // ignore: strict_raw_type
    List<FormInput>? extraEditAccountInputs,
    FormInputValidator<String?, ValidationError>? customEmailValidator,
    FormInputValidator<String?, ValidationError>? customPasswordValidator,
  }) {
    _formRepository = formRepository ?? FormRepositoryImpl();

    if (formRepository != null) {
      return;
    }
    _formRepository
      ..registerForm(
        Forms.buildSignUpForm(
          customEmailValidator,
          customPasswordValidator,
          extraSignUpInputs,
        ),
      )
      ..registerForm(
        Forms.buildSignInForm(
          customEmailValidator,
          customPasswordValidator,
        ),
      )
      ..registerForm(
        Forms.buildPasswordResetForm(customEmailValidator),
      )
      ..registerForm(
        Forms.buildEditAccountForm(
          customEmailValidator,
          customPasswordValidator,
          extraEditAccountInputs,
        ),
      );
  }

  /// The remote data source used to perform the authentication process.
  final AuthenticationRemoteDataSource<Data> authenticationRemoteDataSource;

  /// The cache data source used to cache the current account.
  final AuthenticationCacheDataSource<Data> authenticationCacheDataSource;

  /// The session data source used to manage the current session.
  final AuthenticationSessionDataSource<Data> authenticationSessionDataSource;

  late FormRepository _formRepository;

  /// {@macro form_repo}
  @override
  FormRepository get formRepository => _formRepository;

  // Cache related methods ====================================================

  /// {@macro check_cache_account}
  @override
  Future<void> checkForCachedAccount() async {
    final cachedAccount =
        await authenticationCacheDataSource.getCachedAccount();

    if (cachedAccount == null) {
      addSession(
        const AuthenticationSession(
          latestEvent: UnknownAuthenticationEvent(),
        ),
      );
      return;
    }

    addSession(
      AuthenticationSession.fromEvent(
        SignedInFromCacheEvent(account: cachedAccount),
      ),
    );
    return;
  }

  // Session related methods ===================================================

  /// {@macro add_session}
  @override
  void addSession(AuthenticationSession<Data> session) =>
      authenticationSessionDataSource.addSession(session);

  /// {@macro session_stream}
  @override
  Stream<AuthenticationSession<Data>> sessionStream() =>
      authenticationSessionDataSource.sessionStream();

  /// {@macro current_session}
  @override
  FutureOrResult<AuthenticationSession<Data>> currentSession() =>
      Result.tryCatchAsync<AuthenticationSession<Data>, AppException,
          AppException>(
        () async {
          final session =
              await authenticationSessionDataSource.currentSession();

          return session;
        },
        (error) => error,
      );

  // SignUp/SignIn methods ====================================================

  /// {@macro signup_pwd}
  @override
  FutureOrResult<Account> signUpWithEmailAndPassword({
    required String email,
    required String password,
  }) =>
      Result.tryCatchAsync<Account, AppException, AppException>(
        () async {
          final account =
              await authenticationRemoteDataSource.signUpWithEmailAndPassword(
            email: email,
            password: password,
          );
          // Cache the account
          await authenticationCacheDataSource.cacheAccount(account);
          return account;
        },
        (error) => error,
      );

  /// {@macro signin_pwd}
  @override
  FutureOrResult<Account> signInWithEmailAndPassword({
    required String email,
    required String password,
  }) =>
      Result.tryCatchAsync<Account, AppException, AppException>(
        () async {
          final account =
              await authenticationRemoteDataSource.signInWithEmailAndPassword(
            email: email,
            password: password,
          );
          // Cache the account
          await authenticationCacheDataSource.cacheAccount(account);
          return account;
        },
        (error) => error,
      );

  /// {@macro signin_anom}
  @override
  FutureOrResult<Account> signInAnonymously() =>
      Result.tryCatchAsync<Account, AppException, AppException>(
        () async {
          final account =
              await authenticationRemoteDataSource.signInAnonymously();
          return account;
        },
        (error) => error,
      );

  /// {@macro signin_google}
  @override
  FutureOrResult<Account> signInWithGoogle() =>
      Result.tryCatchAsync<Account, AppException, AppException>(
        () async {
          final account =
              await authenticationRemoteDataSource.signInWithGoogle();
          // Cache the account
          await authenticationCacheDataSource.cacheAccount(account);
          return account;
        },
        (error) => error,
      );

  /// {@macro signout}
  @override
  FutureOrResult<void> signOut() =>
      Result.tryCatchAsync<void, AppException, AppException>(
        () async {
          await authenticationRemoteDataSource.signOut();
          // Remove the cached account
          await authenticationCacheDataSource.removeCachedAccount();
        },
        (error) => error,
      );

  // Account management methods ===============================================

  /// {@macro refresh}
  @override
  FutureOrResult<Account> refresh() =>
      Result.tryCatchAsync<Account, AppException, AppException>(
        () async {
          final account = await authenticationRemoteDataSource.refresh();
          // Cache the account
          await authenticationCacheDataSource.cacheAccount(account);
          return account;
        },
        (error) => error,
      );

  /// {@macro reauthenticate}
  @override
  FutureOrResult<Account> reauthenticate() =>
      Result.tryCatchAsync<Account, AppException, AppException>(
        () async {
          final account = await authenticationRemoteDataSource.reauthenticate();
          // Cache the account
          await authenticationCacheDataSource.cacheAccount(account);
          return account;
        },
        (error) => error,
      );

  /// {@macro update_email}
  @override
  FutureOrResult<Account> updateEmail({required String email}) =>
      Result.tryCatchAsync<Account, AppException, AppException>(
        () async {
          final account =
              await authenticationRemoteDataSource.updateEmail(email: email);
          // Cache the account
          await authenticationCacheDataSource.cacheAccount(account);
          return account;
        },
        (error) => error,
      );

  /// {@macro update_password}
  @override
  FutureOrResult<Account> updatePassword({required String password}) =>
      Result.tryCatchAsync<Account, AppException, AppException>(
        () async {
          final account = await authenticationRemoteDataSource.updatePassword(
            password: password,
          );
          // Cache the account
          await authenticationCacheDataSource.cacheAccount(account);
          return account;
        },
        (error) => error,
      );

  /// {@macro delete}
  @override
  FutureOrResult<void> delete() =>
      Result.tryCatchAsync<void, AppException, AppException>(
        () async {
          await authenticationRemoteDataSource.delete();
          // Remove the cached account
          await authenticationCacheDataSource.removeCachedAccount();
        },
        (error) => error,
      );

  // Email related stuff ======================================================

  /// {@macro send_email_verification}
  @override
  FutureOrResult<void> sendEmailVerification() =>
      Result.tryCatchAsync<void, AppException, AppException>(
        () async {
          await authenticationRemoteDataSource.sendEmailVerification();
        },
        (error) => error,
      );

  /// {@macro send_password_reset_email}
  @override
  FutureOrResult<void> sendPasswordResetEmail({required String email}) =>
      Result.tryCatchAsync<void, AppException, AppException>(
        () async {
          await authenticationRemoteDataSource.sendPasswordResetEmail(
            email: email,
          );
        },
        (error) => error,
      );

  /// {@macro confirm_password_reset}
  @override
  FutureOrResult<void> confirmPasswordReset({
    required String code,
    required String newPassword,
  }) =>
      Result.tryCatchAsync<void, AppException, AppException>(
        () async {
          await authenticationRemoteDataSource.confirmPasswordReset(
            code: code,
            newPassword: newPassword,
          );
        },
        (error) => error,
      );

  /// {@macro verify_password_reset_code}
  @override
  FutureOrResult<bool> verifyPasswordResetCode({required String code}) =>
      Result.tryCatchAsync<bool, AppException, AppException>(
        () async {
          final response = await authenticationRemoteDataSource
              .verifyPasswordResetCode(code: code);
          return response;
        },
        (error) => error,
      );
}
