/**
 * ItemDetail Page Tests
 *
 * Tests for the item detail page including viewing, rental flow,
 * admin actions, and image gallery navigation.
 */

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 ItemDetail from '../../pages/ItemDetail';
import { useAuth } from '../../contexts/AuthContext';
import { itemAPI, rentalAPI } from '../../services/api';
import { mockUser, mockAdminUser, mockItemWithOwner, createMockRental } from '../../mocks/handlers';

// Mock dependencies
vi.mock('../../contexts/AuthContext');
vi.mock('../../services/api', () => ({
  itemAPI: {
    getItem: vi.fn(),
    adminSoftDeleteItem: vi.fn(),
    adminRestoreItem: vi.fn(),
  },
  rentalAPI: {
    getRentals: vi.fn(),
    getRentalCostPreview: vi.fn(),
  },
}));

// Mock child components
vi.mock('../../services/uploadService', () => ({
  getImageUrl: vi.fn((filename, variant) => `https://test-bucket.s3.amazonaws.com/${variant}/${filename}`),
}));

vi.mock('react-datepicker', () => ({
  default: ({ selected, onChange, minDate, placeholderText, className }: any) => (
    <input
      type="text"
      className={className}
      placeholder={placeholderText}
      value={selected ? selected.toLocaleDateString() : ''}
      onChange={(e) => onChange(new Date(e.target.value))}
      data-testid="date-picker"
    />
  ),
}));

vi.mock('../../components/GoogleMapWithRadius', () => ({
  default: () => <div data-testid="google-map">Map</div>,
}));

vi.mock('../../components/ItemReviews', () => ({
  default: ({ itemId }: { itemId: string }) => <div data-testid="item-reviews">Reviews for {itemId}</div>,
}));

vi.mock('../../components/ConfirmationModal', () => ({
  default: ({ show, onConfirm, onClose, title, loading, showReasonInput, reason, onReasonChange }: any) =>
    show ? (
      <div data-testid="confirmation-modal">
        <h5>{title}</h5>
        {showReasonInput && (
          <input
            data-testid="deletion-reason"
            value={reason}
            onChange={(e) => onReasonChange(e.target.value)}
          />
        )}
        <button onClick={onClose}>Cancel</button>
        <button onClick={onConfirm} disabled={loading}>
          {loading ? 'Processing...' : 'Confirm'}
        </button>
      </div>
    ) : null,
}));

vi.mock('../../components/Avatar', () => ({
  default: ({ user }: { user: any }) => <div data-testid="avatar">{user?.firstName}</div>,
}));

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 mockedGetItem = itemAPI.getItem as MockedFunction<typeof itemAPI.getItem>;
const mockedGetRentals = rentalAPI.getRentals as MockedFunction<typeof rentalAPI.getRentals>;
const mockedGetCostPreview = rentalAPI.getRentalCostPreview as MockedFunction<typeof rentalAPI.getRentalCostPreview>;
const mockedAdminSoftDelete = itemAPI.adminSoftDeleteItem as MockedFunction<typeof itemAPI.adminSoftDeleteItem>;
const mockedAdminRestore = itemAPI.adminRestoreItem as MockedFunction<typeof itemAPI.adminRestoreItem>;

// Helper to render ItemDetail with route params
const renderItemDetail = (itemId: string = '1', 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/${itemId}`]}>
      <Routes>
        <Route path="/items/:id" element={<ItemDetail />} />
      </Routes>
    </MemoryRouter>
  );
};

describe('ItemDetail', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockedGetItem.mockResolvedValue({ data: mockItemWithOwner });
    mockedGetRentals.mockResolvedValue({ data: [] });
    mockedGetCostPreview.mockResolvedValue({ data: { baseAmount: 50 } });

    // Mock clipboard API
    Object.assign(navigator, {
      clipboard: {
        writeText: vi.fn().mockResolvedValue(undefined),
      },
    });
  });

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

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

  describe('Error Handling', () => {
    it('displays error when item fetch fails', async () => {
      mockedGetItem.mockRejectedValue({
        response: { data: { message: 'Item not found' } },
      });

      renderItemDetail();

      await waitFor(() => {
        expect(screen.getByRole('alert')).toBeInTheDocument();
        expect(screen.getByText('Item not found')).toBeInTheDocument();
      });
    });

    it('displays default error when no message provided', async () => {
      mockedGetItem.mockRejectedValue(new Error('Network error'));

      renderItemDetail('1', { user: null });

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

  describe('Item Display', () => {
    it('fetches and displays item details', async () => {
      renderItemDetail();

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      expect(screen.getByText('A test item for rental')).toBeInTheDocument();
      expect(mockedGetItem).toHaveBeenCalledWith('1');
    });

    it('displays item pricing', async () => {
      renderItemDetail();

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      // mockItemWithOwner has pricePerHour: 10, which is displayed first
      // The pricing text may be split across multiple text nodes due to JSX whitespace
      await waitFor(() => {
        const pricingCard = document.getElementById('mobile-pricing-card');
        expect(pricingCard).toBeInTheDocument();
        expect(pricingCard?.textContent).toContain('$10');
        expect(pricingCard?.textContent).toContain('/Hour');
      });
    });

    it('displays owner information', async () => {
      renderItemDetail();

      await waitFor(() => {
        expect(screen.getByTestId('avatar')).toBeInTheDocument();
      });
    });

    it('displays reviews section', async () => {
      renderItemDetail();

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

    it('displays map', async () => {
      renderItemDetail();

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

  describe('Image Gallery', () => {
    it('navigates image gallery via thumbnails', async () => {
      const itemWithMultipleImages = {
        ...mockItemWithOwner,
        imageFilenames: ['image1.jpg', 'image2.jpg', 'image3.jpg'],
      };
      mockedGetItem.mockResolvedValue({ data: itemWithMultipleImages });

      renderItemDetail();

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      // Find and click the second thumbnail
      const thumbnails = screen.getAllByAltText(/Test Item \d/);
      expect(thumbnails).toHaveLength(3);

      fireEvent.click(thumbnails[1]);

      // Main image should update (the src should change)
      const mainImage = screen.getByAltText('Test Item');
      expect(mainImage).toBeInTheDocument();
    });
  });

  describe('Owner View', () => {
    it('shows edit button for owner', async () => {
      const ownerUser = { ...mockUser, id: '2' }; // mockItemWithOwner has ownerId: '2'
      renderItemDetail('1', { user: ownerUser as any });

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      expect(screen.getByRole('button', { name: /edit listing/i })).toBeInTheDocument();
    });

    it('navigates to edit page on edit click', async () => {
      const ownerUser = { ...mockUser, id: '2' };
      renderItemDetail('1', { user: ownerUser as any });

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      const editButton = screen.getByRole('button', { name: /edit listing/i });
      fireEvent.click(editButton);

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

    it('does not show rental controls for owner', async () => {
      const ownerUser = { ...mockUser, id: '2' };
      renderItemDetail('1', { user: ownerUser as any });

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

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

  describe('Admin Actions', () => {
    const adminUser = { ...mockAdminUser };

    it('shows admin delete button for non-deleted items', async () => {
      renderItemDetail('1', { user: adminUser as any });

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

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

    it('shows admin restore button for deleted items', async () => {
      const deletedItem = { ...mockItemWithOwner, isDeleted: true };
      mockedGetItem.mockResolvedValue({ data: deletedItem });

      renderItemDetail('1', { user: adminUser as any });

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

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

    it('opens confirmation modal for soft delete', async () => {
      renderItemDetail('1', { user: adminUser as any });

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      const deleteButton = screen.getByRole('button', { name: /delete/i });
      fireEvent.click(deleteButton);

      expect(screen.getByTestId('confirmation-modal')).toBeInTheDocument();
      expect(screen.getByText('Confirm Delete')).toBeInTheDocument();
    });

    it('performs soft delete with reason', async () => {
      mockedAdminSoftDelete.mockResolvedValue({ data: {} });

      renderItemDetail('1', { user: adminUser as any });

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      const deleteButton = screen.getByRole('button', { name: /delete/i });
      fireEvent.click(deleteButton);

      const reasonInput = screen.getByTestId('deletion-reason');
      fireEvent.change(reasonInput, { target: { value: 'Policy violation' } });

      const confirmButton = screen.getByRole('button', { name: /confirm/i });
      fireEvent.click(confirmButton);

      await waitFor(() => {
        expect(mockedAdminSoftDelete).toHaveBeenCalledWith('1', 'Policy violation');
      });
    });

    it('performs restore', async () => {
      const deletedItem = { ...mockItemWithOwner, isDeleted: true };
      mockedGetItem.mockResolvedValue({ data: deletedItem });
      mockedAdminRestore.mockResolvedValue({ data: {} });

      renderItemDetail('1', { user: adminUser as any });

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      const restoreButton = screen.getByRole('button', { name: /restore/i });
      fireEvent.click(restoreButton);

      const confirmButton = screen.getByRole('button', { name: /confirm/i });
      fireEvent.click(confirmButton);

      await waitFor(() => {
        expect(mockedAdminRestore).toHaveBeenCalledWith('1');
      });
    });

    it('displays deleted item warning for admins', async () => {
      const deletedItem = {
        ...mockItemWithOwner,
        isDeleted: true,
        deletionReason: 'Terms violation',
        deletedAt: new Date().toISOString(),
      };
      mockedGetItem.mockResolvedValue({ data: deletedItem });

      renderItemDetail('1', { user: adminUser as any });

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      expect(screen.getByText(/item soft deleted/i)).toBeInTheDocument();
    });
  });

  describe('Rental Flow', () => {
    it('shows "already renting" warning for active rentals', async () => {
      // Create rental with matching item id and active status
      // The component checks: rental.item?.id === id && ["pending", "confirmed", "active"].includes(rental.status)
      const activeRental = {
        id: 'rental-1',
        itemId: '1',
        renterId: '1',
        ownerId: '2',
        item: { id: '1', name: 'Test Item' }, // Simplified item with matching id
        status: 'active',
        displayStatus: 'active',
        startDateTime: new Date().toISOString(),
        endDateTime: new Date(Date.now() + 86400000).toISOString(),
        totalAmount: 25,
        paymentStatus: 'paid',
      };
      mockedGetRentals.mockResolvedValue({ data: [activeRental] });

      renderItemDetail();

      // Wait for item to load first
      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      // Wait for getRentals to be called and verify the mock data
      await waitFor(() => {
        expect(mockedGetRentals).toHaveBeenCalled();
      });

      // The warning should appear in the alert element
      // Use a more flexible wait for the component state to update
      await waitFor(() => {
        const warnings = screen.queryAllByRole('alert');
        const activeRentalWarning = warnings.find(el =>
          el.textContent?.includes('already have an active rental')
        );
        expect(activeRentalWarning).toBeTruthy();
      }, { timeout: 3000 });
    });

    it('rent button disabled when invalid selection', async () => {
      renderItemDetail();

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      // Find the Rent Now button(s) - there may be multiple for mobile/desktop
      const rentButtons = screen.getAllByRole('button', { name: /rent now/i });
      rentButtons.forEach((button) => {
        expect(button).toBeDisabled();
      });
    });
  });

  describe('Copy Link', () => {
    it('copies link to clipboard', async () => {
      renderItemDetail();

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      const copyButton = screen.getByRole('button', { name: /copy link/i });
      fireEvent.click(copyButton);

      expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
        expect.stringContaining('/items/1')
      );
    });
  });

  describe('Unauthenticated User', () => {
    it('shows item details without rental controls for unauthenticated user', async () => {
      renderItemDetail('1', { user: null });

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      // Should show item details
      expect(screen.getByText('A test item for rental')).toBeInTheDocument();
    });
  });

  describe('Cancellation Policy', () => {
    it('displays cancellation policy', async () => {
      renderItemDetail();

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      expect(screen.getByText('Cancellation Policy')).toBeInTheDocument();
      expect(screen.getByText(/full refund.*48\+/i)).toBeInTheDocument();
    });

    it('displays replacement cost', async () => {
      renderItemDetail();

      await waitFor(() => {
        expect(screen.getByText('Test Item')).toBeInTheDocument();
      });

      expect(screen.getByText(/replacement cost.*\$500/i)).toBeInTheDocument();
    });
  });
});
