import { describe, it, expect, vi, beforeEach } from 'vitest';
import axios, { AxiosError } from 'axios';
import {
  login,
  register,
  logout,
  refreshToken,
  requestPasswordReset,
  resetPassword,
  verifyEmail,
  resendVerificationEmail,
  type AuthResponse,
  type ApiError,
} from './authService';
import { apiClient } from '@/services/api/client';

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

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

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

  describe('login', () => {
    it('should successfully login and return auth response', async () => {
      const mockResponse: AuthResponse = {
        accessToken: 'access-token-123',
        refreshToken: 'refresh-token-123',
        user: {
          id: 1,
          email: 'test@example.com',
          username: 'testuser',
        },
      };

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

      const result = await login({
        email: 'test@example.com',
        password: 'password123',
      });

      expect(result).toEqual(mockResponse);
      expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/login', {
        email: 'test@example.com',
        password: 'password123',
      });
    });

    it('should throw ApiError on login failure', async () => {
      const mockError = new AxiosError('Login failed');
      mockError.response = {
        status: 401,
        data: { error: 'Invalid credentials' },
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(
        login({
          email: 'test@example.com',
          password: 'wrongpassword',
        }),
      ).rejects.toMatchObject({
        message: 'Invalid credentials',
        code: '401',
      });
    });

    it('should handle network errors', async () => {
      const mockError = new AxiosError('Network Error');
      mockError.request = {};

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(
        login({
          email: 'test@example.com',
          password: 'password123',
        }),
      ).rejects.toMatchObject({
        message: 'Network error: Unable to connect to server',
        code: 'NETWORK_ERROR',
      });
    });
  });

  describe('register', () => {
    it('should successfully register and return auth response', async () => {
      const mockResponse: AuthResponse = {
        accessToken: 'access-token-123',
        refreshToken: 'refresh-token-123',
        user: {
          id: 1,
          email: 'newuser@example.com',
          username: 'newuser',
        },
      };

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

      const result = await register({
        email: 'newuser@example.com',
        password: 'password123',
        confirmPassword: 'password123',
        username: 'newuser',
      });

      expect(result).toEqual(mockResponse);
      expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/register', {
        email: 'newuser@example.com',
        password: 'password123',
        username: 'newuser',
      });
    });

    it('should throw ApiError on registration failure', async () => {
      const mockError = new AxiosError('Registration failed');
      mockError.response = {
        status: 409,
        data: { error: 'Email already exists' },
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(
        register({
          email: 'existing@example.com',
          password: 'password123',
          confirmPassword: 'password123',
          username: 'existinguser',
        }),
      ).rejects.toMatchObject({
        message: 'Email already exists',
        code: '409',
      });
    });
  });

  describe('logout', () => {
    it('should successfully logout', async () => {
      mockedApiClient.post.mockResolvedValue({ data: {} });

      await logout();

      expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/logout');
    });

    it('should throw ApiError on logout failure', async () => {
      const mockError = new AxiosError('Logout failed');
      mockError.response = {
        status: 500,
        data: { error: 'Internal server error' },
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(logout()).rejects.toMatchObject({
        message: 'Internal server error',
        code: '500',
      });
    });
  });

  describe('refreshToken', () => {
    it('should successfully refresh token and return auth response', async () => {
      const mockResponse: AuthResponse = {
        accessToken: 'new-access-token-123',
        refreshToken: 'new-refresh-token-123',
        user: {
          id: 1,
          email: 'test@example.com',
          username: 'testuser',
        },
      };

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

      const result = await refreshToken('refresh-token-123');

      expect(result).toEqual(mockResponse);
      expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/refresh', {
        refreshToken: 'refresh-token-123',
      });
    });

    it('should throw ApiError on refresh failure', async () => {
      const mockError = new AxiosError('Refresh failed');
      mockError.response = {
        status: 401,
        data: { error: 'Invalid refresh token' },
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(refreshToken('invalid-token')).rejects.toMatchObject({
        message: 'Invalid refresh token',
        code: '401',
      });
    });
  });

  describe('requestPasswordReset', () => {
    it('should successfully request password reset', async () => {
      mockedApiClient.post.mockResolvedValue({ data: {} });

      await requestPasswordReset({
        email: 'test@example.com',
      });

      expect(mockedApiClient.post).toHaveBeenCalledWith(
        '/auth/password/reset-request',
        {
          email: 'test@example.com',
        },
      );
    });

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

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(
        requestPasswordReset({
          email: 'nonexistent@example.com',
        }),
      ).rejects.toMatchObject({
        message: 'User not found',
        code: '404',
      });
    });
  });

  describe('resetPassword', () => {
    it('should successfully reset password', async () => {
      mockedApiClient.post.mockResolvedValue({ data: {} });

      await resetPassword({
        token: 'reset-token-123',
        password: 'newpassword123',
        confirmPassword: 'newpassword123',
      });

      expect(mockedApiClient.post).toHaveBeenCalledWith(
        '/auth/password/reset',
        {
          token: 'reset-token-123',
          password: 'newpassword123',
        },
      );
    });

    it('should throw ApiError on reset failure', async () => {
      const mockError = new AxiosError('Reset failed');
      mockError.response = {
        status: 400,
        data: { error: 'Invalid or expired token' },
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(
        resetPassword({
          token: 'invalid-token',
          password: 'newpassword123',
          confirmPassword: 'newpassword123',
        }),
      ).rejects.toMatchObject({
        message: 'Invalid or expired token',
        code: '400',
      });
    });
  });

  describe('verifyEmail', () => {
    it('should successfully verify email', async () => {
      mockedApiClient.get.mockResolvedValue({ data: {} });

      await verifyEmail('verification-token-123');

      expect(mockedApiClient.get).toHaveBeenCalledWith(
        '/auth/verify-email?token=verification-token-123',
      );
    });

    it('should throw ApiError on verification failure', async () => {
      const mockError = new AxiosError('Verification failed');
      mockError.response = {
        status: 400,
        data: { error: 'Invalid or expired token' },
      } as any;

      mockedApiClient.get.mockRejectedValue(mockError);

      await expect(verifyEmail('invalid-token')).rejects.toMatchObject({
        message: 'Invalid or expired token',
        code: '400',
      });
    });
  });

  describe('resendVerificationEmail', () => {
    it('should successfully resend verification email', async () => {
      mockedApiClient.post.mockResolvedValue({ data: {} });

      await resendVerificationEmail('test@example.com');

      expect(mockedApiClient.post).toHaveBeenCalledWith(
        '/auth/resend-verification',
        {
          email: 'test@example.com',
        },
      );
    });

    it('should throw ApiError on resend failure', async () => {
      const mockError = new AxiosError('Resend failed');
      mockError.response = {
        status: 429,
        data: { error: 'Too many requests' },
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(
        resendVerificationEmail('test@example.com'),
      ).rejects.toMatchObject({
        message: 'Too many requests',
        code: '429',
      });
    });
  });
});
