import 'dart:convert';

import 'package:afterwork/core/errors/exceptions.dart';
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
import 'package:afterwork/data/models/event_model.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mocktail/mocktail.dart';

// Mock classes
class MockHttpClient extends Mock implements http.Client {}

void main() {
  late EventRemoteDataSource dataSource;
  late MockHttpClient mockHttpClient;

  setUpAll(() {
    // Register fallback values for mocktail
    registerFallbackValue(Uri.parse('http://example.com'));
    registerFallbackValue(<String, String>{});
  });

  setUp(() {
    mockHttpClient = MockHttpClient();
    dataSource = EventRemoteDataSource(mockHttpClient);
  });

  group('EventRemoteDataSource', () {
    final tEventJson = {
      'id': '1',
      'title': 'Test Event',
      'description': 'Test Description',
      'startDate': '2026-01-01T00:00:00Z',
      'location': 'Test Location',
      'category': 'Test Category',
      'link': 'https://test.com',
      'imageUrl': 'https://test.com/image.jpg',
      'creatorEmail': 'creator@test.com',
      'creatorFirstName': 'John',
      'creatorLastName': 'Doe',
      'profileImageUrl': 'https://test.com/profile.jpg',
      'participants': ['user1', 'user2'],
      'status': 'ouvert',
      'reactionsCount': 5,
      'commentsCount': 3,
      'sharesCount': 2,
    };

    final tEventModel = EventModel.fromJson(tEventJson);

    group('getAllEvents', () {
      test('should return list of events when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.get(any())).thenAnswer(
          (_) async => http.Response(
            jsonEncode([tEventJson]),
            200,
          ),
        );

        // Act
        final result = await dataSource.getAllEvents();

        // Assert
        expect(result, isA<List<EventModel>>());
        expect(result.length, 1);
        expect(result.first.id, '1');
        verify(() => mockHttpClient.get(any())).called(1);
      });

      test('should throw ServerException when API call fails', () async {
        // Arrange
        when(() => mockHttpClient.get(any())).thenAnswer(
          (_) async => http.Response('Error', 500),
        );

        // Act & Assert
        expect(
          () => dataSource.getAllEvents(),
          throwsA(isA<ServerException>()),
        );
      });
    });

    group('getEventsCreatedByUserAndFriends', () {
      const tUserId = 'user123';

      test('should return list of events when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer(
          (_) async => http.Response(
            jsonEncode([tEventJson]),
            200,
          ),
        );

        // Act
        final result = await dataSource.getEventsCreatedByUserAndFriends(tUserId);

        // Assert
        expect(result, isA<List<EventModel>>());
        expect(result.length, 1);
        expect(result.first.id, '1');
        verify(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .called(1);
      });

      test('should throw ServerException when API call fails', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer((_) async => http.Response('Error', 500));

        // Act & Assert
        expect(
          () => dataSource.getEventsCreatedByUserAndFriends(tUserId),
          throwsA(isA<ServerException>()),
        );
      });

      test('should send correct request body', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer(
          (_) async => http.Response(jsonEncode([]), 200),
        );

        // Act
        await dataSource.getEventsCreatedByUserAndFriends(tUserId);

        // Assert
        final captured = verify(() => mockHttpClient.post(
              any(),
              headers: any(named: 'headers'),
              body: captureAny(named: 'body'),
            )).captured;
        final body = jsonDecode(captured.first as String) as Map<String, dynamic>;
        expect(body['userId'], tUserId);
      });
    });

    group('createEvent', () {
      test('should return created event when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer(
          (_) async => http.Response(
            jsonEncode(tEventJson),
            201,
          ),
        );

        // Act
        final result = await dataSource.createEvent(tEventModel);

        // Assert
        expect(result, isA<EventModel>());
        expect(result.id, '1');
        verify(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .called(1);
      });

      test('should throw ServerException when API call fails', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer((_) async => http.Response('Error', 400));

        // Act & Assert
        expect(
          () => dataSource.createEvent(tEventModel),
          throwsA(isA<ServerException>()),
        );
      });
    });

    group('getEventById', () {
      const tEventId = '1';

      test('should return event when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.get(any())).thenAnswer(
          (_) async => http.Response(
            jsonEncode(tEventJson),
            200,
          ),
        );

        // Act
        final result = await dataSource.getEventById(tEventId);

        // Assert
        expect(result, isA<EventModel>());
        expect(result.id, tEventId);
        verify(() => mockHttpClient.get(any())).called(1);
      });

      test('should throw ServerException when API call fails', () async {
        // Arrange
        when(() => mockHttpClient.get(any())).thenAnswer(
          (_) async => http.Response('Error', 404),
        );

        // Act & Assert
        expect(
          () => dataSource.getEventById(tEventId),
          throwsA(isA<ServerException>()),
        );
      });
    });

    group('updateEvent', () {
      const tEventId = '1';

      test('should return updated event when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.put(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer(
          (_) async => http.Response(
            jsonEncode(tEventJson),
            200,
          ),
        );

        // Act
        final result = await dataSource.updateEvent(tEventId, tEventModel);

        // Assert
        expect(result, isA<EventModel>());
        expect(result.id, tEventId);
        verify(() => mockHttpClient.put(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .called(1);
      });

      test('should throw ServerException when API call fails', () async {
        // Arrange
        when(() => mockHttpClient.put(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer((_) async => http.Response('Error', 400));

        // Act & Assert
        expect(
          () => dataSource.updateEvent(tEventId, tEventModel),
          throwsA(isA<ServerException>()),
        );
      });
    });

    group('deleteEvent', () {
      const tEventId = '1';

      test('should delete event successfully when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.delete(any())).thenAnswer(
          (_) async => http.Response('', 204),
        );

        // Act
        await dataSource.deleteEvent(tEventId);

        // Assert
        verify(() => mockHttpClient.delete(any())).called(1);
      });

      test('should throw ServerException when API call fails', () async {
        // Arrange
        when(() => mockHttpClient.delete(any())).thenAnswer(
          (_) async => http.Response('Error', 500),
        );

        // Act & Assert
        expect(
          () => dataSource.deleteEvent(tEventId),
          throwsA(isA<ServerException>()),
        );
      });
    });

    group('participateInEvent', () {
      const tEventId = '1';
      const tUserId = 'user123';

      test('should return event when participation is successful', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer(
          (_) async => http.Response(
            jsonEncode(tEventJson),
            200,
          ),
        );

        // Act
        final result = await dataSource.participateInEvent(tEventId, tUserId);

        // Assert
        expect(result, isA<EventModel>());
        verify(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .called(1);
      });

      test('should throw ServerException when API call fails', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer((_) async => http.Response('Error', 400));

        // Act & Assert
        expect(
          () => dataSource.participateInEvent(tEventId, tUserId),
          throwsA(isA<ServerException>()),
        );
      });
    });

    group('reactToEvent', () {
      const tEventId = '1';
      const tUserId = 'user123';

      test('should react to event successfully when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer((_) async => http.Response('Success', 200));

        // Act
        await dataSource.reactToEvent(tEventId, tUserId);

        // Assert
        verify(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .called(1);
      });

      test('should throw ServerException when API call fails', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer((_) async => http.Response('Error', 400));

        // Act & Assert
        expect(
          () => dataSource.reactToEvent(tEventId, tUserId),
          throwsA(isA<ServerException>()),
        );
      });
    });

    group('closeEvent', () {
      const tEventId = '1';

      test('should close event successfully when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.patch(any(), headers: any(named: 'headers')))
            .thenAnswer((_) async => http.Response('Success', 200));

        // Act
        await dataSource.closeEvent(tEventId);

        // Assert
        verify(() => mockHttpClient.patch(any(), headers: any(named: 'headers'))).called(1);
      });

      test('should throw ServerExceptionWithMessage when status code is 400', () async {
        // Arrange
        when(() => mockHttpClient.patch(any(), headers: any(named: 'headers')))
            .thenAnswer(
          (_) async => http.Response(
            jsonEncode({'message': 'Event already closed'}),
            400,
          ),
        );

        // Act & Assert
        expect(
          () => dataSource.closeEvent(tEventId),
          throwsA(isA<ServerExceptionWithMessage>()),
        );
      });

      test('should throw ServerExceptionWithMessage when status code is not 200 or 400', () async {
        // Arrange
        when(() => mockHttpClient.patch(any(), headers: any(named: 'headers')))
            .thenAnswer((_) async => http.Response('Error', 500));

        // Act & Assert
        expect(
          () => dataSource.closeEvent(tEventId),
          throwsA(isA<ServerExceptionWithMessage>()),
        );
      });
    });

    group('reopenEvent', () {
      const tEventId = '1';

      test('should reopen event successfully when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.patch(any(), headers: any(named: 'headers')))
            .thenAnswer((_) async => http.Response('Success', 200));

        // Act
        await dataSource.reopenEvent(tEventId);

        // Assert
        verify(() => mockHttpClient.patch(any(), headers: any(named: 'headers'))).called(1);
      });

      test('should throw ServerExceptionWithMessage when status code is 400', () async {
        // Arrange
        when(() => mockHttpClient.patch(any(), headers: any(named: 'headers')))
            .thenAnswer(
          (_) async => http.Response(
            jsonEncode({'message': 'Event already open'}),
            400,
          ),
        );

        // Act & Assert
        expect(
          () => dataSource.reopenEvent(tEventId),
          throwsA(isA<ServerExceptionWithMessage>()),
        );
      });

      test('should throw ServerExceptionWithMessage when status code is 404', () async {
        // Arrange
        when(() => mockHttpClient.patch(any(), headers: any(named: 'headers')))
            .thenAnswer((_) async => http.Response('Not found', 404));

        // Act & Assert
        expect(
          () => dataSource.reopenEvent(tEventId),
          throwsA(isA<ServerExceptionWithMessage>()),
        );
      });

      test('should throw ServerExceptionWithMessage when status code is not 200, 400 or 404', () async {
        // Arrange
        when(() => mockHttpClient.patch(any(), headers: any(named: 'headers')))
            .thenAnswer((_) async => http.Response('Error', 500));

        // Act & Assert
        expect(
          () => dataSource.reopenEvent(tEventId),
          throwsA(isA<ServerExceptionWithMessage>()),
        );
      });
    });
  });
}

