import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AxiosError } from 'axios';
import {
  getProfile,
  getProfileByUsername,
  updateProfile,
  calculateProfileCompletion,
  followUser,
  unfollowUser,
  getFollowers,
  getFollowing,
  type UserProfile,
  type UpdateProfileRequest,
} from './profileService';
import { apiClient } from '@/services/api/client';

// Mock apiClient
vi.mock('@/services/api/client', () => ({
  apiClient: {
    get: vi.fn(),
    put: vi.fn(),
    post: vi.fn(),
    delete: vi.fn(),
  },
}));

const mockedApiClient = apiClient as {
  get: ReturnType<typeof vi.fn>;
  put: ReturnType<typeof vi.fn>;
  post: ReturnType<typeof vi.fn>;
  delete: ReturnType<typeof vi.fn>;
};

describe('profileService', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('getProfile', () => {
    it('should fetch user profile by ID', async () => {
      const mockProfile: UserProfile = {
        id: 'user-1',
        username: 'testuser',
        first_name: 'Test',
        last_name: 'User',
        avatar_url: 'https://example.com/avatar.jpg',
        bio: 'Test bio',
        location: 'Test Location',
        birthdate: '1990-01-01',
        gender: 'male',
        created_at: '2024-01-01T00:00:00Z',
        followers_count: 10,
        following_count: 5,
      };

      mockedApiClient.get.mockResolvedValue({
        data: { profile: mockProfile },
      });

      const result = await getProfile('user-1');

      expect(result).toEqual(mockProfile);
      expect(mockedApiClient.get).toHaveBeenCalledWith('/users/user-1');
    });

    it('should throw error on fetch failure', async () => {
      const mockError = new AxiosError('Fetch failed');
      mockError.response = {
        status: 404,
        data: { error: 'User not found' },
      } as any;

      mockedApiClient.get.mockRejectedValue(mockError);

      await expect(getProfile('invalid-user')).rejects.toThrow();
    });
  });

  describe('getProfileByUsername', () => {
    it('should fetch user profile by username', async () => {
      const mockProfile: UserProfile = {
        id: 'user-1',
        username: 'testuser',
        first_name: 'Test',
        last_name: 'User',
        avatar_url: null,
        bio: null,
        location: null,
        birthdate: null,
        gender: null,
        created_at: '2024-01-01T00:00:00Z',
      };

      mockedApiClient.get.mockResolvedValue({
        data: { profile: mockProfile },
      });

      const result = await getProfileByUsername('testuser');

      expect(result).toEqual(mockProfile);
      expect(mockedApiClient.get).toHaveBeenCalledWith(
        '/users/by-username/testuser',
      );
    });

    it('should handle response without profile wrapper', async () => {
      const mockProfile: UserProfile = {
        id: 'user-1',
        username: 'testuser',
        first_name: 'Test',
        last_name: 'User',
        avatar_url: null,
        bio: null,
        location: null,
        birthdate: null,
        gender: null,
        created_at: '2024-01-01T00:00:00Z',
      };

      mockedApiClient.get.mockResolvedValue({
        data: mockProfile,
      });

      const result = await getProfileByUsername('testuser');

      expect(result).toEqual(mockProfile);
    });
  });

  describe('updateProfile', () => {
    it('should update user profile successfully', async () => {
      const updateData: UpdateProfileRequest = {
        first_name: 'Updated',
        last_name: 'Name',
        bio: 'Updated bio',
      };

      const mockUpdatedProfile: UserProfile = {
        id: 'user-1',
        username: 'testuser',
        first_name: 'Updated',
        last_name: 'Name',
        avatar_url: null,
        bio: 'Updated bio',
        location: null,
        birthdate: null,
        gender: null,
        created_at: '2024-01-01T00:00:00Z',
      };

      mockedApiClient.put.mockResolvedValue({
        data: { profile: mockUpdatedProfile },
      });

      const result = await updateProfile('user-1', updateData);

      expect(result).toEqual(mockUpdatedProfile);
      expect(mockedApiClient.put).toHaveBeenCalledWith(
        '/users/user-1',
        updateData,
      );
    });

    it('should throw error on update failure', async () => {
      const mockError = new AxiosError('Update failed');
      mockError.response = {
        status: 400,
        data: { error: 'Invalid data' },
      } as any;

      mockedApiClient.put.mockRejectedValue(mockError);

      await expect(
        updateProfile('user-1', { username: 'invalid' }),
      ).rejects.toThrow();
    });
  });

  describe('calculateProfileCompletion', () => {
    it('should calculate profile completion', async () => {
      const mockCompletion = {
        percentage: 75,
        missing: ['bio', 'location'],
      };

      mockedApiClient.get.mockResolvedValue({
        data: mockCompletion,
      });

      const result = await calculateProfileCompletion('user-1');

      expect(result).toEqual(mockCompletion);
      expect(mockedApiClient.get).toHaveBeenCalledWith(
        '/users/user-1/completion',
      );
    });
  });

  describe('followUser', () => {
    it('should follow a user successfully', async () => {
      const mockResponse = {
        message: 'User followed successfully',
        is_following: true,
      };

      mockedApiClient.post.mockResolvedValue({
        data: mockResponse,
      });

      const result = await followUser('user-2');

      expect(result).toEqual(mockResponse);
      expect(mockedApiClient.post).toHaveBeenCalledWith('/users/user-2/follow');
    });

    it('should throw error on follow failure', async () => {
      const mockError = new AxiosError('Follow failed');
      mockError.response = {
        status: 400,
        data: { error: 'Cannot follow yourself' },
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(followUser('user-1')).rejects.toThrow();
    });
  });

  describe('unfollowUser', () => {
    it('should unfollow a user successfully', async () => {
      const mockResponse = {
        message: 'User unfollowed successfully',
        is_following: false,
      };

      mockedApiClient.delete.mockResolvedValue({
        data: mockResponse,
      });

      const result = await unfollowUser('user-2');

      expect(result).toEqual(mockResponse);
      expect(mockedApiClient.delete).toHaveBeenCalledWith(
        '/users/user-2/follow',
      );
    });
  });

  describe('getFollowers', () => {
    it('should fetch user followers', async () => {
      const mockResponse = {
        followers: [
          {
            id: 'follower-1',
            username: 'follower1',
            avatar_url: 'https://example.com/avatar.jpg',
            created_at: '2024-01-01T00:00:00Z',
          },
        ],
        total: 1,
      };

      mockedApiClient.get.mockResolvedValue({
        data: mockResponse,
      });

      const result = await getFollowers('user-1', 1, 20);

      expect(result).toEqual(mockResponse);
      expect(mockedApiClient.get).toHaveBeenCalledWith(
        '/users/user-1/followers',
        {
          params: { page: 1, limit: 20 },
        },
      );
    });
  });

  describe('getFollowing', () => {
    it('should fetch user following', async () => {
      const mockResponse = {
        following: [
          {
            id: 'following-1',
            username: 'following1',
            avatar_url: 'https://example.com/avatar.jpg',
            created_at: '2024-01-01T00:00:00Z',
          },
        ],
        total: 1,
      };

      mockedApiClient.get.mockResolvedValue({
        data: mockResponse,
      });

      const result = await getFollowing('user-1', 1, 20);

      expect(result).toEqual(mockResponse);
      expect(mockedApiClient.get).toHaveBeenCalledWith(
        '/users/user-1/following',
        {
          params: { page: 1, limit: 20 },
        },
      );
    });
  });
});
