// src/App.test.tsx
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
import { MemoryRouter, Outlet } from 'react-router-dom';
import App from './App';
import * as apiClient from './services/apiClient';

// Mock useAuth to allow overriding the user state in tests
const mockUseAuth = vi.fn();
vi.mock('./hooks/useAuth', () => ({
  useAuth: () => mockUseAuth(),
}));

// Mock child components to isolate the App component
vi.mock('./features/flyer/FlyerDisplay', () => ({ FlyerDisplay: () => <div data-testid="flyer-display-mock">Flyer Display</div> }));
vi.mock('./features/flyer/ExtractedDataTable', () => ({ ExtractedDataTable: () => <div data-testid="extracted-data-table-mock">Extracted Data Table</div> }));
vi.mock('./features/flyer/AnalysisPanel', () => ({ AnalysisPanel: () => <div data-testid="analysis-panel-mock">Analysis Panel</div> }));
vi.mock('./features/charts/PriceChart', () => ({ PriceChart: () => <div data-testid="price-chart-mock">Price Chart</div> }));
vi.mock('./components/Header', () => ({ Header: () => <header data-testid="header-mock">Header</header> }));
vi.mock('./features/flyer/BulkImporter', () => ({ BulkImporter: () => <div data-testid="bulk-importer-mock">Bulk Importer</div> }));
vi.mock('./features/charts/PriceHistoryChart', () => ({ PriceHistoryChart: () => <div data-testid="price-history-chart-mock">Price History Chart</div> }));
vi.mock('./features/flyer/FlyerList', () => ({ FlyerList: () => <div data-testid="flyer-list-mock">Flyer List</div> }));
vi.mock('./features/flyer/ProcessingStatus', () => ({ ProcessingStatus: () => <div data-testid="processing-status-mock">Processing Status</div> }));
vi.mock('./features/flyer/BulkImportSummary', () => ({ BulkImportSummary: () => <div data-testid="bulk-import-summary-mock">Bulk Import Summary</div> }));
vi.mock('./pages/admin/components/ProfileManager', () => ({ ProfileManager: () => <div data-testid="profile-manager-mock">Profile Manager</div> }));
vi.mock('./features/shopping/ShoppingList', () => ({ ShoppingListComponent: () => <div data-testid="shopping-list-mock">Shopping List</div> }));
vi.mock('./features/voice-assistant/VoiceAssistant', () => ({ VoiceAssistant: () => <div data-testid="voice-assistant-mock">Voice Assistant</div> }));
vi.mock('./pages/admin/AdminPage', () => ({ AdminPage: () => <div data-testid="admin-page-mock">Admin Page</div> }));
// In react-router v6, wrapper routes must render an <Outlet /> for nested routes to appear.
// Our previous mock was trying to render `{children}`, which is incorrect for this pattern.
// This new mock correctly simulates the behavior of the actual AdminRoute component.
vi.mock('./components/AdminRoute', () => ({ 
  AdminRoute: ({ children }: { children?: React.ReactNode }) => (
    <div data-testid="admin-route-mock">
      {children}
      <Outlet />
    </div>
  ) 
}));
vi.mock('./pages/admin/CorrectionsPage', () => ({ CorrectionsPage: () => <div data-testid="corrections-page-mock">Corrections Page</div> }));
vi.mock('./pages/admin/ActivityLog', () => ({ ActivityLog: () => <div data-testid="activity-log-mock">Activity Log</div> }));
vi.mock('./features/shopping/WatchedItemsList', () => ({ WatchedItemsList: () => <div data-testid="watched-items-list-mock">Watched Items List</div> }));
vi.mock('./pages/admin/AdminStatsPage', () => ({ AdminStatsPage: () => <div data-testid="admin-stats-page-mock">Admin Stats Page</div> }));
vi.mock('./pages/ResetPasswordPage', () => ({ ResetPasswordPage: () => <div data-testid="reset-password-page-mock">Reset Password Page</div> }));
vi.mock('./pages/admin/components/AnonymousUserBanner', () => ({ AnonymousUserBanner: () => <div data-testid="anonymous-user-banner-mock">Anonymous User Banner</div> }));
vi.mock('./pages/VoiceLabPage', () => ({ VoiceLabPage: () => <div data-testid="voice-lab-page-mock">Voice Lab Page</div> }));
vi.mock('./components/WhatsNewModal', () => ({ WhatsNewModal: () => <div data-testid="whats-new-modal-mock">What's New Modal</div> }));
// Mock pdfjs-dist to prevent the "DOMMatrix is not defined" error in JSDOM.
// This must be done in any test file that imports App.tsx.
vi.mock('pdfjs-dist', () => ({
  GlobalWorkerOptions: { workerSrc: '' },
  getDocument: vi.fn(() => ({
    promise: Promise.resolve({ getPage: vi.fn() }),
  })),
}));

// By casting the apiClient to `Mocked<typeof apiClient>`, we get type-safe access 
// to Vitest's mock functions like `mockResolvedValue`.
// The `Mocked` type is imported directly from 'vitest' to avoid the namespace
// collision that occurs when using `vi.Mocked` with an imported `vi` object.
const mockedApiClient = apiClient as Mocked<typeof apiClient>;

describe('App Component', () => {
    beforeEach(() => {
        vi.clearAllMocks();
        // Default auth state: loading or guest
        mockUseAuth.mockReturnValue({
            user: null,
            profile: null,
            authStatus: 'Determining...',
            isLoading: true,
        });

        // Clear local storage to prevent auth state from leaking between tests.
        localStorage.clear();
        // Default mocks for API calls
        // Use mockImplementation to create a new Response object for each call,
        // preventing "Body has already been read" errors.
        // Use mockImplementation to create a new Response object for each call,
        // preventing "Body has already been read" errors.
        mockedApiClient.fetchFlyers.mockImplementation(() => Promise.resolve(new Response(JSON.stringify([]))));
        mockedApiClient.fetchMasterItems.mockImplementation(() => Promise.resolve(new Response(JSON.stringify([]))));
        mockedApiClient.fetchWatchedItems.mockImplementation(() => Promise.resolve(new Response(JSON.stringify([]))));
        mockedApiClient.fetchShoppingLists.mockImplementation(() => Promise.resolve(new Response(JSON.stringify([]))));
        mockedApiClient.getAuthenticatedUserProfile.mockRejectedValue(new Error('Not authenticated'));
    });

    const renderApp = (initialEntries = ['/']) => {
        return render(
            <MemoryRouter initialEntries={initialEntries}>
                <App />
            </MemoryRouter>
        );
    };

    it('should render the main layout and header', async () => {
        renderApp();
        await waitFor(() => {
            expect(screen.getByTestId('header-mock')).toBeInTheDocument();
            expect(screen.getByTestId('flyer-list-mock')).toBeInTheDocument();
            // The BulkImporter is not rendered for anonymous users.
            expect(screen.queryByTestId('bulk-importer-mock')).not.toBeInTheDocument();
        });
    });

    it('should render the BulkImporter for an admin user', async () => {
        // FIX 4: Update the BulkImporter test case to simulate an admin user via useAuth mock
        const mockAdminProfile = {
            user_id: 'admin-id',
            user: { user_id: 'admin-id', email: 'admin@example.com' },
            role: 'admin',
            full_name: 'Admin',
            avatar_url: '',
        };
        
        // Force the auth hook to return an authenticated admin user
        mockUseAuth.mockReturnValue({
            user: mockAdminProfile.user,
            profile: mockAdminProfile,
            authStatus: 'AUTHENTICATED',
            isLoading: false,
        });

        renderApp();

        // Wait for the header (which means app loaded) AND the bulk importer
        await waitFor(() => {
            expect(screen.getByTestId('header-mock')).toBeInTheDocument();
            expect(screen.getByTestId('bulk-importer-mock')).toBeInTheDocument();
        });
    });

    it('should show a welcome message when no flyer is selected', async () => {
        renderApp();
        await waitFor(() => {
            expect(screen.getByText(/welcome to flyer crawler/i)).toBeInTheDocument();
        });
    });

    it('should render the admin page on the /admin route', async () => {
        // Mock a logged-in admin user
        const mockAdminProfile = {
            // The Profile type requires user_id at the top level, in addition
            // to the nested user object.
            user_id: 'admin-id',
            user: { user_id: 'admin-id', email: 'admin@example.com' },
            role: 'admin',
            full_name: 'Admin',
            avatar_url: '',
        };
        mockedApiClient.getAuthenticatedUserProfile.mockResolvedValue(new Response(JSON.stringify(mockAdminProfile)));

        // The app's auth hook checks for a token before fetching the user profile.
        // We need to mock it to allow the authentication flow to proceed.
        localStorage.setItem('authToken', 'fake-admin-token');

        renderApp(['/admin']);

        // Use findByTestId to handle the asynchronous rendering of the page
        // after the user profile has been fetched and the auth state has been updated.
        expect(await screen.findByTestId('admin-page-mock')).toBeInTheDocument();
    });

    it('should render the reset password page on the correct route', async () => {
        renderApp(['/reset-password/some-token']);

        await waitFor(() => {
            expect(screen.getByTestId('reset-password-page-mock')).toBeInTheDocument();
        });
    });
});