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/logs/presentation/bloc/logs_monitoring_bloc.dart';
import 'package:unionflow_mobile_apps/features/logs/data/repositories/logs_monitoring_repository.dart';
import 'package:unionflow_mobile_apps/features/logs/data/models/system_log_model.dart';
import 'package:unionflow_mobile_apps/features/logs/data/models/system_metrics_model.dart';
import 'package:unionflow_mobile_apps/features/logs/data/models/system_alert_model.dart';

@GenerateMocks([LogsMonitoringRepository])
import 'logs_monitoring_bloc_test.mocks.dart';

void main() {
  late LogsMonitoringBloc bloc;
  late MockLogsMonitoringRepository mockRepository;

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

  SystemLogModel fakeLog({String id = 'log-1', String level = 'INFO'}) =>
      SystemLogModel(id: id, level: level, source: 'API', message: 'Test log');

  SystemMetricsModel fakeMetrics() => const SystemMetricsModel(
        cpuUsagePercent: 45.0,
        memoryUsagePercent: 60.0,
        activeConnections: 10,
      );

  SystemAlertModel fakeAlert({String id = 'alert-1', bool acknowledged = false}) =>
      SystemAlertModel(id: id, level: 'WARNING', title: 'CPU High', acknowledged: acknowledged);

  setUp(() {
    mockRepository = MockLogsMonitoringRepository();
    bloc = LogsMonitoringBloc(mockRepository);
  });

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

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

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

  // ── SearchLogs ────────────────────────────────────────────────────────────

  group('SearchLogs', () {
    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, LogsLoaded] on success with default parameters',
      build: () {
        when(mockRepository.searchLogs(
          level: anyNamed('level'),
          source: anyNamed('source'),
          searchQuery: anyNamed('searchQuery'),
          timeRange: anyNamed('timeRange'),
        )).thenAnswer((_) async => [fakeLog(), fakeLog(id: 'log-2')]);
        return bloc;
      },
      act: (b) => b.add(SearchLogs()),
      expect: () => [
        isA<LogsMonitoringLoading>(),
        isA<LogsLoaded>().having((s) => s.logs.length, 'logs.length', 2),
      ],
    );

    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, LogsLoaded] with filtered parameters',
      build: () {
        when(mockRepository.searchLogs(
          level: 'ERROR',
          source: 'API',
          searchQuery: 'timeout',
          timeRange: 'Dernières 1h',
        )).thenAnswer((_) async => [fakeLog(level: 'ERROR')]);
        return bloc;
      },
      act: (b) => b.add(SearchLogs(
        level: 'ERROR',
        source: 'API',
        searchQuery: 'timeout',
        timeRange: 'Dernières 1h',
      )),
      expect: () => [
        isA<LogsMonitoringLoading>(),
        isA<LogsLoaded>().having(
          (s) => s.logs.first.level,
          'level',
          'ERROR',
        ),
      ],
      verify: (_) {
        verify(mockRepository.searchLogs(
          level: 'ERROR',
          source: 'API',
          searchQuery: 'timeout',
          timeRange: 'Dernières 1h',
        )).called(1);
      },
    );

    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, LogsMonitoringError] on failure',
      build: () {
        when(mockRepository.searchLogs(
          level: anyNamed('level'),
          source: anyNamed('source'),
          searchQuery: anyNamed('searchQuery'),
          timeRange: anyNamed('timeRange'),
        )).thenThrow(Exception('network error'));
        return bloc;
      },
      act: (b) => b.add(SearchLogs()),
      expect: () => [
        isA<LogsMonitoringLoading>(),
        isA<LogsMonitoringError>().having(
          (s) => s.error,
          'error',
          contains('Erreur'),
        ),
      ],
    );

    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, LogsLoaded] with empty result',
      build: () {
        when(mockRepository.searchLogs(
          level: anyNamed('level'),
          source: anyNamed('source'),
          searchQuery: anyNamed('searchQuery'),
          timeRange: anyNamed('timeRange'),
        )).thenAnswer((_) async => []);
        return bloc;
      },
      act: (b) => b.add(SearchLogs()),
      expect: () => [
        isA<LogsMonitoringLoading>(),
        isA<LogsLoaded>().having((s) => s.logs, 'logs', isEmpty),
      ],
    );
  });

  // ── LoadMetrics ───────────────────────────────────────────────────────────

  group('LoadMetrics', () {
    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, MetricsLoaded] on success',
      build: () {
        when(mockRepository.getMetrics()).thenAnswer((_) async => fakeMetrics());
        return bloc;
      },
      act: (b) => b.add(LoadMetrics()),
      expect: () => [
        isA<LogsMonitoringLoading>(),
        isA<MetricsLoaded>().having(
          (s) => s.metrics.cpuUsagePercent,
          'cpuUsagePercent',
          45.0,
        ),
      ],
      verify: (_) => verify(mockRepository.getMetrics()).called(1),
    );

    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, LogsMonitoringError] on metrics failure',
      build: () {
        when(mockRepository.getMetrics()).thenThrow(Exception('metrics error'));
        return bloc;
      },
      act: (b) => b.add(LoadMetrics()),
      expect: () => [isA<LogsMonitoringLoading>(), isA<LogsMonitoringError>()],
    );
  });

  // ── LoadAlerts ────────────────────────────────────────────────────────────

  group('LoadAlerts', () {
    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, AlertsLoaded] on success',
      build: () {
        when(mockRepository.getAlerts())
            .thenAnswer((_) async => [fakeAlert(), fakeAlert(id: 'alert-2')]);
        return bloc;
      },
      act: (b) => b.add(LoadAlerts()),
      expect: () => [
        isA<LogsMonitoringLoading>(),
        isA<AlertsLoaded>().having((s) => s.alerts.length, 'alerts.length', 2),
      ],
    );

    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, LogsMonitoringError] on alerts failure',
      build: () {
        when(mockRepository.getAlerts()).thenThrow(Exception('alerts error'));
        return bloc;
      },
      act: (b) => b.add(LoadAlerts()),
      expect: () => [isA<LogsMonitoringLoading>(), isA<LogsMonitoringError>()],
    );

    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, AlertsLoaded] with empty list',
      build: () {
        when(mockRepository.getAlerts()).thenAnswer((_) async => []);
        return bloc;
      },
      act: (b) => b.add(LoadAlerts()),
      expect: () => [
        isA<LogsMonitoringLoading>(),
        isA<AlertsLoaded>().having((s) => s.alerts, 'alerts', isEmpty),
      ],
    );
  });

  // ── AcknowledgeAlert ──────────────────────────────────────────────────────

  group('AcknowledgeAlert', () {
    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, AlertsLoaded, LogsMonitoringSuccess] on acknowledge success',
      build: () {
        when(mockRepository.acknowledgeAlert('alert-1')).thenAnswer((_) async {});
        when(mockRepository.getAlerts())
            .thenAnswer((_) async => [fakeAlert(acknowledged: true)]);
        return bloc;
      },
      act: (b) => b.add(AcknowledgeAlert('alert-1')),
      expect: () => [
        isA<LogsMonitoringLoading>(),
        isA<AlertsLoaded>(),
        isA<LogsMonitoringSuccess>().having(
          (s) => s.message,
          'message',
          'Alerte acquittée',
        ),
      ],
      verify: (_) {
        verify(mockRepository.acknowledgeAlert('alert-1')).called(1);
        verify(mockRepository.getAlerts()).called(1);
      },
    );

    blocTest<LogsMonitoringBloc, LogsMonitoringState>(
      'emits [Loading, LogsMonitoringError] on acknowledge failure',
      build: () {
        when(mockRepository.acknowledgeAlert(any)).thenThrow(Exception('ack error'));
        return bloc;
      },
      act: (b) => b.add(AcknowledgeAlert('alert-1')),
      expect: () => [isA<LogsMonitoringLoading>(), isA<LogsMonitoringError>()],
    );
  });
}
