// src/pages/UserProfilePage.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import UserProfilePage from './UserProfilePage';
import * as apiClient from '../services/apiClient';
import { UserProfile, Achievement, UserAchievement } from '../types';
import {
  createMockUserProfile,
  createMockUserAchievement,
  createMockUser,
} from '../tests/utils/mockFactories';
import { QueryWrapper } from '../tests/utils/renderWithProviders';
import { useUserProfileData } from '../hooks/useUserProfileData';

// Must explicitly call vi.mock() for apiClient
vi.mock('../services/apiClient');
vi.mock('../hooks/useUserProfileData');

const renderWithQuery = (ui: React.ReactElement) => render(ui, { wrapper: QueryWrapper });

vi.mock('../services/notificationService', () => ({
  notifySuccess: vi.fn(),
  notifyError: vi.fn(),
}));
import { notifyError } from '../services/notificationService';

vi.mock('../components/AchievementsList', () => ({
  AchievementsList: ({
    achievements,
  }: {
    achievements: (UserAchievement & Achievement)[] | null;
  }) => (
    <div data-testid="achievements-list-mock">Achievements Count: {achievements?.length || 0}</div>
  ),
}));

const mockedApiClient = vi.mocked(apiClient);
const mockedUseUserProfileData = vi.mocked(useUserProfileData);
const mockedNotifyError = vi.mocked(notifyError);

// --- Mock Data ---
const mockProfile: UserProfile = createMockUserProfile({
  user: createMockUser({ user_id: 'user-123', email: 'test@example.com' }),
  full_name: 'Test User',
  avatar_url: 'https://example.com/avatar.jpg',
  points: 150,
  role: 'user',
});

const mockAchievements: (UserAchievement & Achievement)[] = [
  createMockUserAchievement({
    achievement_id: 1,
    user_id: 'user-123',
    achieved_at: '2024-01-01T00:00:00Z',
    name: 'First Steps',
    description: 'Uploaded first flyer.',
    icon: 'upload',
    points_value: 10,
  }),
];

const mockSetProfile = vi.fn();

describe('UserProfilePage', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Default mock implementation: Success state
    mockedUseUserProfileData.mockReturnValue({
      profile: mockProfile,
      setProfile: mockSetProfile,
      achievements: mockAchievements,
      isLoading: false,
      error: null,
    });
  });

  it('should display a loading message initially', () => {
    mockedUseUserProfileData.mockReturnValue({
      profile: null,
      setProfile: mockSetProfile,
      achievements: [],
      isLoading: true,
      error: null,
    });
    renderWithQuery(<UserProfilePage />);
    expect(screen.getByText('Loading profile...')).toBeInTheDocument();
  });

  it('should display an error message if fetching profile fails', () => {
    mockedUseUserProfileData.mockReturnValue({
      profile: null,
      setProfile: mockSetProfile,
      achievements: [],
      isLoading: false,
      error: 'Network Error',
    });
    renderWithQuery(<UserProfilePage />);
    expect(screen.getByText('Error: Network Error')).toBeInTheDocument();
  });

  it('should render the profile and achievements on successful fetch', () => {
    renderWithQuery(<UserProfilePage />);
    expect(screen.getByRole('heading', { name: 'Test User' })).toBeInTheDocument();
    expect(screen.getByText('test@example.com')).toBeInTheDocument();
    expect(screen.getByText('150 Points')).toBeInTheDocument();
    expect(screen.getByAltText('User Avatar')).toHaveAttribute('src', mockProfile.avatar_url);
    expect(screen.getByTestId('achievements-list-mock')).toHaveTextContent('Achievements Count: 1');
  });

  it('should render a fallback message if profile is null after loading', () => {
    mockedUseUserProfileData.mockReturnValue({
      profile: null,
      setProfile: mockSetProfile,
      achievements: [],
      isLoading: false,
      error: null,
    });
    renderWithQuery(<UserProfilePage />);
    expect(screen.getByText('Could not load user profile.')).toBeInTheDocument();
  });

  it('should display a fallback avatar if the user has no avatar_url', () => {
    const profileWithoutAvatar = { ...mockProfile, avatar_url: null, full_name: 'No Avatar User' };
    mockedUseUserProfileData.mockReturnValue({
      profile: profileWithoutAvatar,
      setProfile: mockSetProfile,
      achievements: [],
      isLoading: false,
      error: null,
    });

    renderWithQuery(<UserProfilePage />);

    const avatarImage = screen.getByAltText('User Avatar');
    const expectedSrc = 'https://api.dicebear.com/8.x/initials/svg?seed=No Avatar User';
    expect(avatarImage).toHaveAttribute('src', expectedSrc);
  });

  it('should use email for avatar seed if full_name is missing', () => {
    const profileNoName = { ...mockProfile, full_name: null, avatar_url: null };
    mockedUseUserProfileData.mockReturnValue({
      profile: profileNoName,
      setProfile: mockSetProfile,
      achievements: [],
      isLoading: false,
      error: null,
    });

    renderWithQuery(<UserProfilePage />);

    const avatar = screen.getByAltText('User Avatar');
    expect(avatar.getAttribute('src')).toContain(`seed=${profileNoName.user.email}`);
  });

  it('should trigger file input click when avatar is clicked', () => {
    renderWithQuery(<UserProfilePage />);
    const fileInput = screen.getByTestId('avatar-file-input');
    const clickSpy = vi.spyOn(fileInput, 'click');
    const avatarContainer = screen.getByAltText('User Avatar');
    fireEvent.click(avatarContainer);
    expect(clickSpy).toHaveBeenCalled();
  });

  describe('Name Editing', () => {
    it('should allow editing and saving the user name', async () => {
      const updatedProfile = { ...mockProfile, full_name: 'Updated Name' };
      mockedApiClient.updateUserProfile.mockResolvedValue(
        new Response(JSON.stringify(updatedProfile)),
      );
      renderWithQuery(<UserProfilePage />);

      fireEvent.click(screen.getByRole('button', { name: /edit/i }));
      const nameInput = screen.getByRole('textbox');
      fireEvent.change(nameInput, { target: { value: 'Updated Name' } });
      fireEvent.click(screen.getByRole('button', { name: /save/i }));

      await waitFor(() => {
        expect(mockedApiClient.updateUserProfile).toHaveBeenCalledWith({
          full_name: 'Updated Name',
        });
        expect(mockSetProfile).toHaveBeenCalled();
      });
    });

    it('should allow canceling the name edit', () => {
      renderWithQuery(<UserProfilePage />);
      fireEvent.click(screen.getByRole('button', { name: /edit/i }));
      fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
      expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
      expect(screen.getByRole('heading', { name: 'Test User' })).toBeInTheDocument();
    });

    it('should show an error if saving the name fails with a non-ok response', async () => {
      mockedApiClient.updateUserProfile.mockResolvedValue(
        new Response(JSON.stringify({ message: 'Validation failed' }), { status: 400 }),
      );
      renderWithQuery(<UserProfilePage />);

      fireEvent.click(screen.getByRole('button', { name: /edit/i }));
      const nameInput = screen.getByRole('textbox');
      fireEvent.change(nameInput, { target: { value: 'Invalid Name' } });
      fireEvent.click(screen.getByRole('button', { name: /save/i }));

      await waitFor(() => {
        expect(mockedNotifyError).toHaveBeenCalledWith('Validation failed');
      });
    });
  });

  describe('Avatar Upload', () => {
    it('should upload a new avatar and update the profile', async () => {
      const updatedProfile = { ...mockProfile, avatar_url: 'https://example.com/new-avatar.png' };
      mockedApiClient.uploadAvatar.mockResolvedValue(new Response(JSON.stringify(updatedProfile)));

      renderWithQuery(<UserProfilePage />);

      const fileInput = screen.getByTestId('avatar-file-input');
      const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' });

      fireEvent.change(fileInput, { target: { files: [file] } });

      await waitFor(() => {
        expect(mockedApiClient.uploadAvatar).toHaveBeenCalledWith(file);
        expect(mockSetProfile).toHaveBeenCalled();
      });
    });

    it('should not attempt to upload if no file is selected', () => {
      renderWithQuery(<UserProfilePage />);
      const fileInput = screen.getByTestId('avatar-file-input');
      fireEvent.change(fileInput, { target: { files: null } });
      expect(mockedApiClient.uploadAvatar).not.toHaveBeenCalled();
    });

    it('should show an error if avatar upload returns a non-ok response', async () => {
      mockedApiClient.uploadAvatar.mockResolvedValue(
        new Response(JSON.stringify({ message: 'File too large' }), { status: 413 }),
      );
      renderWithQuery(<UserProfilePage />);

      const fileInput = screen.getByTestId('avatar-file-input');
      const file = new File(['(⌐□_□)'], 'large.png', { type: 'image/png' });
      fireEvent.change(fileInput, { target: { files: [file] } });

      await waitFor(() => {
        expect(mockedNotifyError).toHaveBeenCalledWith('File too large');
      });
    });
  });
});
