import { mount, flushPromises } from '@vue/test-utils';
import AccountPage from '../AccountPage.vue'; // Adjust path
import { apiClient, API_ENDPOINTS as MOCK_API_ENDPOINTS } from '@/config/api';
import { useNotificationStore } from '@/stores/notifications';
import { vi } from 'vitest';

// --- Mocks ---
vi.mock('@/config/api', () => ({
  apiClient: {
    get: vi.fn(),
    put: vi.fn(),
  },
  API_ENDPOINTS: { // Provide the structure used by the component
    USERS: {
      PROFILE: '/users/me/',
      UPDATE_PROFILE: '/users/me/profile/', // Assuming, adjust if different
      PASSWORD: '/users/me/password/',
      PREFERENCES: '/users/me/preferences/',
    },
  },
}));

vi.mock('@/stores/notifications', () => ({
  useNotificationStore: vi.fn(() => ({
    addNotification: vi.fn(),
  })),
}));

const mockApiClient = apiClient as vi.Mocked<typeof apiClient>;
let mockNotificationStore: ReturnType<typeof useNotificationStore>;

// Helper function to create a wrapper
const createWrapper = (props = {}) => {
  return mount(AccountPage, {
    props,
    global: {
      stubs: {
        // Stubbing router-link and router-view if they were used, though not directly visible in AccountPage.vue
        // 'router-link': true,
        // 'router-view': true,
      }
    }
  });
};

describe('AccountPage.vue', () => {
  const mockProfileData = {
    name: 'Test User',
    email: 'test@example.com',
  };
  const mockPreferencesData = {
    emailNotifications: true,
    listUpdates: false,
    groupActivities: true,
  };

  beforeEach(() => {
    vi.clearAllMocks();
    // Default successful profile fetch
    mockApiClient.get.mockImplementation(async (url) => {
      if (url === MOCK_API_ENDPOINTS.USERS.PROFILE) {
        return { data: { ...mockProfileData, preferences: mockPreferencesData } };
      }
      return { data: {} };
    });
    mockNotificationStore = useNotificationStore() as vi.Mocked<ReturnType<typeof useNotificationStore>>;
    (useNotificationStore as vi.Mock).mockReturnValue(mockNotificationStore);
  });

  describe('Rendering and Initial Data Fetching', () => {
    it('renders loading state initially', async () => {
      mockApiClient.get.mockImplementationOnce(() => new Promise(() => {})); // Keep it pending
      const wrapper = createWrapper();
      expect(wrapper.text()).toContain('Loading profile...');
      expect(wrapper.find('.spinner-dots').exists()).toBe(true);
    });

    it('fetches and displays profile information on mount', async () => {
      const wrapper = createWrapper();
      await flushPromises(); // Wait for onMounted and fetchProfile

      expect(mockApiClient.get).toHaveBeenCalledWith(MOCK_API_ENDPOINTS.USERS.PROFILE);
      const nameInput = wrapper.find<HTMLInputElement>('#profileName');
      const emailInput = wrapper.find<HTMLInputElement>('#profileEmail');
      expect(nameInput.element.value).toBe(mockProfileData.name);
      expect(emailInput.element.value).toBe(mockProfileData.email);
      expect(emailInput.attributes('readonly')).toBeDefined();
    });

    it('displays error message if profile fetch fails and allows retry', async () => {
      const fetchError = new Error('Network Error');
      mockApiClient.get.mockRejectedValueOnce(fetchError);
      const wrapper = createWrapper();
      await flushPromises();

      expect(wrapper.text()).toContain('Network Error'); // Error message displayed
      expect(mockNotificationStore.addNotification).toHaveBeenCalledWith({ message: 'Network Error', type: 'error' });

      // Test retry
      mockApiClient.get.mockResolvedValueOnce({ data: mockProfileData }); // Setup success for retry
      await wrapper.find('.btn-danger').trigger('click'); // Click retry
      await flushPromises();

      expect(mockApiClient.get).toHaveBeenCalledTimes(2); // Original + retry
      const nameInput = wrapper.find<HTMLInputElement>('#profileName');
      expect(nameInput.element.value).toBe(mockProfileData.name);
      expect(wrapper.find('.alert-error').exists()).toBe(false); // Error message gone
    });
  });

  describe('Profile Information Form', () => {
    it('updates profile successfully', async () => {
      const wrapper = createWrapper();
      await flushPromises(); // Initial load

      const newName = 'Updated Test User';
      await wrapper.find('#profileName').setValue(newName);
      mockApiClient.put.mockResolvedValueOnce({ data: {} }); // Mock successful update

      await wrapper.find('form').trigger('submit.prevent'); // Assuming first form is profile
      await flushPromises();

      expect(mockApiClient.put).toHaveBeenCalledWith(
        MOCK_API_ENDPOINTS.USERS.UPDATE_PROFILE,
        { name: newName, email: mockProfileData.email } // email is readonly, should retain original
      );
      expect(mockNotificationStore.addNotification).toHaveBeenCalledWith({ message: 'Profile updated successfully', type: 'success' });
    });

    it('handles profile update failure', async () => {
        const wrapper = createWrapper();
        await flushPromises();
  
        mockApiClient.put.mockRejectedValueOnce(new Error('Update failed'));
        await wrapper.find('form').trigger('submit.prevent');
        await flushPromises();
  
        expect(mockNotificationStore.addNotification).toHaveBeenCalledWith({ message: 'Update failed', type: 'error' });
      });
  });

  describe('Change Password Form', () => {
    let wrapper: ReturnType<typeof createWrapper>;
    beforeEach(async () => {
        wrapper = createWrapper();
        await flushPromises(); // Initial load
      });

    it('changes password successfully', async () => {
      await wrapper.find('#currentPassword').setValue('currentPass123');
      await wrapper.find('#newPassword').setValue('newPassword123');
      mockApiClient.put.mockResolvedValueOnce({ data: {} }); // Mock successful password change

      // Find the second form for password change
      const forms = wrapper.findAll('form');
      await forms[1].trigger('submit.prevent');
      await flushPromises();

      expect(mockApiClient.put).toHaveBeenCalledWith(
        MOCK_API_ENDPOINTS.USERS.PASSWORD,
        { current: 'currentPass123', new: 'newPassword123' }
      );
      expect(mockNotificationStore.addNotification).toHaveBeenCalledWith({ message: 'Password changed successfully', type: 'success' });
      expect(wrapper.find<HTMLInputElement>('#currentPassword').element.value).toBe('');
      expect(wrapper.find<HTMLInputElement>('#newPassword').element.value).toBe('');
    });
    
    it('shows validation error if new password is too short', async () => {
        await wrapper.find('#currentPassword').setValue('currentPass123');
        await wrapper.find('#newPassword').setValue('short');
  
        const forms = wrapper.findAll('form');
        await forms[1].trigger('submit.prevent');
        await flushPromises();
  
        expect(mockApiClient.put).not.toHaveBeenCalled();
        expect(mockNotificationStore.addNotification).toHaveBeenCalledWith({
          message: 'New password must be at least 8 characters long.', type: 'warning'
        });
      });

      it('shows validation error if fields are empty', async () => {
        const forms = wrapper.findAll('form');
        await forms[1].trigger('submit.prevent'); // Submit with empty fields
        await flushPromises();
  
        expect(mockApiClient.put).not.toHaveBeenCalled();
        expect(mockNotificationStore.addNotification).toHaveBeenCalledWith({
          message: 'Please fill in both current and new password fields.', type: 'warning'
        });
      });

    it('handles password change failure', async () => {
        await wrapper.find('#currentPassword').setValue('currentPass123');
        await wrapper.find('#newPassword').setValue('newPasswordSecure');
        mockApiClient.put.mockRejectedValueOnce(new Error('Password change failed'));
  
        const forms = wrapper.findAll('form');
        await forms[1].trigger('submit.prevent');
        await flushPromises();
  
        expect(mockNotificationStore.addNotification).toHaveBeenCalledWith({ message: 'Password change failed', type: 'error' });
      });
  });

  describe('Notification Preferences', () => {
    it('updates preferences successfully when a toggle is changed', async () => {
        const wrapper = createWrapper();
        await flushPromises(); // Initial load
  
        const emailNotificationsToggle = wrapper.findAll<HTMLInputElement>('input[type="checkbox"]')[0];
        const initialEmailPref = mockPreferencesData.emailNotifications;
        expect(emailNotificationsToggle.element.checked).toBe(initialEmailPref);
  
        mockApiClient.put.mockResolvedValueOnce({ data: {} }); // Mock successful preference update
  
        await emailNotificationsToggle.setValue(!initialEmailPref); // This also triggers @change
        await flushPromises();
  
        const expectedPreferences = { ...mockPreferencesData, emailNotifications: !initialEmailPref };
        expect(mockApiClient.put).toHaveBeenCalledWith(
          MOCK_API_ENDPOINTS.USERS.PREFERENCES,
          expectedPreferences
        );
        expect(mockNotificationStore.addNotification).toHaveBeenCalledWith({ message: 'Preferences updated successfully', type: 'success' });
      });

      it('handles preference update failure', async () => {
        const wrapper = createWrapper();
        await flushPromises();
  
        const listUpdatesToggle = wrapper.findAll<HTMLInputElement>('input[type="checkbox"]')[1];
        const initialListPref = mockPreferencesData.listUpdates;
  
        mockApiClient.put.mockRejectedValueOnce(new Error('Pref update failed'));
  
        await listUpdatesToggle.setValue(!initialListPref);
        await flushPromises();
  
        expect(mockNotificationStore.addNotification).toHaveBeenCalledWith({ message: 'Pref update failed', type: 'error' });
        // Optionally, test if the toggle reverts, though the current component code doesn't explicitly do that on error.
      });
  });
});
