import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AxiosError } from 'axios';
import { twoFactorService } from './2fa-service';
import { apiClient } from './api/client';
import { requireFeature } from '@/config/features';

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

// Mock feature config
vi.mock('@/config/features', () => ({
  requireFeature: vi.fn(),
  FEATURES: {
    TWO_FACTOR_AUTH: true,
  },
}));

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

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

  describe('getStatus', () => {
    it('should get 2FA status successfully', async () => {
      const mockStatus = { enabled: true };

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

      const result = await twoFactorService.getStatus();

      expect(result).toEqual(mockStatus);
      expect(requireFeature).toHaveBeenCalledWith('TWO_FACTOR_AUTH');
      expect(mockedApiClient.get).toHaveBeenCalledWith('/auth/2fa/status');
    });

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

      mockedApiClient.get.mockRejectedValue(mockError);

      await expect(twoFactorService.getStatus()).rejects.toThrow();
    });
  });

  describe('setup', () => {
    it('should setup 2FA successfully', async () => {
      const mockSetupResponse = {
        secret: 'JBSWY3DPEHPK3PXP',
        qr_code_url: 'https://example.com/qr.png',
        recovery_codes: ['code1', 'code2', 'code3'],
      };

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

      const result = await twoFactorService.setup();

      expect(result).toEqual(mockSetupResponse);
      expect(requireFeature).toHaveBeenCalledWith('TWO_FACTOR_AUTH');
      expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/2fa/setup');
    });

    it('should throw error on setup failure', async () => {
      const mockError = new AxiosError('Setup failed');
      mockError.response = {
        status: 400,
        data: { error: '2FA already enabled' },
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(twoFactorService.setup()).rejects.toThrow();
    });
  });

  describe('verify', () => {
    it('should verify 2FA code successfully', async () => {
      mockedApiClient.post.mockResolvedValue({
        data: { message: '2FA enabled successfully' },
      });

      await twoFactorService.verify('JBSWY3DPEHPK3PXP', '123456');

      expect(requireFeature).toHaveBeenCalledWith('TWO_FACTOR_AUTH');
      expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/2fa/verify', {
        secret: 'JBSWY3DPEHPK3PXP',
        code: '123456',
      });
    });

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

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(
        twoFactorService.verify('JBSWY3DPEHPK3PXP', 'invalid'),
      ).rejects.toThrow();
    });
  });

  describe('disable', () => {
    it('should disable 2FA successfully', async () => {
      mockedApiClient.post.mockResolvedValue({
        data: { message: '2FA disabled successfully' },
      });

      await twoFactorService.disable('password123');

      expect(requireFeature).toHaveBeenCalledWith('TWO_FACTOR_AUTH');
      expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/2fa/disable', {
        password: 'password123',
      });
    });

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

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(twoFactorService.disable('wrongpassword')).rejects.toThrow();
    });
  });
});
