import { describe, it, expect, vi, beforeEach } from 'vitest';

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

import {
  getMFAStatus,
  sendPhoneVerification,
  verifyPhone,
  enableSMSMFA,
  setupTOTP,
  verifyTOTPSetup,
  generateBackupCodes,
  getBackupCodesStatus,
  disableMFA,
  sendMFALoginCode,
  verifyMFALogin,
  listTrustedDevices,
  revokeTrustedDevice,
  revokeAllTrustedDevices,
} from '../mfa';
import apiClient from '../client';

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

  // ============================================================================
  // MFA Status
  // ============================================================================

  describe('getMFAStatus', () => {
    it('fetches MFA status from API', async () => {
      const mockStatus = {
        mfa_enabled: true,
        mfa_method: 'TOTP' as const,
        methods: ['TOTP' as const, 'BACKUP' as const],
        phone_last_4: '1234',
        phone_verified: true,
        totp_verified: true,
        backup_codes_count: 8,
        backup_codes_generated_at: '2024-01-01T00:00:00Z',
        trusted_devices_count: 2,
      };
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStatus });

      const result = await getMFAStatus();

      expect(apiClient.get).toHaveBeenCalledWith('/auth/mfa/status/');
      expect(result).toEqual(mockStatus);
    });

    it('returns status when MFA is disabled', async () => {
      const mockStatus = {
        mfa_enabled: false,
        mfa_method: 'NONE' as const,
        methods: [],
        phone_last_4: null,
        phone_verified: false,
        totp_verified: false,
        backup_codes_count: 0,
        backup_codes_generated_at: null,
        trusted_devices_count: 0,
      };
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStatus });

      const result = await getMFAStatus();

      expect(result.mfa_enabled).toBe(false);
      expect(result.mfa_method).toBe('NONE');
      expect(result.methods).toHaveLength(0);
    });

    it('returns status with both SMS and TOTP enabled', async () => {
      const mockStatus = {
        mfa_enabled: true,
        mfa_method: 'BOTH' as const,
        methods: ['SMS' as const, 'TOTP' as const, 'BACKUP' as const],
        phone_last_4: '5678',
        phone_verified: true,
        totp_verified: true,
        backup_codes_count: 10,
        backup_codes_generated_at: '2024-01-15T12:00:00Z',
        trusted_devices_count: 3,
      };
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStatus });

      const result = await getMFAStatus();

      expect(result.mfa_method).toBe('BOTH');
      expect(result.methods).toContain('SMS');
      expect(result.methods).toContain('TOTP');
      expect(result.methods).toContain('BACKUP');
    });
  });

  // ============================================================================
  // SMS Setup
  // ============================================================================

  describe('sendPhoneVerification', () => {
    it('sends phone verification code', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'Verification code sent to +1234567890',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await sendPhoneVerification('+1234567890');

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/phone/send/', {
        phone: '+1234567890',
      });
      expect(result).toEqual(mockResponse.data);
      expect(result.success).toBe(true);
    });

    it('handles different phone number formats', async () => {
      const mockResponse = {
        data: { success: true, message: 'Code sent' },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      await sendPhoneVerification('555-123-4567');

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/phone/send/', {
        phone: '555-123-4567',
      });
    });
  });

  describe('verifyPhone', () => {
    it('verifies phone with valid code', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'Phone number verified successfully',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await verifyPhone('123456');

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/phone/verify/', {
        code: '123456',
      });
      expect(result.success).toBe(true);
    });

    it('handles verification failure', async () => {
      const mockResponse = {
        data: {
          success: false,
          message: 'Invalid verification code',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await verifyPhone('000000');

      expect(result.success).toBe(false);
      expect(result.message).toContain('Invalid');
    });
  });

  describe('enableSMSMFA', () => {
    it('enables SMS MFA successfully', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'SMS MFA enabled successfully',
          mfa_method: 'SMS',
          backup_codes: ['code1', 'code2', 'code3'],
          backup_codes_message: 'Save these backup codes',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await enableSMSMFA();

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/sms/enable/');
      expect(result.success).toBe(true);
      expect(result.mfa_method).toBe('SMS');
      expect(result.backup_codes).toHaveLength(3);
    });

    it('enables SMS MFA without generating backup codes', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'SMS MFA enabled',
          mfa_method: 'SMS',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await enableSMSMFA();

      expect(result.success).toBe(true);
      expect(result.backup_codes).toBeUndefined();
    });
  });

  // ============================================================================
  // TOTP Setup (Authenticator App)
  // ============================================================================

  describe('setupTOTP', () => {
    it('initializes TOTP setup with QR code', async () => {
      const mockResponse = {
        data: {
          success: true,
          secret: 'JBSWY3DPEHPK3PXP',
          qr_code: 'data:image/png;base64,iVBORw0KGgoAAAANS...',
          provisioning_uri: 'otpauth://totp/SmoothSchedule:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=SmoothSchedule',
          message: 'Scan the QR code with your authenticator app',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await setupTOTP();

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/totp/setup/');
      expect(result.success).toBe(true);
      expect(result.secret).toBe('JBSWY3DPEHPK3PXP');
      expect(result.qr_code).toContain('data:image/png');
      expect(result.provisioning_uri).toContain('otpauth://totp/');
    });

    it('returns provisioning URI for manual entry', async () => {
      const mockResponse = {
        data: {
          success: true,
          secret: 'SECRETKEY123',
          qr_code: 'data:image/png;base64,ABC...',
          provisioning_uri: 'otpauth://totp/App:user@test.com?secret=SECRETKEY123',
          message: 'Setup message',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await setupTOTP();

      expect(result.provisioning_uri).toContain('SECRETKEY123');
    });
  });

  describe('verifyTOTPSetup', () => {
    it('verifies TOTP code and completes setup', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'TOTP authentication enabled successfully',
          mfa_method: 'TOTP',
          backup_codes: ['backup1', 'backup2', 'backup3', 'backup4', 'backup5'],
          backup_codes_message: 'Store these codes securely',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await verifyTOTPSetup('123456');

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/totp/verify/', {
        code: '123456',
      });
      expect(result.success).toBe(true);
      expect(result.mfa_method).toBe('TOTP');
      expect(result.backup_codes).toHaveLength(5);
    });

    it('handles invalid TOTP code', async () => {
      const mockResponse = {
        data: {
          success: false,
          message: 'Invalid TOTP code',
          mfa_method: '',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await verifyTOTPSetup('000000');

      expect(result.success).toBe(false);
      expect(result.message).toContain('Invalid');
    });
  });

  // ============================================================================
  // Backup Codes
  // ============================================================================

  describe('generateBackupCodes', () => {
    it('generates new backup codes', async () => {
      const mockResponse = {
        data: {
          success: true,
          backup_codes: [
            'AAAA-BBBB-CCCC',
            'DDDD-EEEE-FFFF',
            'GGGG-HHHH-IIII',
            'JJJJ-KKKK-LLLL',
            'MMMM-NNNN-OOOO',
            'PPPP-QQQQ-RRRR',
            'SSSS-TTTT-UUUU',
            'VVVV-WWWW-XXXX',
            'YYYY-ZZZZ-1111',
            '2222-3333-4444',
          ],
          message: 'Backup codes generated successfully',
          warning: 'Previous backup codes have been invalidated',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await generateBackupCodes();

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/backup-codes/');
      expect(result.success).toBe(true);
      expect(result.backup_codes).toHaveLength(10);
      expect(result.warning).toContain('invalidated');
    });

    it('generates codes in correct format', async () => {
      const mockResponse = {
        data: {
          success: true,
          backup_codes: ['CODE-1234-ABCD', 'CODE-5678-EFGH'],
          message: 'Generated',
          warning: 'Old codes invalidated',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await generateBackupCodes();

      result.backup_codes.forEach(code => {
        expect(code).toMatch(/^[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+$/);
      });
    });
  });

  describe('getBackupCodesStatus', () => {
    it('returns backup codes status', async () => {
      const mockResponse = {
        data: {
          count: 8,
          generated_at: '2024-01-15T10:30:00Z',
        },
      };
      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getBackupCodesStatus();

      expect(apiClient.get).toHaveBeenCalledWith('/auth/mfa/backup-codes/status/');
      expect(result.count).toBe(8);
      expect(result.generated_at).toBe('2024-01-15T10:30:00Z');
    });

    it('returns status when no codes exist', async () => {
      const mockResponse = {
        data: {
          count: 0,
          generated_at: null,
        },
      };
      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getBackupCodesStatus();

      expect(result.count).toBe(0);
      expect(result.generated_at).toBeNull();
    });
  });

  // ============================================================================
  // Disable MFA
  // ============================================================================

  describe('disableMFA', () => {
    it('disables MFA with password', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'MFA has been disabled',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await disableMFA({ password: 'mypassword123' });

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/disable/', {
        password: 'mypassword123',
      });
      expect(result.success).toBe(true);
      expect(result.message).toContain('disabled');
    });

    it('disables MFA with valid MFA code', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'MFA disabled successfully',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await disableMFA({ mfa_code: '123456' });

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/disable/', {
        mfa_code: '123456',
      });
      expect(result.success).toBe(true);
    });

    it('handles both password and MFA code', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'MFA disabled',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      await disableMFA({ password: 'pass', mfa_code: '654321' });

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/disable/', {
        password: 'pass',
        mfa_code: '654321',
      });
    });

    it('handles incorrect credentials', async () => {
      const mockResponse = {
        data: {
          success: false,
          message: 'Invalid password or MFA code',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await disableMFA({ password: 'wrongpass' });

      expect(result.success).toBe(false);
      expect(result.message).toContain('Invalid');
    });
  });

  // ============================================================================
  // MFA Login Challenge
  // ============================================================================

  describe('sendMFALoginCode', () => {
    it('sends SMS code for login', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'Verification code sent to your phone',
          method: 'SMS',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await sendMFALoginCode(42, 'SMS');

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/login/send/', {
        user_id: 42,
        method: 'SMS',
      });
      expect(result.success).toBe(true);
      expect(result.method).toBe('SMS');
    });

    it('defaults to SMS method when not specified', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'Code sent',
          method: 'SMS',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      await sendMFALoginCode(123);

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/login/send/', {
        user_id: 123,
        method: 'SMS',
      });
    });

    it('sends TOTP method (no actual code sent)', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'Use your authenticator app',
          method: 'TOTP',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await sendMFALoginCode(99, 'TOTP');

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/login/send/', {
        user_id: 99,
        method: 'TOTP',
      });
      expect(result.method).toBe('TOTP');
    });
  });

  describe('verifyMFALogin', () => {
    it('verifies MFA code and completes login', async () => {
      const mockResponse = {
        data: {
          success: true,
          access: 'access-token-xyz',
          refresh: 'refresh-token-abc',
          user: {
            id: 42,
            email: 'user@example.com',
            username: 'john_doe',
            first_name: 'John',
            last_name: 'Doe',
            full_name: 'John Doe',
            role: 'owner',
            business_subdomain: 'business1',
            mfa_enabled: true,
          },
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await verifyMFALogin(42, '123456', 'TOTP', false);

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/login/verify/', {
        user_id: 42,
        code: '123456',
        method: 'TOTP',
        trust_device: false,
      });
      expect(result.success).toBe(true);
      expect(result.access).toBe('access-token-xyz');
      expect(result.user.email).toBe('user@example.com');
    });

    it('verifies SMS code', async () => {
      const mockResponse = {
        data: {
          success: true,
          access: 'token1',
          refresh: 'token2',
          user: {
            id: 1,
            email: 'test@test.com',
            username: 'test',
            first_name: 'Test',
            last_name: 'User',
            full_name: 'Test User',
            role: 'staff',
            business_subdomain: null,
            mfa_enabled: true,
          },
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await verifyMFALogin(1, '654321', 'SMS');

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/login/verify/', {
        user_id: 1,
        code: '654321',
        method: 'SMS',
        trust_device: false,
      });
      expect(result.success).toBe(true);
    });

    it('verifies backup code', async () => {
      const mockResponse = {
        data: {
          success: true,
          access: 'token-a',
          refresh: 'token-b',
          user: {
            id: 5,
            email: 'backup@test.com',
            username: 'backup_user',
            first_name: 'Backup',
            last_name: 'Test',
            full_name: 'Backup Test',
            role: 'manager',
            business_subdomain: 'company',
            mfa_enabled: true,
          },
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await verifyMFALogin(5, 'AAAA-BBBB-CCCC', 'BACKUP');

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/login/verify/', {
        user_id: 5,
        code: 'AAAA-BBBB-CCCC',
        method: 'BACKUP',
        trust_device: false,
      });
      expect(result.success).toBe(true);
    });

    it('trusts device after successful verification', async () => {
      const mockResponse = {
        data: {
          success: true,
          access: 'trusted-access',
          refresh: 'trusted-refresh',
          user: {
            id: 10,
            email: 'trusted@example.com',
            username: 'trusted',
            first_name: 'Trusted',
            last_name: 'User',
            full_name: 'Trusted User',
            role: 'owner',
            business_subdomain: 'trusted-biz',
            mfa_enabled: true,
          },
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      await verifyMFALogin(10, '999888', 'TOTP', true);

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/login/verify/', {
        user_id: 10,
        code: '999888',
        method: 'TOTP',
        trust_device: true,
      });
    });

    it('defaults trustDevice to false', async () => {
      const mockResponse = {
        data: {
          success: true,
          access: 'a',
          refresh: 'b',
          user: {
            id: 1,
            email: 'e@e.com',
            username: 'u',
            first_name: 'F',
            last_name: 'L',
            full_name: 'F L',
            role: 'staff',
            business_subdomain: null,
            mfa_enabled: true,
          },
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      await verifyMFALogin(1, '111111', 'SMS');

      expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/login/verify/', {
        user_id: 1,
        code: '111111',
        method: 'SMS',
        trust_device: false,
      });
    });

    it('handles invalid MFA code', async () => {
      const mockResponse = {
        data: {
          success: false,
          access: '',
          refresh: '',
          user: {
            id: 0,
            email: '',
            username: '',
            first_name: '',
            last_name: '',
            full_name: '',
            role: '',
            business_subdomain: null,
            mfa_enabled: false,
          },
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue(mockResponse);

      const result = await verifyMFALogin(1, 'invalid', 'TOTP');

      expect(result.success).toBe(false);
    });
  });

  // ============================================================================
  // Trusted Devices
  // ============================================================================

  describe('listTrustedDevices', () => {
    it('lists all trusted devices', async () => {
      const mockDevices = {
        devices: [
          {
            id: 1,
            name: 'Chrome on Windows',
            ip_address: '192.168.1.100',
            created_at: '2024-01-01T10:00:00Z',
            last_used_at: '2024-01-15T14:30:00Z',
            expires_at: '2024-02-01T10:00:00Z',
            is_current: true,
          },
          {
            id: 2,
            name: 'Safari on iPhone',
            ip_address: '192.168.1.101',
            created_at: '2024-01-05T12:00:00Z',
            last_used_at: '2024-01-14T09:15:00Z',
            expires_at: '2024-02-05T12:00:00Z',
            is_current: false,
          },
        ],
      };
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockDevices });

      const result = await listTrustedDevices();

      expect(apiClient.get).toHaveBeenCalledWith('/auth/mfa/devices/');
      expect(result.devices).toHaveLength(2);
      expect(result.devices[0].is_current).toBe(true);
      expect(result.devices[1].name).toBe('Safari on iPhone');
    });

    it('returns empty list when no devices', async () => {
      const mockDevices = { devices: [] };
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockDevices });

      const result = await listTrustedDevices();

      expect(result.devices).toHaveLength(0);
    });

    it('includes device metadata', async () => {
      const mockDevices = {
        devices: [
          {
            id: 99,
            name: 'Firefox on Linux',
            ip_address: '10.0.0.50',
            created_at: '2024-01-10T08:00:00Z',
            last_used_at: '2024-01-16T16:45:00Z',
            expires_at: '2024-02-10T08:00:00Z',
            is_current: false,
          },
        ],
      };
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockDevices });

      const result = await listTrustedDevices();

      const device = result.devices[0];
      expect(device.id).toBe(99);
      expect(device.name).toBe('Firefox on Linux');
      expect(device.ip_address).toBe('10.0.0.50');
      expect(device.created_at).toBeTruthy();
      expect(device.last_used_at).toBeTruthy();
      expect(device.expires_at).toBeTruthy();
    });
  });

  describe('revokeTrustedDevice', () => {
    it('revokes a specific device', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'Device revoked successfully',
        },
      };
      vi.mocked(apiClient.delete).mockResolvedValue(mockResponse);

      const result = await revokeTrustedDevice(42);

      expect(apiClient.delete).toHaveBeenCalledWith('/auth/mfa/devices/42/');
      expect(result.success).toBe(true);
      expect(result.message).toContain('revoked');
    });

    it('handles different device IDs', async () => {
      const mockResponse = {
        data: { success: true, message: 'Revoked' },
      };
      vi.mocked(apiClient.delete).mockResolvedValue(mockResponse);

      await revokeTrustedDevice(999);

      expect(apiClient.delete).toHaveBeenCalledWith('/auth/mfa/devices/999/');
    });

    it('handles device not found', async () => {
      const mockResponse = {
        data: {
          success: false,
          message: 'Device not found',
        },
      };
      vi.mocked(apiClient.delete).mockResolvedValue(mockResponse);

      const result = await revokeTrustedDevice(0);

      expect(result.success).toBe(false);
      expect(result.message).toContain('not found');
    });
  });

  describe('revokeAllTrustedDevices', () => {
    it('revokes all trusted devices', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'All devices revoked successfully',
          count: 5,
        },
      };
      vi.mocked(apiClient.delete).mockResolvedValue(mockResponse);

      const result = await revokeAllTrustedDevices();

      expect(apiClient.delete).toHaveBeenCalledWith('/auth/mfa/devices/revoke-all/');
      expect(result.success).toBe(true);
      expect(result.count).toBe(5);
      expect(result.message).toContain('All devices revoked');
    });

    it('returns zero count when no devices to revoke', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'No devices to revoke',
          count: 0,
        },
      };
      vi.mocked(apiClient.delete).mockResolvedValue(mockResponse);

      const result = await revokeAllTrustedDevices();

      expect(result.count).toBe(0);
    });

    it('includes count of revoked devices', async () => {
      const mockResponse = {
        data: {
          success: true,
          message: 'Devices revoked',
          count: 12,
        },
      };
      vi.mocked(apiClient.delete).mockResolvedValue(mockResponse);

      const result = await revokeAllTrustedDevices();

      expect(result.count).toBe(12);
      expect(result.success).toBe(true);
    });
  });
});
