import 'dart:convert';

import 'package:afterwork/core/errors/exceptions.dart';
import 'package:afterwork/data/repositories/friends_repository_impl.dart';
import 'package:afterwork/domain/entities/friend.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 FriendsRepositoryImpl repository;
  late MockHttpClient mockHttpClient;

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

  setUp(() {
    mockHttpClient = MockHttpClient();
    reset(mockHttpClient); // Reset mock between tests
    repository = FriendsRepositoryImpl(client: mockHttpClient);
  });

  group('FriendsRepositoryImpl', () {
    const tUserId = 'user123';
    const tFriendId = 'friend456';
    final tFriend = Friend(
      friendId: tFriendId,
      friendFirstName: 'John',
      friendLastName: 'Doe',
      email: 'john@example.com',
      status: FriendStatus.accepted,
    );

    final tFriendsJson = [
      {
        'friendId': 'friend1',
        'friendFirstName': 'Jane',
        'friendLastName': 'Smith',
        'email': 'jane@example.com',
        'status': 'accepted',
      },
      {
        'friendId': 'friend2',
        'friendFirstName': 'Bob',
        'friendLastName': 'Johnson',
        'email': 'bob@example.com',
        'status': 'pending',
      },
    ];

    group('fetchFriends', () {
      test('should return list of friends when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.get(
              any(),
              headers: any(named: 'headers'),
            )).thenAnswer(
          (_) async => http.Response(
            jsonEncode(tFriendsJson),
            200,
          ),
        );

        // Act
        final result = await repository.fetchFriends(tUserId, 0, 10);

        // Assert
        expect(result, isA<List<Friend>>());
        expect(result.length, 2);
        expect(result.first.friendId, 'friend1');
        expect(result.first.friendFirstName, 'Jane');
        verify(() => mockHttpClient.get(
              any(),
              headers: any(named: 'headers'),
            )).called(1);
      });

      test('should throw exception when API call fails', () async {
        // Arrange
        when(() => mockHttpClient.get(
              any(),
              headers: any(named: 'headers'),
            )).thenAnswer(
          (_) async => http.Response('{"message": "Error"}', 500),
        );

        // Act & Assert
        expect(
          () => repository.fetchFriends(tUserId, 0, 10),
          throwsA(isA<ServerException>()),
        );
        verify(() => mockHttpClient.get(
              any(),
              headers: any(named: 'headers'),
            )).called(1);
      });

      test('should throw exception when exception occurs', () async {
        // Arrange
        when(() => mockHttpClient.get(any())).thenThrow(Exception('Network error'));

        // Act & Assert
        expect(
          () => repository.fetchFriends(tUserId, 0, 10),
          throwsA(isA<ServerException>()),
        );
      });

      test('should call correct URL with pagination parameters', () async {
        // Arrange
        when(() => mockHttpClient.get(
              any(),
              headers: any(named: 'headers'),
            )).thenAnswer(
          (_) async => http.Response(jsonEncode([]), 200),
        );

        // Act
        await repository.fetchFriends(tUserId, 2, 20);

        // Assert
        final captured = verify(() => mockHttpClient.get(
              captureAny(),
              headers: any(named: 'headers'),
            )).captured;
        final uri = captured.first as Uri;
        expect(uri.path, contains('/friends/list/$tUserId'));
        expect(uri.queryParameters['page'], '2');
        expect(uri.queryParameters['size'], '20');
      });
    });

    group('addFriend', () {
      test('should add friend 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 repository.addFriend(tUserId, tFriendId);

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

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

        // Act & Assert
        expect(
          () => repository.addFriend(tUserId, tFriendId),
          throwsA(isA<ValidationException>()),
        );
      });

      test('should throw exception when network error occurs', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenThrow(Exception('Network error'));

        // Act & Assert
        expect(
          () => repository.addFriend(tUserId, tFriendId),
          throwsA(isA<ServerException>()),
        );
      });

      test('should send correct JSON body', () async {
        // Arrange - Use a valid JSON response
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer((_) async => http.Response('{"success": true}', 200));

        // Act
        await repository.addFriend(tUserId, tFriendId);

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

    group('removeFriend', () {
      test('should remove friend successfully when API call is successful', () async {
        // Arrange
        when(() => mockHttpClient.delete(
              any(),
              headers: any(named: 'headers'),
            )).thenAnswer(
          (_) async => http.Response('Success', 200),
        );

        // Act
        await repository.removeFriend(tFriendId);

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

      test('should throw exception when API call fails', () async {
        // Arrange
        when(() => mockHttpClient.delete(any())).thenAnswer(
          (_) async => http.Response('{"message": "Error"}', 404),
        );

        // Act & Assert
        expect(
          () => repository.removeFriend(tFriendId),
          throwsA(isA<ServerException>()),
        );
      });

      test('should call correct URL', () async {
        // Arrange
        when(() => mockHttpClient.delete(
              any(),
              headers: any(named: 'headers'),
            )).thenAnswer(
          (_) async => http.Response('Success', 200),
        );

        // Act
        await repository.removeFriend(tFriendId);

        // Assert
        final captured = verify(() => mockHttpClient.delete(
              captureAny(),
              headers: any(named: 'headers'),
            )).captured;
        final uri = captured.first as Uri;
        expect(uri.path, contains('/friends/$tFriendId'));
      });
    });

    group('getFriendDetails', () {
      final tFriendDetailsJson = {
        'friendId': tFriendId,
        'friendFirstName': 'John',
        'friendLastName': 'Doe',
        'email': 'john@example.com',
        'status': 'accepted',
      };

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

        // Act
        final result = await repository.getFriendDetails(tFriendId, tUserId);

        // Assert
        expect(result, isNotNull);
        expect(result?.friendId, tFriendId);
        expect(result?.friendFirstName, 'John');
        expect(result?.friendLastName, 'Doe');
      });

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

        // Act
        final result = await repository.getFriendDetails(tFriendId, tUserId);

        // Assert
        expect(result, isNull);
      });

      test('should throw exception when exception occurs', () async {
        // Arrange
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenThrow(Exception('Network error'));

        // Act & Assert
        expect(
          () => repository.getFriendDetails(tFriendId, tUserId),
          throwsA(isA<ServerException>()),
        );
      });

      test('should send correct request body', () async {
        // Arrange - Ensure the mock is properly set up
        when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer((_) async => http.Response(jsonEncode(tFriendDetailsJson), 200));

        // Act
        final result = await repository.getFriendDetails(tFriendId, tUserId);

        // Assert - Verify the result first
        expect(result, isNotNull);
        
        // Then verify the request body
        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['friendId'], tFriendId);
        expect(body['userId'], tUserId);
      });
    });

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

        // Act
        await repository.updateFriendStatus(tFriendId, 'accepted');

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

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

        // Act & Assert
        expect(
          () => repository.updateFriendStatus(tFriendId, 'accepted'),
          throwsA(isA<ValidationException>()),
        );
      });

      test('should send correct status in body', () async {
        // Arrange - Use a valid JSON response
        when(() => mockHttpClient.patch(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer((_) async => http.Response('{"success": true}', 200));

        // Act
        await repository.updateFriendStatus(tFriendId, 'blocked');

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

      test('should call correct URL', () async {
        // Arrange
        when(() => mockHttpClient.patch(any(), headers: any(named: 'headers'), body: any(named: 'body')))
            .thenAnswer((_) async => http.Response('Success', 200));

        // Act
        await repository.updateFriendStatus(tFriendId, 'accepted');

        // Assert
        final captured = verify(() => mockHttpClient.patch(
              captureAny(),
              headers: any(named: 'headers'),
              body: any(named: 'body'),
            )).captured;
        final uri = captured.first as Uri;
        expect(uri.path, contains('/friends/$tFriendId/status'));
      });
    });
  });
}

