import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';

// Mock profile API
vi.mock('../../api/profile', () => ({
  getProfile: vi.fn(),
  updateProfile: vi.fn(),
  uploadAvatar: vi.fn(),
  deleteAvatar: vi.fn(),
  sendVerificationEmail: vi.fn(),
  verifyEmail: vi.fn(),
  requestEmailChange: vi.fn(),
  confirmEmailChange: vi.fn(),
  changePassword: vi.fn(),
  setupTOTP: vi.fn(),
  verifyTOTP: vi.fn(),
  disableTOTP: vi.fn(),
  getRecoveryCodes: vi.fn(),
  regenerateRecoveryCodes: vi.fn(),
  sendPhoneVerification: vi.fn(),
  verifyPhoneCode: vi.fn(),
  getSessions: vi.fn(),
  revokeSession: vi.fn(),
  revokeOtherSessions: vi.fn(),
  getLoginHistory: vi.fn(),
  getUserEmails: vi.fn(),
  addUserEmail: vi.fn(),
  deleteUserEmail: vi.fn(),
  sendUserEmailVerification: vi.fn(),
  verifyUserEmail: vi.fn(),
  setPrimaryEmail: vi.fn(),
}));

import {
  useProfile,
  useUpdateProfile,
  useUploadAvatar,
  useDeleteAvatar,
  useSendVerificationEmail,
  useVerifyEmail,
  useRequestEmailChange,
  useConfirmEmailChange,
  useChangePassword,
  useSetupTOTP,
  useVerifyTOTP,
  useDisableTOTP,
  useRegenerateRecoveryCodes,
  useSendPhoneVerification,
  useVerifyPhoneCode,
  useSessions,
  useRevokeSession,
  useRevokeOtherSessions,
  useLoginHistory,
  useUserEmails,
  useAddUserEmail,
  useDeleteUserEmail,
  useSendUserEmailVerification,
  useVerifyUserEmail,
  useSetPrimaryEmail,
} from '../useProfile';
import * as profileApi from '../../api/profile';

// Create wrapper
const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  return function Wrapper({ children }: { children: React.ReactNode }) {
    return React.createElement(QueryClientProvider, { client: queryClient }, children);
  };
};

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

  describe('useProfile', () => {
    it('fetches user profile', async () => {
      const mockProfile = { id: 1, name: 'Test User', email: 'test@example.com' };
      vi.mocked(profileApi.getProfile).mockResolvedValue(mockProfile as any);

      const { result } = renderHook(() => useProfile(), {
        wrapper: createWrapper(),
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

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

  describe('useUpdateProfile', () => {
    it('updates profile', async () => {
      const mockUpdated = { id: 1, name: 'Updated' };
      vi.mocked(profileApi.updateProfile).mockResolvedValue(mockUpdated as any);

      const { result } = renderHook(() => useUpdateProfile(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync({ name: 'Updated' });
      });

      expect(profileApi.updateProfile).toHaveBeenCalled();
    });
  });

  describe('useUploadAvatar', () => {
    it('uploads avatar', async () => {
      vi.mocked(profileApi.uploadAvatar).mockResolvedValue({ avatar_url: 'url' });

      const { result } = renderHook(() => useUploadAvatar(), {
        wrapper: createWrapper(),
      });

      const file = new File(['test'], 'avatar.jpg');
      await act(async () => {
        await result.current.mutateAsync(file);
      });

      expect(profileApi.uploadAvatar).toHaveBeenCalled();
    });
  });

  describe('useDeleteAvatar', () => {
    it('deletes avatar', async () => {
      vi.mocked(profileApi.deleteAvatar).mockResolvedValue(undefined);

      const { result } = renderHook(() => useDeleteAvatar(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync();
      });

      expect(profileApi.deleteAvatar).toHaveBeenCalled();
    });
  });

  describe('email hooks', () => {
    it('sends verification email', async () => {
      vi.mocked(profileApi.sendVerificationEmail).mockResolvedValue(undefined);

      const { result } = renderHook(() => useSendVerificationEmail(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync();
      });

      expect(profileApi.sendVerificationEmail).toHaveBeenCalled();
    });

    it('verifies email', async () => {
      vi.mocked(profileApi.verifyEmail).mockResolvedValue(undefined);

      const { result } = renderHook(() => useVerifyEmail(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync('token');
      });

      expect(profileApi.verifyEmail).toHaveBeenCalled();
    });

    it('requests email change', async () => {
      vi.mocked(profileApi.requestEmailChange).mockResolvedValue(undefined);

      const { result } = renderHook(() => useRequestEmailChange(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync('new@example.com');
      });

      expect(profileApi.requestEmailChange).toHaveBeenCalled();
    });

    it('confirms email change', async () => {
      vi.mocked(profileApi.confirmEmailChange).mockResolvedValue(undefined);

      const { result } = renderHook(() => useConfirmEmailChange(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync('token');
      });

      expect(profileApi.confirmEmailChange).toHaveBeenCalled();
    });
  });

  describe('useChangePassword', () => {
    it('changes password', async () => {
      vi.mocked(profileApi.changePassword).mockResolvedValue(undefined);

      const { result } = renderHook(() => useChangePassword(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync({
          currentPassword: 'old',
          newPassword: 'new',
        });
      });

      expect(profileApi.changePassword).toHaveBeenCalled();
    });
  });

  describe('2FA hooks', () => {
    it('sets up TOTP', async () => {
      const mockSetup = { secret: 'ABC', qr_code: 'qr', provisioning_uri: 'uri' };
      vi.mocked(profileApi.setupTOTP).mockResolvedValue(mockSetup);

      const { result } = renderHook(() => useSetupTOTP(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        const data = await result.current.mutateAsync();
        expect(data).toEqual(mockSetup);
      });
    });

    it('verifies TOTP', async () => {
      vi.mocked(profileApi.verifyTOTP).mockResolvedValue({ success: true, recovery_codes: [] });

      const { result } = renderHook(() => useVerifyTOTP(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync('123456');
      });

      expect(profileApi.verifyTOTP).toHaveBeenCalled();
    });

    it('disables TOTP', async () => {
      vi.mocked(profileApi.disableTOTP).mockResolvedValue(undefined);

      const { result } = renderHook(() => useDisableTOTP(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync('123456');
      });

      expect(profileApi.disableTOTP).toHaveBeenCalled();
    });

    it('regenerates recovery codes', async () => {
      vi.mocked(profileApi.regenerateRecoveryCodes).mockResolvedValue(['code1', 'code2']);

      const { result } = renderHook(() => useRegenerateRecoveryCodes(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        const codes = await result.current.mutateAsync();
        expect(codes).toEqual(['code1', 'code2']);
      });
    });
  });

  describe('phone hooks', () => {
    it('sends phone verification', async () => {
      vi.mocked(profileApi.sendPhoneVerification).mockResolvedValue(undefined);

      const { result } = renderHook(() => useSendPhoneVerification(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync('555-1234');
      });

      expect(profileApi.sendPhoneVerification).toHaveBeenCalled();
    });

    it('verifies phone code', async () => {
      vi.mocked(profileApi.verifyPhoneCode).mockResolvedValue(undefined);

      const { result } = renderHook(() => useVerifyPhoneCode(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync('123456');
      });

      expect(profileApi.verifyPhoneCode).toHaveBeenCalled();
    });
  });

  describe('session hooks', () => {
    it('fetches sessions', async () => {
      const mockSessions = [{ id: '1', device_info: 'Chrome' }];
      vi.mocked(profileApi.getSessions).mockResolvedValue(mockSessions as any);

      const { result } = renderHook(() => useSessions(), {
        wrapper: createWrapper(),
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(result.current.data).toEqual(mockSessions);
    });

    it('revokes session', async () => {
      vi.mocked(profileApi.revokeSession).mockResolvedValue(undefined);

      const { result } = renderHook(() => useRevokeSession(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync('session-id');
      });

      expect(profileApi.revokeSession).toHaveBeenCalled();
    });

    it('revokes other sessions', async () => {
      vi.mocked(profileApi.revokeOtherSessions).mockResolvedValue(undefined);

      const { result } = renderHook(() => useRevokeOtherSessions(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync();
      });

      expect(profileApi.revokeOtherSessions).toHaveBeenCalled();
    });

    it('fetches login history', async () => {
      const mockHistory = [{ id: '1', success: true }];
      vi.mocked(profileApi.getLoginHistory).mockResolvedValue(mockHistory as any);

      const { result } = renderHook(() => useLoginHistory(), {
        wrapper: createWrapper(),
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(result.current.data).toEqual(mockHistory);
    });
  });

  describe('multiple email hooks', () => {
    it('fetches user emails', async () => {
      const mockEmails = [{ id: 1, email: 'test@example.com' }];
      vi.mocked(profileApi.getUserEmails).mockResolvedValue(mockEmails as any);

      const { result } = renderHook(() => useUserEmails(), {
        wrapper: createWrapper(),
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(result.current.data).toEqual(mockEmails);
    });

    it('adds user email', async () => {
      vi.mocked(profileApi.addUserEmail).mockResolvedValue({ id: 2, email: 'new@example.com' } as any);

      const { result } = renderHook(() => useAddUserEmail(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync('new@example.com');
      });

      expect(profileApi.addUserEmail).toHaveBeenCalled();
    });

    it('deletes user email', async () => {
      vi.mocked(profileApi.deleteUserEmail).mockResolvedValue(undefined);

      const { result } = renderHook(() => useDeleteUserEmail(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync(2);
      });

      expect(profileApi.deleteUserEmail).toHaveBeenCalled();
    });

    it('sends user email verification', async () => {
      vi.mocked(profileApi.sendUserEmailVerification).mockResolvedValue(undefined);

      const { result } = renderHook(() => useSendUserEmailVerification(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync(2);
      });

      expect(profileApi.sendUserEmailVerification).toHaveBeenCalled();
    });

    it('verifies user email', async () => {
      vi.mocked(profileApi.verifyUserEmail).mockResolvedValue(undefined);

      const { result } = renderHook(() => useVerifyUserEmail(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync({ emailId: 2, token: 'token' });
      });

      expect(profileApi.verifyUserEmail).toHaveBeenCalled();
    });

    it('sets primary email', async () => {
      vi.mocked(profileApi.setPrimaryEmail).mockResolvedValue(undefined);

      const { result } = renderHook(() => useSetPrimaryEmail(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync(2);
      });

      expect(profileApi.setPrimaryEmail).toHaveBeenCalled();
    });
  });
});
