/// Tests unitaires pour AdhesionsBloc
library adhesions_bloc_test;

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/adhesions/bloc/adhesions_bloc.dart';
import 'package:unionflow_mobile_apps/features/adhesions/data/models/adhesion_model.dart';
import 'package:unionflow_mobile_apps/features/adhesions/data/repositories/adhesion_repository.dart';

@GenerateMocks([AdhesionRepository])
import 'adhesions_bloc_test.mocks.dart';

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

AdhesionModel _makeAdhesion({
  String id = 'adh1',
  String statut = 'EN_ATTENTE',
}) =>
    AdhesionModel(
      id: id,
      membreId: 'membre1',
      nomMembre: 'Dupont Jean',
      organisationId: 'org1',
      nomOrganisation: 'TestOrg',
      statut: statut,
      fraisAdhesion: 10000,
    );

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

void main() {
  late MockAdhesionRepository mockRepository;

  AdhesionsBloc buildBloc() => AdhesionsBloc(mockRepository);

  setUp(() {
    mockRepository = MockAdhesionRepository();
  });

  // ---- initial state -------------------------------------------------------

  test('initial state has status initial and empty adhesions list', () {
    final bloc = buildBloc();
    expect(bloc.state.status, AdhesionsStatus.initial);
    expect(bloc.state.adhesions, isEmpty);
    bloc.close();
  });

  // ---- LoadAdhesions -------------------------------------------------------

  group('LoadAdhesions', () {
    final adhesionList = [_makeAdhesion(), _makeAdhesion(id: 'adh2')];

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits loading then loaded with adhesions list',
      build: () {
        when(mockRepository.getAll(
                page: anyNamed('page'), size: anyNamed('size')))
            .thenAnswer((_) async => adhesionList);
        return buildBloc();
      },
      act: (b) => b.add(const LoadAdhesions()),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded)
            .having((s) => s.adhesions.length, 'count', 2),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits loading then loaded with empty list',
      build: () {
        when(mockRepository.getAll(
                page: anyNamed('page'), size: anyNamed('size')))
            .thenAnswer((_) async => []);
        return buildBloc();
      },
      act: (b) => b.add(const LoadAdhesions()),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded)
            .having((s) => s.adhesions, 'adhesions', isEmpty),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits loading then error on exception',
      build: () {
        when(mockRepository.getAll(
                page: anyNamed('page'), size: anyNamed('size')))
            .thenThrow(Exception('network failure'));
        return buildBloc();
      },
      act: (b) => b.add(const LoadAdhesions()),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.error),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'uses custom page and size',
      build: () {
        when(mockRepository.getAll(page: 1, size: 5))
            .thenAnswer((_) async => [_makeAdhesion()]);
        return buildBloc();
      },
      act: (b) => b.add(const LoadAdhesions(page: 1, size: 5)),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded),
      ],
      verify: (_) => verify(mockRepository.getAll(page: 1, size: 5)),
    );
  });

  // ---- LoadAdhesionsByMembre -----------------------------------------------

  group('LoadAdhesionsByMembre', () {
    final list = [_makeAdhesion()];

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits loading then loaded with membre adhesions',
      build: () {
        when(mockRepository.getByMembre(any,
                page: anyNamed('page'), size: anyNamed('size')))
            .thenAnswer((_) async => list);
        return buildBloc();
      },
      act: (b) => b.add(LoadAdhesionsByMembre('membre1')),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded)
            .having((s) => s.adhesions.length, 'count', 1),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits error when repository throws',
      build: () {
        when(mockRepository.getByMembre(any,
                page: anyNamed('page'), size: anyNamed('size')))
            .thenThrow(Exception('server error'));
        return buildBloc();
      },
      act: (b) => b.add(LoadAdhesionsByMembre('membre1')),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.error),
      ],
    );
  });

  // ---- LoadAdhesionsEnAttente ----------------------------------------------

  group('LoadAdhesionsEnAttente', () {
    final enAttente = [_makeAdhesion(statut: 'EN_ATTENTE')];

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits loading then loaded with pending adhesions',
      build: () {
        when(mockRepository.getEnAttente(
                page: anyNamed('page'), size: anyNamed('size')))
            .thenAnswer((_) async => enAttente);
        return buildBloc();
      },
      act: (b) => b.add(const LoadAdhesionsEnAttente()),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded)
            .having((s) => s.adhesions.length, 'count', 1),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits error on repository failure',
      build: () {
        when(mockRepository.getEnAttente(
                page: anyNamed('page'), size: anyNamed('size')))
            .thenThrow(Exception('server error'));
        return buildBloc();
      },
      act: (b) => b.add(const LoadAdhesionsEnAttente()),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.error),
      ],
    );
  });

  // ---- LoadAdhesionsByStatut -----------------------------------------------

  group('LoadAdhesionsByStatut', () {
    final approved = [_makeAdhesion(statut: 'APPROUVEE')];

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits loading then loaded filtered by statut',
      build: () {
        when(mockRepository.getByStatut(any,
                page: anyNamed('page'), size: anyNamed('size')))
            .thenAnswer((_) async => approved);
        return buildBloc();
      },
      act: (b) => b.add(LoadAdhesionsByStatut('APPROUVEE')),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits error on failure',
      build: () {
        when(mockRepository.getByStatut(any,
                page: anyNamed('page'), size: anyNamed('size')))
            .thenThrow(Exception('filter failed'));
        return buildBloc();
      },
      act: (b) => b.add(LoadAdhesionsByStatut('APPROUVEE')),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.error),
      ],
    );
  });

  // ---- LoadAdhesionById ----------------------------------------------------

  group('LoadAdhesionById', () {
    final adhesion = _makeAdhesion();

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits loading then loaded with adhesionDetail',
      build: () {
        when(mockRepository.getById(any))
            .thenAnswer((_) async => adhesion);
        return buildBloc();
      },
      act: (b) => b.add(LoadAdhesionById('adh1')),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded)
            .having((s) => s.adhesionDetail?.id, 'detail id', 'adh1'),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits error when not found',
      build: () {
        when(mockRepository.getById(any))
            .thenThrow(Exception('not found'));
        return buildBloc();
      },
      act: (b) => b.add(LoadAdhesionById('missing')),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.error),
      ],
    );
  });

  // ---- CreateAdhesion ------------------------------------------------------

  group('CreateAdhesion', () {
    final adhesion = _makeAdhesion();

    blocTest<AdhesionsBloc, AdhesionsState>(
      'creates adhesion then re-triggers LoadAdhesions',
      build: () {
        when(mockRepository.create(any)).thenAnswer((_) async => adhesion);
        // LoadAdhesions triggered internally
        when(mockRepository.getAll(
                page: anyNamed('page'), size: anyNamed('size')))
            .thenAnswer((_) async => [adhesion]);
        return buildBloc();
      },
      act: (b) => b.add(CreateAdhesion(adhesion)),
      expect: () => [
        // Loading from CreateAdhesion
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        // Loading from auto-triggered LoadAdhesions
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        // Loaded from LoadAdhesions result
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits error on failure',
      build: () {
        when(mockRepository.create(any))
            .thenThrow(Exception('creation failed'));
        return buildBloc();
      },
      act: (b) => b.add(CreateAdhesion(adhesion)),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.error),
      ],
    );
  });

  // ---- ApprouverAdhesion ---------------------------------------------------

  group('ApprouverAdhesion', () {
    final approved = _makeAdhesion(statut: 'APPROUVEE');

    blocTest<AdhesionsBloc, AdhesionsState>(
      'approves adhesion and re-triggers LoadAdhesions',
      build: () {
        when(mockRepository.approuver(any, approuvePar: anyNamed('approuvePar')))
            .thenAnswer((_) async => approved);
        when(mockRepository.getAll(
                page: anyNamed('page'), size: anyNamed('size')))
            .thenAnswer((_) async => [approved]);
        return buildBloc();
      },
      act: (b) => b.add(ApprouverAdhesion('adh1', approuvePar: 'admin1')),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded)
            .having((s) => s.adhesionDetail?.statut, 'statut', 'APPROUVEE'),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits error on failure',
      build: () {
        when(mockRepository.approuver(any, approuvePar: anyNamed('approuvePar')))
            .thenThrow(Exception('approval failed'));
        return buildBloc();
      },
      act: (b) => b.add(ApprouverAdhesion('adh1')),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.error),
      ],
    );
  });

  // ---- RejeterAdhesion -----------------------------------------------------

  group('RejeterAdhesion', () {
    final rejected = _makeAdhesion(statut: 'REJETEE');

    blocTest<AdhesionsBloc, AdhesionsState>(
      'rejects adhesion with motif and re-triggers LoadAdhesions',
      build: () {
        when(mockRepository.rejeter(any, any))
            .thenAnswer((_) async => rejected);
        when(mockRepository.getAll(
                page: anyNamed('page'), size: anyNamed('size')))
            .thenAnswer((_) async => []);
        return buildBloc();
      },
      act: (b) => b.add(RejeterAdhesion('adh1', 'Dossier incomplet')),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded)
            .having((s) => s.adhesionDetail?.statut, 'statut', 'REJETEE'),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits error on failure',
      build: () {
        when(mockRepository.rejeter(any, any))
            .thenThrow(Exception('rejection failed'));
        return buildBloc();
      },
      act: (b) => b.add(RejeterAdhesion('adh1', 'motif')),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.error),
      ],
    );
  });

  // ---- EnregistrerPaiementAdhesion -----------------------------------------

  group('EnregistrerPaiementAdhesion', () {
    final paid = _makeAdhesion(statut: 'PAYEE');

    blocTest<AdhesionsBloc, AdhesionsState>(
      'records payment then re-triggers LoadAdhesions',
      build: () {
        when(mockRepository.enregistrerPaiement(
          any,
          montantPaye: anyNamed('montantPaye'),
          methodePaiement: anyNamed('methodePaiement'),
          referencePaiement: anyNamed('referencePaiement'),
        )).thenAnswer((_) async => paid);
        when(mockRepository.getAll(
                page: anyNamed('page'), size: anyNamed('size')))
            .thenAnswer((_) async => [paid]);
        return buildBloc();
      },
      act: (b) => b.add(EnregistrerPaiementAdhesion(
        'adh1',
        montantPaye: 10000,
        methodePaiement: 'WAVE_MONEY',
        referencePaiement: 'REF-001',
      )),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded)
            .having((s) => s.adhesionDetail?.statut, 'statut', 'PAYEE'),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loaded),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits error on failure',
      build: () {
        when(mockRepository.enregistrerPaiement(
          any,
          montantPaye: anyNamed('montantPaye'),
          methodePaiement: anyNamed('methodePaiement'),
          referencePaiement: anyNamed('referencePaiement'),
        )).thenThrow(Exception('payment failed'));
        return buildBloc();
      },
      act: (b) => b.add(EnregistrerPaiementAdhesion('adh1', montantPaye: 5000)),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.loading),
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.error),
      ],
    );
  });

  // ---- LoadAdhesionsStats --------------------------------------------------

  group('LoadAdhesionsStats', () {
    final stats = {
      'total': 10,
      'enAttente': 3,
      'approuvees': 5,
      'rejetees': 2,
    };

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits state with stats populated on success',
      build: () {
        when(mockRepository.getStats()).thenAnswer((_) async => stats);
        return buildBloc();
      },
      act: (b) => b.add(const LoadAdhesionsStats()),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.stats, 'stats', stats),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits state with null stats when repository returns null',
      build: () {
        when(mockRepository.getStats()).thenAnswer((_) async => null);
        return buildBloc();
      },
      act: (b) => b.add(const LoadAdhesionsStats()),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.stats, 'stats', isNull),
      ],
    );

    blocTest<AdhesionsBloc, AdhesionsState>(
      'emits error on failure',
      build: () {
        when(mockRepository.getStats())
            .thenThrow(Exception('stats error'));
        return buildBloc();
      },
      act: (b) => b.add(const LoadAdhesionsStats()),
      expect: () => [
        isA<AdhesionsState>()
            .having((s) => s.status, 'status', AdhesionsStatus.error),
      ],
    );
  });
}
