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

import 'package:unionflow_mobile_apps/features/admin/bloc/admin_users_bloc.dart';
import 'package:unionflow_mobile_apps/features/admin/data/repositories/admin_user_repository.dart';
import 'package:unionflow_mobile_apps/features/admin/data/models/admin_user_model.dart';

@GenerateMocks([AdminUserRepository])
import 'admin_users_bloc_test.mocks.dart';

void main() {
  late AdminUsersBloc bloc;
  late MockAdminUserRepository mockRepository;

  // ── Fixtures ──────────────────────────────────────────────────────────────

  AdminUserModel fakeUser({String id = 'usr-1'}) => AdminUserModel(
        id: id,
        username: 'jdupont',
        email: 'j.dupont@test.com',
        prenom: 'Jean',
        nom: 'Dupont',
        enabled: true,
      );

  AdminRoleModel fakeRole({String id = 'role-1', String name = 'MEMBRE'}) =>
      AdminRoleModel(id: id, name: name, description: 'Role $name');

  AdminUserSearchResult fakeSearchResult({List<AdminUserModel>? users}) =>
      AdminUserSearchResult(
        users: users ?? [fakeUser()],
        totalCount: users?.length ?? 1,
        currentPage: 0,
        pageSize: 20,
        totalPages: 1,
      );

  setUp(() {
    mockRepository = MockAdminUserRepository();
    bloc = AdminUsersBloc(mockRepository);
  });

  tearDown(() => bloc.close());

  // ── Initial state ─────────────────────────────────────────────────────────

  test('initial state is AdminUsersInitial', () {
    expect(bloc.state, isA<AdminUsersInitial>());
  });

  // ── AdminUsersLoadRequested ───────────────────────────────────────────────

  group('AdminUsersLoadRequested', () {
    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersLoading, AdminUsersLoaded] on success',
      build: () {
        when(mockRepository.search(
          page: anyNamed('page'),
          size: anyNamed('size'),
          search: anyNamed('search'),
        )).thenAnswer((_) async => fakeSearchResult());
        return bloc;
      },
      act: (b) => b.add(AdminUsersLoadRequested()),
      expect: () => [
        isA<AdminUsersLoading>(),
        isA<AdminUsersLoaded>()
            .having((s) => s.users.length, 'users.length', 1)
            .having((s) => s.totalCount, 'totalCount', 1)
            .having((s) => s.currentPage, 'currentPage', 0),
      ],
      verify: (_) {
        verify(mockRepository.search(page: 0, size: 20, search: null)).called(1);
      },
    );

    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersLoading, AdminUsersLoaded] with search term',
      build: () {
        when(mockRepository.search(
          page: anyNamed('page'),
          size: anyNamed('size'),
          search: 'Jean',
        )).thenAnswer((_) async => fakeSearchResult());
        return bloc;
      },
      act: (b) => b.add(AdminUsersLoadRequested(search: 'Jean')),
      expect: () => [
        isA<AdminUsersLoading>(),
        isA<AdminUsersLoaded>(),
      ],
      verify: (_) {
        verify(mockRepository.search(page: 0, size: 20, search: 'Jean')).called(1);
      },
    );

    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersLoading, AdminUsersLoaded] with pagination',
      build: () {
        when(mockRepository.search(
          page: 2,
          size: 10,
          search: anyNamed('search'),
        )).thenAnswer((_) async => fakeSearchResult());
        return bloc;
      },
      act: (b) => b.add(AdminUsersLoadRequested(page: 2, size: 10)),
      expect: () => [isA<AdminUsersLoading>(), isA<AdminUsersLoaded>()],
      verify: (_) {
        verify(mockRepository.search(page: 2, size: 10, search: null)).called(1);
      },
    );

    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersLoading, AdminUsersError] on failure',
      build: () {
        when(mockRepository.search(
          page: anyNamed('page'),
          size: anyNamed('size'),
          search: anyNamed('search'),
        )).thenThrow(Exception('network error'));
        return bloc;
      },
      act: (b) => b.add(AdminUsersLoadRequested()),
      expect: () => [
        isA<AdminUsersLoading>(),
        isA<AdminUsersError>().having(
          (s) => s.message,
          'message',
          contains('Exception'),
        ),
      ],
    );
  });

  // ── AdminUserDetailRequested ──────────────────────────────────────────────

  group('AdminUserDetailRequested', () {
    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersLoading, AdminUserDetailLoaded] on success',
      build: () {
        when(mockRepository.getById('usr-1')).thenAnswer((_) async => fakeUser());
        when(mockRepository.getUserRoles('usr-1'))
            .thenAnswer((_) async => [fakeRole()]);
        return bloc;
      },
      act: (b) => b.add(AdminUserDetailRequested('usr-1')),
      expect: () => [
        isA<AdminUsersLoading>(),
        isA<AdminUserDetailLoaded>()
            .having((s) => s.user.id, 'user.id', 'usr-1')
            .having((s) => s.userRoles.length, 'userRoles.length', 1),
      ],
      verify: (_) {
        verify(mockRepository.getById('usr-1')).called(1);
        verify(mockRepository.getUserRoles('usr-1')).called(1);
      },
    );

    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersLoading, AdminUsersError] when user not found (null)',
      build: () {
        when(mockRepository.getById('usr-x')).thenAnswer((_) async => null);
        return bloc;
      },
      act: (b) => b.add(AdminUserDetailRequested('usr-x')),
      expect: () => [
        isA<AdminUsersLoading>(),
        isA<AdminUsersError>().having(
          (s) => s.message,
          'message',
          'Utilisateur non trouvé',
        ),
      ],
    );

    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersLoading, AdminUsersError] on repository failure',
      build: () {
        when(mockRepository.getById(any)).thenThrow(Exception('server error'));
        return bloc;
      },
      act: (b) => b.add(AdminUserDetailRequested('usr-1')),
      expect: () => [isA<AdminUsersLoading>(), isA<AdminUsersError>()],
    );
  });

  // ── AdminUserDetailWithRolesRequested ─────────────────────────────────────

  group('AdminUserDetailWithRolesRequested', () {
    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersLoading, AdminUserDetailLoaded] with allRoles on success',
      build: () {
        when(mockRepository.getById('usr-1')).thenAnswer((_) async => fakeUser());
        when(mockRepository.getUserRoles('usr-1'))
            .thenAnswer((_) async => [fakeRole(name: 'MEMBRE')]);
        when(mockRepository.getRealmRoles()).thenAnswer(
          (_) async => [fakeRole(name: 'MEMBRE'), fakeRole(id: 'role-2', name: 'ADMIN')],
        );
        return bloc;
      },
      act: (b) => b.add(AdminUserDetailWithRolesRequested('usr-1')),
      expect: () => [
        isA<AdminUsersLoading>(),
        isA<AdminUserDetailLoaded>()
            .having((s) => s.allRoles.length, 'allRoles.length', 2)
            .having((s) => s.userRoles.length, 'userRoles.length', 1),
      ],
      verify: (_) {
        verify(mockRepository.getRealmRoles()).called(1);
      },
    );

    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersLoading, AdminUsersError] when user not found',
      build: () {
        when(mockRepository.getById('unknown')).thenAnswer((_) async => null);
        return bloc;
      },
      act: (b) => b.add(AdminUserDetailWithRolesRequested('unknown')),
      expect: () => [
        isA<AdminUsersLoading>(),
        isA<AdminUsersError>().having(
          (s) => s.message,
          'message',
          'Utilisateur non trouvé',
        ),
      ],
    );

    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersLoading, AdminUsersError] on getRealmRoles failure',
      build: () {
        when(mockRepository.getById('usr-1')).thenAnswer((_) async => fakeUser());
        when(mockRepository.getUserRoles(any)).thenAnswer((_) async => []);
        when(mockRepository.getRealmRoles()).thenThrow(Exception('roles error'));
        return bloc;
      },
      act: (b) => b.add(AdminUserDetailWithRolesRequested('usr-1')),
      expect: () => [isA<AdminUsersLoading>(), isA<AdminUsersError>()],
    );
  });

  // ── AdminUserRolesUpdateRequested ─────────────────────────────────────────

  group('AdminUserRolesUpdateRequested', () {
    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUserRolesUpdated] on update success then reloads detail',
      build: () {
        when(mockRepository.setUserRoles('usr-1', ['ADMIN', 'MEMBRE']))
            .thenAnswer((_) async {});
        // For the auto-dispatched AdminUserDetailWithRolesRequested
        when(mockRepository.getById('usr-1')).thenAnswer((_) async => fakeUser());
        when(mockRepository.getUserRoles('usr-1'))
            .thenAnswer((_) async => [fakeRole(name: 'ADMIN')]);
        when(mockRepository.getRealmRoles()).thenAnswer(
          (_) async => [fakeRole(name: 'ADMIN'), fakeRole(id: 'role-2', name: 'MEMBRE')],
        );
        return bloc;
      },
      act: (b) => b.add(AdminUserRolesUpdateRequested('usr-1', ['ADMIN', 'MEMBRE'])),
      expect: () => [
        isA<AdminUserRolesUpdated>(),
        isA<AdminUsersLoading>(),
        isA<AdminUserDetailLoaded>(),
      ],
      verify: (_) {
        verify(mockRepository.setUserRoles('usr-1', ['ADMIN', 'MEMBRE'])).called(1);
      },
    );

    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersError] on update failure',
      build: () {
        when(mockRepository.setUserRoles(any, any)).thenThrow(Exception('roles update error'));
        return bloc;
      },
      act: (b) => b.add(AdminUserRolesUpdateRequested('usr-1', ['ADMIN'])),
      expect: () => [isA<AdminUsersError>()],
    );
  });

  // ── AdminRolesLoadRequested ───────────────────────────────────────────────

  group('AdminRolesLoadRequested', () {
    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminRolesLoaded] on success',
      build: () {
        when(mockRepository.getRealmRoles()).thenAnswer(
          (_) async => [
            fakeRole(name: 'MEMBRE'),
            fakeRole(id: 'role-2', name: 'ADMIN'),
            fakeRole(id: 'role-3', name: 'SUPER_ADMIN'),
          ],
        );
        return bloc;
      },
      act: (b) => b.add(AdminRolesLoadRequested()),
      expect: () => [
        isA<AdminRolesLoaded>().having(
          (s) => s.roles.length,
          'roles.length',
          3,
        ),
      ],
      verify: (_) => verify(mockRepository.getRealmRoles()).called(1),
    );

    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminRolesLoaded] with empty list',
      build: () {
        when(mockRepository.getRealmRoles()).thenAnswer((_) async => []);
        return bloc;
      },
      act: (b) => b.add(AdminRolesLoadRequested()),
      expect: () => [
        isA<AdminRolesLoaded>().having((s) => s.roles, 'roles', isEmpty),
      ],
    );

    blocTest<AdminUsersBloc, AdminUsersState>(
      'emits [AdminUsersError] on roles load failure',
      build: () {
        when(mockRepository.getRealmRoles()).thenThrow(Exception('keycloak error'));
        return bloc;
      },
      act: (b) => b.add(AdminRolesLoadRequested()),
      expect: () => [isA<AdminUsersError>()],
    );
  });
}
