/**
 * ItemList Page Tests
 *
 * Tests for the item browsing page with search, filters,
 * pagination, and map/list view modes.
 */

import React from 'react';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { vi, type MockedFunction } from 'vitest';
import { MemoryRouter, Routes, Route } from 'react-router';
import ItemList from '../../pages/ItemList';
import { useAuth } from '../../contexts/AuthContext';
import { itemAPI } from '../../services/api';
import { mockUser, createMockItem, createMockPaginatedResponse } from '../../mocks/handlers';

// Mock dependencies
vi.mock('../../contexts/AuthContext');
vi.mock('../../services/api', () => ({
  itemAPI: {
    getItems: vi.fn(),
  },
}));

// Mock child components
vi.mock('../../components/ItemCard', () => ({
  default: ({ item }: { item: any }) => (
    <div data-testid={`item-card-${item.id}`}>
      <span>{item.name}</span>
      <span>${item.pricePerDay}/day</span>
    </div>
  ),
}));

vi.mock('../../components/SearchResultsMap', () => ({
  default: ({ items, onItemSelect }: any) => (
    <div data-testid="search-results-map">
      <span>Map with {items.length} items</span>
      {items.map((item: any) => (
        <button key={item.id} onClick={() => onItemSelect(item)}>
          Select {item.name}
        </button>
      ))}
    </div>
  ),
}));

vi.mock('../../components/FilterPanel', () => ({
  default: ({ show, onClose, onApplyFilters }: any) =>
    show ? (
      <div data-testid="filter-panel">
        <button onClick={onClose}>Close Filters</button>
        <button
          onClick={() =>
            onApplyFilters({
              lat: '37.7749',
              lng: '-122.4194',
              radius: '10',
              locationName: 'San Francisco',
            })
          }
        >
          Apply Filter
        </button>
      </div>
    ) : null,
}));

const mockNavigate = vi.fn();
vi.mock('react-router', async (importOriginal) => {
  const actual = await importOriginal<typeof import('react-router')>();
  return {
    ...actual,
    useNavigate: () => mockNavigate,
  };
});

const mockedUseAuth = useAuth as MockedFunction<typeof useAuth>;
const mockedGetItems = itemAPI.getItems as MockedFunction<typeof itemAPI.getItems>;

// Helper to render ItemList with route params
const renderItemList = (searchParams: string = '', authOverrides: Partial<ReturnType<typeof useAuth>> = {}) => {
  mockedUseAuth.mockReturnValue({
    user: mockUser,
    loading: false,
    showAuthModal: false,
    authModalMode: 'login',
    login: vi.fn(),
    register: vi.fn(),
    googleLogin: vi.fn(),
    logout: vi.fn(),
    checkAuth: vi.fn(),
    updateUser: vi.fn(),
    openAuthModal: vi.fn(),
    closeAuthModal: vi.fn(),
    ...authOverrides,
  });

  return render(
    <MemoryRouter initialEntries={[`/items${searchParams}`]}>
      <Routes>
        <Route path="/items" element={<ItemList />} />
      </Routes>
    </MemoryRouter>
  );
};

describe('ItemList', () => {
  const mockItems = [
    createMockItem({ id: '1', name: 'Camping Tent', pricePerDay: 25, isAvailable: true }),
    createMockItem({ id: '2', name: 'Mountain Bike', pricePerDay: 50, isAvailable: true }),
    createMockItem({ id: '3', name: 'Kayak', pricePerDay: 40, isAvailable: true }),
  ];

  beforeEach(() => {
    vi.clearAllMocks();
    mockedGetItems.mockResolvedValue({
      data: createMockPaginatedResponse(mockItems, 1, 1, 3),
    });

    // Mock window.scrollTo
    Object.defineProperty(window, 'scrollTo', {
      value: vi.fn(),
      writable: true,
    });

    // Mock window.innerWidth for responsive behavior
    Object.defineProperty(window, 'innerWidth', {
      value: 1024,
      writable: true,
    });
  });

  describe('Loading State', () => {
    it('shows loading spinner while fetching items', () => {
      mockedGetItems.mockImplementation(() => new Promise(() => {}));

      renderItemList();

      expect(screen.getByRole('status')).toBeInTheDocument();
    });
  });

  describe('Fetching and Displaying Items', () => {
    it('fetches items on mount', async () => {
      renderItemList();

      await waitFor(() => {
        expect(mockedGetItems).toHaveBeenCalled();
      });
    });

    it('displays items in grid', async () => {
      renderItemList();

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
        expect(screen.getByTestId('item-card-2')).toBeInTheDocument();
        expect(screen.getByTestId('item-card-3')).toBeInTheDocument();
      });

      expect(screen.getByText('Camping Tent')).toBeInTheDocument();
      expect(screen.getByText('Mountain Bike')).toBeInTheDocument();
      expect(screen.getByText('Kayak')).toBeInTheDocument();
    });

    it('displays item count', async () => {
      renderItemList();

      await waitFor(() => {
        expect(screen.getByText('3 items found')).toBeInTheDocument();
      });
    });

    it('filters out unavailable items', async () => {
      const itemsWithUnavailable = [
        ...mockItems,
        createMockItem({ id: '4', name: 'Unavailable Item', isAvailable: false }),
      ];

      mockedGetItems.mockResolvedValue({
        data: createMockPaginatedResponse(itemsWithUnavailable, 1, 1, 4),
      });

      renderItemList();

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });

      // Unavailable item should not be rendered
      expect(screen.queryByTestId('item-card-4')).not.toBeInTheDocument();
    });
  });

  describe('Empty Results', () => {
    it('handles empty results gracefully', async () => {
      mockedGetItems.mockResolvedValue({
        data: createMockPaginatedResponse([], 1, 1, 0),
      });

      renderItemList();

      await waitFor(() => {
        expect(screen.getByText('No items found')).toBeInTheDocument();
      });

      expect(screen.getByText(/try adjusting your search criteria/i)).toBeInTheDocument();
    });
  });

  describe('Error Handling', () => {
    it('displays error message on API failure', async () => {
      mockedGetItems.mockRejectedValue({
        response: { data: { message: 'Failed to fetch items' } },
      });

      renderItemList();

      await waitFor(() => {
        expect(screen.getByRole('alert')).toBeInTheDocument();
        expect(screen.getByText('Failed to fetch items')).toBeInTheDocument();
      });
    });
  });

  describe('Filters from URL Params', () => {
    it('applies filters from URL params', async () => {
      renderItemList('?search=tent&city=Seattle');

      await waitFor(() => {
        expect(mockedGetItems).toHaveBeenCalledWith(
          expect.objectContaining({
            search: 'tent',
            city: 'Seattle',
          })
        );
      });
    });

    it('applies location filters from URL', async () => {
      renderItemList('?lat=37.7749&lng=-122.4194&radius=25');

      await waitFor(() => {
        expect(mockedGetItems).toHaveBeenCalledWith(
          expect.objectContaining({
            lat: '37.7749',
            lng: '-122.4194',
            radius: '25',
          })
        );
      });
    });
  });

  describe('Filter Panel', () => {
    it('toggles filter panel', async () => {
      renderItemList();

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });

      // Filter panel should not be visible initially
      expect(screen.queryByTestId('filter-panel')).not.toBeInTheDocument();

      // Click filter button
      const filterButton = screen.getByTestId('filter-button');
      fireEvent.click(filterButton);

      // Filter panel should be visible
      expect(screen.getByTestId('filter-panel')).toBeInTheDocument();

      // Close filter panel
      const closeButton = screen.getByRole('button', { name: /close filters/i });
      fireEvent.click(closeButton);

      expect(screen.queryByTestId('filter-panel')).not.toBeInTheDocument();
    });

    it('applies filters and updates URL', async () => {
      renderItemList();

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });

      // Open filter panel
      const filterButton = screen.getByTestId('filter-button');
      fireEvent.click(filterButton);

      // Apply filter
      const applyButton = screen.getByRole('button', { name: /apply filter/i });
      fireEvent.click(applyButton);

      await waitFor(() => {
        expect(mockNavigate).toHaveBeenCalledWith(
          expect.stringContaining('lat=37.7749'),
          { replace: true }
        );
      });
    });
  });

  describe('View Mode Toggle', () => {
    it('defaults to list view', async () => {
      renderItemList();

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });

      // Items should be displayed, not map
      expect(screen.queryByTestId('search-results-map')).not.toBeInTheDocument();
    });

    it('switches to map view', async () => {
      renderItemList();

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });

      // Click map view button
      const mapButton = screen.getByRole('button', { name: /map/i });
      fireEvent.click(mapButton);

      await waitFor(() => {
        expect(screen.getByTestId('search-results-map')).toBeInTheDocument();
      });

      // Items should not be displayed directly
      expect(screen.queryByTestId('item-card-1')).not.toBeInTheDocument();
    });

    it('switches back to list view', async () => {
      renderItemList();

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });

      // Switch to map
      const mapButton = screen.getByRole('button', { name: /map/i });
      fireEvent.click(mapButton);

      await waitFor(() => {
        expect(screen.getByTestId('search-results-map')).toBeInTheDocument();
      });

      // Switch back to list
      const listButton = screen.getByRole('button', { name: /list/i });
      fireEvent.click(listButton);

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });
    });

    it('navigates to item on map selection', async () => {
      renderItemList();

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });

      // Switch to map
      const mapButton = screen.getByRole('button', { name: /map/i });
      fireEvent.click(mapButton);

      await waitFor(() => {
        expect(screen.getByTestId('search-results-map')).toBeInTheDocument();
      });

      // Select item on map
      const selectButton = screen.getByRole('button', { name: /select camping tent/i });
      fireEvent.click(selectButton);

      expect(mockNavigate).toHaveBeenCalledWith('/items/1');
    });
  });

  describe('Pagination', () => {
    it('displays pagination when multiple pages exist', async () => {
      mockedGetItems.mockResolvedValue({
        data: createMockPaginatedResponse(mockItems, 1, 3, 60),
      });

      renderItemList();

      await waitFor(() => {
        expect(screen.getByText(/\(page 1 of 3\)/)).toBeInTheDocument();
      });

      // Pagination buttons should exist
      expect(screen.getByLabelText('Previous page')).toBeInTheDocument();
      expect(screen.getByLabelText('Next page')).toBeInTheDocument();
    });

    it('navigates to next page', async () => {
      mockedGetItems.mockResolvedValue({
        data: createMockPaginatedResponse(mockItems, 1, 3, 60),
      });

      renderItemList();

      await waitFor(() => {
        expect(screen.getByText(/\(page 1 of 3\)/)).toBeInTheDocument();
      });

      const nextButton = screen.getByLabelText('Next page');
      fireEvent.click(nextButton);

      await waitFor(() => {
        expect(mockNavigate).toHaveBeenCalledWith(
          expect.stringContaining('page=2'),
          { replace: true }
        );
      });
    });

    it('navigates to previous page', async () => {
      mockedGetItems.mockResolvedValue({
        data: createMockPaginatedResponse(mockItems, 2, 3, 60),
      });

      renderItemList('?page=2');

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });

      const prevButton = screen.getByLabelText('Previous page');
      fireEvent.click(prevButton);

      await waitFor(() => {
        expect(mockNavigate).toHaveBeenCalled();
      });
    });

    it('disables prev button on first page', async () => {
      mockedGetItems.mockResolvedValue({
        data: createMockPaginatedResponse(mockItems, 1, 3, 60),
      });

      renderItemList();

      await waitFor(() => {
        expect(screen.getByText(/\(page 1 of 3\)/)).toBeInTheDocument();
      });

      const prevButton = screen.getByLabelText('Previous page');
      expect(prevButton).toBeDisabled();
    });

    it('disables next button on last page', async () => {
      mockedGetItems.mockResolvedValue({
        data: createMockPaginatedResponse(mockItems, 3, 3, 60),
      });

      renderItemList('?page=3');

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });

      const nextButton = screen.getByLabelText('Next page');
      expect(nextButton).toBeDisabled();
    });

    it('scrolls to top on page change', async () => {
      mockedGetItems.mockResolvedValue({
        data: createMockPaginatedResponse(mockItems, 1, 3, 60),
      });

      renderItemList();

      await waitFor(() => {
        expect(screen.getByText(/\(page 1 of 3\)/)).toBeInTheDocument();
      });

      const nextButton = screen.getByLabelText('Next page');
      fireEvent.click(nextButton);

      expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
    });

    it('does not display pagination for single page', async () => {
      renderItemList();

      await waitFor(() => {
        expect(screen.getByTestId('item-card-1')).toBeInTheDocument();
      });

      expect(screen.queryByLabelText('Previous page')).not.toBeInTheDocument();
      expect(screen.queryByLabelText('Next page')).not.toBeInTheDocument();
    });
  });

  describe('Auto-Apply User Address', () => {
    it('applies user address coordinates when no location set', async () => {
      const userWithAddress = {
        ...mockUser,
        addresses: [
          {
            id: '1',
            latitude: 40.7128,
            longitude: -74.006,
            city: 'New York',
            state: 'NY',
          },
        ],
      };

      renderItemList('', { user: userWithAddress as any });

      await waitFor(() => {
        expect(mockNavigate).toHaveBeenCalledWith(
          expect.stringContaining('lat=40.7128'),
          { replace: true }
        );
      });
    });

    it('does not override existing location filters', async () => {
      const userWithAddress = {
        ...mockUser,
        addresses: [
          {
            id: '1',
            latitude: 40.7128,
            longitude: -74.006,
          },
        ],
      };

      renderItemList('?lat=37.7749&lng=-122.4194', { user: userWithAddress as any });

      await waitFor(() => {
        expect(mockedGetItems).toHaveBeenCalled();
      });

      // Should not have navigated to user's address
      expect(mockNavigate).not.toHaveBeenCalledWith(
        expect.stringContaining('lat=40.7128'),
        expect.anything()
      );
    });
  });

  describe('View Toggle Buttons', () => {
    it('hides view toggle when no items', async () => {
      mockedGetItems.mockResolvedValue({
        data: createMockPaginatedResponse([], 1, 1, 0),
      });

      renderItemList();

      await waitFor(() => {
        expect(screen.getByText('No items found')).toBeInTheDocument();
      });

      expect(screen.queryByRole('button', { name: /list/i })).not.toBeInTheDocument();
      expect(screen.queryByRole('button', { name: /map/i })).not.toBeInTheDocument();
    });
  });
});
