// src/pages/admin/AdminStatsPage.test.tsx
import React from 'react';
import { render, screen, waitFor, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import { AdminStatsPage } from './AdminStatsPage';
import * as apiClient from '../../services/apiClient';
import type { AppStats } from '../../services/apiClient';
import { createMockAppStats } from '../../tests/utils/mockFactories';
import { StatCard } from './components/StatCard';

// The apiClient and logger are now mocked globally via src/tests/setup/tests-setup-unit.ts.
const mockedApiClient = vi.mocked(apiClient);

// Mock the child StatCard component to use the shared mock and allow spying
vi.mock('./components/StatCard', async () => {
  const { MockStatCard } = await import('../../tests/utils/componentMocks');
  return { StatCard: vi.fn(MockStatCard) };
});

// Get a reference to the mocked component
const mockedStatCard = StatCard as Mock;

// Helper function to render the component within a router context, as it contains a <Link>
const renderWithRouter = () => {
  return render(
    <MemoryRouter>
      <AdminStatsPage />
    </MemoryRouter>,
  );
};

describe('AdminStatsPage', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockedStatCard.mockClear();
  });

  it('should render a loading spinner while fetching stats', async () => {
    let resolvePromise: (value: Response) => void;
    const mockPromise = new Promise<Response>((resolve) => {
      resolvePromise = resolve;
    });
    // Cast to any to bypass strict type checking for the mock return value vs Promise
    mockedApiClient.getApplicationStats.mockReturnValue(mockPromise as any);

    renderWithRouter();

    expect(screen.getByRole('status', { name: /loading/i })).toBeInTheDocument();

    await act(async () => {
      resolvePromise!(
        new Response(
          JSON.stringify(
            createMockAppStats({
              userCount: 0,
              flyerCount: 0,
              flyerItemCount: 0,
              storeCount: 0,
              pendingCorrectionCount: 0,
              recipeCount: 0,
            }),
          ),
        ),
      );
    });
  });

  it('should display stats cards when data is fetched successfully', async () => {
    const mockStats: AppStats = createMockAppStats({
      userCount: 123,
      flyerCount: 456,
      flyerItemCount: 7890,
      storeCount: 42,
      pendingCorrectionCount: 5,
      recipeCount: 150,
    });
    mockedApiClient.getApplicationStats.mockResolvedValue(new Response(JSON.stringify(mockStats)));
    renderWithRouter();

    // Wait for the stats to be displayed
    await waitFor(() => {
      expect(screen.getByText('Total Users')).toBeInTheDocument();
      expect(screen.getByText('123')).toBeInTheDocument();

      expect(screen.getByText('Flyers Processed')).toBeInTheDocument();
      expect(screen.getByText('456')).toBeInTheDocument();

      expect(screen.getByText('Total Flyer Items')).toBeInTheDocument();
      expect(screen.getByText('7,890')).toBeInTheDocument(); // Note: toLocaleString() adds a comma

      expect(screen.getByText('Stores Tracked')).toBeInTheDocument();
      expect(screen.getByText('42')).toBeInTheDocument();

      expect(screen.getByText('Pending Corrections')).toBeInTheDocument();
      expect(screen.getByText('5')).toBeInTheDocument();

      expect(screen.getByText('Total Recipes')).toBeInTheDocument();
      expect(screen.getByText('150')).toBeInTheDocument();
    });
  });

  it('should pass the correct props to each StatCard component', async () => {
    const mockStats: AppStats = createMockAppStats({
      userCount: 123,
      flyerCount: 456,
      flyerItemCount: 7890,
      storeCount: 42,
      pendingCorrectionCount: 5,
      recipeCount: 150,
    });
    mockedApiClient.getApplicationStats.mockResolvedValue(new Response(JSON.stringify(mockStats)));

    renderWithRouter();

    await waitFor(() => {
      // Wait for the component to have been called at least once
      expect(mockedStatCard).toHaveBeenCalled();
    });

    // Verify it was called 5 times, once for each stat
    expect(mockedStatCard).toHaveBeenCalledTimes(6);

    // Check props for each card individually for robustness
    expect(mockedStatCard).toHaveBeenCalledWith(
      expect.objectContaining({
        title: 'Total Users',
        value: '123',
      }),
      undefined,
    );
    expect(mockedStatCard).toHaveBeenCalledWith(
      expect.objectContaining({
        title: 'Flyers Processed',
        value: '456',
      }),
      undefined,
    );
    expect(mockedStatCard).toHaveBeenCalledWith(
      expect.objectContaining({
        title: 'Total Flyer Items',
        value: '7,890',
      }),
      undefined,
    );
    expect(mockedStatCard).toHaveBeenCalledWith(
      expect.objectContaining({
        title: 'Stores Tracked',
        value: '42',
      }),
      undefined,
    );
    expect(mockedStatCard).toHaveBeenCalledWith(
      expect.objectContaining({
        title: 'Pending Corrections',
        value: '5',
      }),
      undefined,
    );
    expect(mockedStatCard).toHaveBeenCalledWith(
      expect.objectContaining({
        title: 'Total Recipes',
        value: '150',
      }),
      undefined,
    );
  });

  it('should format large numbers with commas for readability', async () => {
    const mockStats: AppStats = createMockAppStats({
      userCount: 1234567,
      flyerCount: 9876,
      flyerItemCount: 123456789,
      recipeCount: 50000,
    });
    mockedApiClient.getApplicationStats.mockResolvedValue(new Response(JSON.stringify(mockStats)));
    renderWithRouter();

    await waitFor(() => {
      expect(screen.getByText('1,234,567')).toBeInTheDocument();
      expect(screen.getByText('9,876')).toBeInTheDocument();
      expect(screen.getByText('123,456,789')).toBeInTheDocument();
      expect(screen.getByText('50,000')).toBeInTheDocument();
    });
  });

  it('should correctly display zero values for all stats', async () => {
    const mockZeroStats: AppStats = createMockAppStats({
      userCount: 0,
      flyerCount: 0,
      flyerItemCount: 0,
      storeCount: 0,
      pendingCorrectionCount: 0,
      recipeCount: 0,
    });
    mockedApiClient.getApplicationStats.mockResolvedValue(
      new Response(JSON.stringify(mockZeroStats)),
    );
    renderWithRouter();

    await waitFor(() => {
      // `getAllByText` will find all instances of '0'. There should be 5.
      const zeroValueElements = screen.getAllByText('0');
      expect(zeroValueElements).toHaveLength(6);

      // Also check that the titles are present to be sure we have the cards.
      expect(screen.getByText('Total Users')).toBeInTheDocument();
      expect(screen.getByText('Pending Corrections')).toBeInTheDocument();
    });
  });

  it('should display an error message if fetching stats fails', async () => {
    const errorMessage = 'Failed to connect to the database.';
    mockedApiClient.getApplicationStats.mockRejectedValue(new Error(errorMessage));
    renderWithRouter();

    // Wait for the error message to appear
    await waitFor(() => {
      expect(screen.getByText(errorMessage)).toBeInTheDocument();
    });
  });

  it('should display a generic error message for unknown errors', async () => {
    mockedApiClient.getApplicationStats.mockRejectedValue('Unknown error object');
    renderWithRouter();

    await waitFor(() => {
      expect(screen.getByText('An unknown error occurred.')).toBeInTheDocument();
    });
  });

  it('should render a link back to the admin dashboard', async () => {
    mockedApiClient.getApplicationStats.mockResolvedValue(
      new Response(JSON.stringify(createMockAppStats())),
    );
    renderWithRouter();

    const link = await screen.findByRole('link', { name: /back to admin dashboard/i });
    expect(link).toBeInTheDocument();
    expect(link).toHaveAttribute('href', '/admin');
  });
});
