// test/unit/login_use_case_test.dart
//
// Unit test untuk LoginUseCase / AuthRepositoryImpl.
// Jalankan: flutter test test/unit/login_use_case_test.dart
//
// Dependencies (pubspec.yaml dev_dependencies):
//   flutter_test: sdk: flutter
//   mockito: ^5.4.4
//   build_runner: ^2.4.6

import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

// ---------- Minimal stubs (copy dari project asli) ----------

abstract class Failure {
  final String message;
  const Failure(this.message);
}

class ServerFailure extends Failure {
  const ServerFailure(super.message);
}

class NetworkFailure extends Failure {
  const NetworkFailure(super.message);
}

class AuthFailure extends Failure {
  const AuthFailure(super.message);
}

class UserEntity {
  final String token;
  final String role;
  UserEntity({required this.token, required this.role});
}

abstract class AuthRepository {
  Future<Either<Failure, UserEntity>> login(String email, String password);
}

// ---------- Mock abstract classes ----------
@GenerateMocks([AuthRepository])
// File mock di-generate via: flutter pub run build_runner build
// Untuk demo tanpa build_runner, kita buat manual mock di bawah

class MockAuthRepository extends Mock implements AuthRepository {}

// ---------- Use case ----------

class LoginUseCase {
  final AuthRepository repository;
  LoginUseCase(this.repository);

  Future<Either<Failure, UserEntity>> call(String email, String password) {
    if (email.trim().isEmpty) {
      return Future.value(const Left(AuthFailure('Email tidak boleh kosong')));
    }
    if (password.length < 6) {
      return Future.value(
          const Left(AuthFailure('Password minimal 6 karakter')));
    }
    return repository.login(email, password);
  }
}

// ---------- Tests ----------

void main() {
  late LoginUseCase loginUseCase;
  late MockAuthRepository mockAuthRepository;

  setUp(() {
    mockAuthRepository = MockAuthRepository();
    loginUseCase = LoginUseCase(mockAuthRepository);
  });

  group('LoginUseCase — validasi input', () {
    test('harus return AuthFailure jika email kosong', () async {
      final result = await loginUseCase.call('', 'password123');
      expect(result.isLeft(), true);
      result.fold(
        (failure) {
          expect(failure, isA<AuthFailure>());
          expect(failure.message, contains('kosong'));
        },
        (_) => fail('Seharusnya return Left'),
      );
    });

    test('harus return AuthFailure jika email hanya spasi', () async {
      final result = await loginUseCase.call('   ', 'password123');
      expect(result.isLeft(), true);
      result.fold(
        (failure) => expect(failure, isA<AuthFailure>()),
        (_) => fail('Seharusnya return Left'),
      );
    });

    test('harus return AuthFailure jika password kurang dari 6 karakter',
        () async {
      final result = await loginUseCase.call('user@test.com', '123');
      expect(result.isLeft(), true);
      result.fold(
        (failure) {
          expect(failure, isA<AuthFailure>());
          expect(failure.message, contains('6'));
        },
        (_) => fail('Seharusnya return Left'),
      );
    });

    test('harus return AuthFailure jika password tepat 5 karakter', () async {
      final result = await loginUseCase.call('user@test.com', 'abc12');
      expect(result.isLeft(), true);
      result.fold(
        (failure) => expect(failure, isA<AuthFailure>()),
        (_) => fail('Seharusnya return Left'),
      );
    });
  });

  group('LoginUseCase — delegasi ke repository', () {
    test('harus call repository.login dengan email dan password yang benar',
        () async {
      const email = 'evan@test.com';
      const password = 'password123';
      final fakeUser = UserEntity(token: 'jwt_token_xyz', role: 'ROLE_USER');

      when(mockAuthRepository.login(email, password))
          .thenAnswer((_) async => Right(fakeUser));

      final result = await loginUseCase.call(email, password);

      verify(mockAuthRepository.login(email, password)).called(1);
      expect(result.isRight(), true);
    });

    test('harus return UserEntity dengan token saat login sukses', () async {
      const email = 'guardian@test.com';
      const password = 'secure123';
      final fakeUser = UserEntity(token: 'guardian_jwt', role: 'ROLE_GUARDIAN');

      when(mockAuthRepository.login(email, password))
          .thenAnswer((_) async => Right(fakeUser));

      final result = await loginUseCase.call(email, password);

      result.fold(
        (_) => fail('Seharusnya sukses'),
        (user) {
          expect(user.token, 'guardian_jwt');
          expect(user.role, 'ROLE_GUARDIAN');
        },
      );
    });

    test('harus return ServerFailure saat API gagal', () async {
      const email = 'user@test.com';
      const password = 'password123';

      when(mockAuthRepository.login(email, password)).thenAnswer(
          (_) async => const Left(ServerFailure('Server tidak merespons')));

      final result = await loginUseCase.call(email, password);

      expect(result.isLeft(), true);
      result.fold(
        (failure) {
          expect(failure, isA<ServerFailure>());
          expect(failure.message, 'Server tidak merespons');
        },
        (_) => fail('Seharusnya return Left'),
      );
    });

    test('harus return NetworkFailure saat tidak ada koneksi', () async {
      const email = 'user@test.com';
      const password = 'password123';

      when(mockAuthRepository.login(email, password)).thenAnswer((_) async =>
          const Left(NetworkFailure('Tidak ada koneksi internet')));

      final result = await loginUseCase.call(email, password);

      result.fold(
        (failure) => expect(failure, isA<NetworkFailure>()),
        (_) => fail('Seharusnya return Left'),
      );
    });

    test('tidak boleh call repository jika validasi gagal', () async {
      await loginUseCase.call('', 'password123');
      verifyNever(mockAuthRepository.login('ignored', 'ignored'));
    });

    test('harus return Right dengan role ROLE_USER untuk user biasa', () async {
      final fakeUser = UserEntity(token: 'user_token', role: 'ROLE_USER');

      when(mockAuthRepository.login('user@test.com', 'user1234'))
          .thenAnswer((_) async => Right(fakeUser));

      final result = await loginUseCase.call('user@test.com', 'user1234');

      result.fold(
        (_) => fail('Seharusnya sukses'),
        (user) => expect(user.role, 'ROLE_USER'),
      );
    });

    test('password tepat 6 karakter harus lolos validasi', () async {
      final fakeUser = UserEntity(token: 'tok', role: 'ROLE_USER');

      when(mockAuthRepository.login('a@b.com', 'abc123'))
          .thenAnswer((_) async => Right(fakeUser));

      final result = await loginUseCase.call('a@b.com', 'abc123');
      expect(result.isRight(), true);
    });
  });

  group('LoginUseCase — edge cases', () {
    test('harus handle karakter spesial di email', () async {
      final fakeUser = UserEntity(token: 'tok', role: 'ROLE_USER');
      const email = 'user+tag@example.co.id';

      when(mockAuthRepository.login(email, 'password123'))
          .thenAnswer((_) async => Right(fakeUser));

      final result = await loginUseCase.call(email, 'password123');
      expect(result.isRight(), true);
    });

    test('harus handle exception dari repository', () async {
      when(mockAuthRepository.login('user@test.com', 'password123'))
          .thenThrow(Exception('Unexpected error'));

      // LoginUseCase tidak wrap exception dari repo sendiri — test ini
      // memverifikasi bahwa exception propagate (akan di-handle di BLoC)
      expect(
        () => loginUseCase.call('user@test.com', 'password123'),
        throwsException,
      );
    });
  });
}
