import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { SWRConfig } from 'swr';

const { mockUseApi } = vi.hoisted(() => ({
  mockUseApi: vi.fn(),
}));

const { mockListPantryItems, mockTransitionPantryItem, mockDeletePantryItem } = vi.hoisted(() => ({
  mockListPantryItems: vi.fn(),
  mockTransitionPantryItem: vi.fn(),
  mockDeletePantryItem: vi.fn(),
}));

vi.mock('@/lib/useApi', () => ({ useApi: mockUseApi }));

vi.mock('@/services/pantry', () => ({
  listPantryItems: mockListPantryItems,
  transitionPantryItem: mockTransitionPantryItem,
  deletePantryItem: mockDeletePantryItem,
}));

vi.mock('next/link', () => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return { default: (props: any) => props.children };
});

import PantryPage from '../../../../src/app/(dashboard)/pantry/page';

beforeEach(() => vi.clearAllMocks());

const wrapper = ({ children }: { children: React.ReactNode }) => (
  <SWRConfig value={{ provider: () => new Map(), dedupingInterval: 0 }}>{children}</SWRConfig>
);

const SAMPLE_ITEM = {
  _id: 'pi-1',
  householdId: 'hh1',
  productId: 'p1',
  productName: 'Whole Milk',
  storageLocation: 'fridge',
  quantity: 1,
  unit: 'liter',
  purchaseDate: '2026-05-01T00:00:00.000Z',
  status: 'sealed',
  freshnessEstimate: {
    estimatedExpiryDate: '2026-05-15T00:00:00.000Z',
    daysRemaining: 11,
    urgency: 'fresh',
    source: 'rule',
  },
  createdBy: 'u1',
  createdAt: '2026-05-01T00:00:00.000Z',
  updatedAt: '2026-05-01T00:00:00.000Z',
};

describe('PantryPage', () => {
  it('shows loading skeleton when session is loading', () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: true });

    render(<PantryPage />, { wrapper });

    expect(screen.getByText('Pantry')).toBeInTheDocument();
    expect(screen.queryByText('All')).not.toBeInTheDocument();
  });

  it('shows household prompt when no householdId', () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false });

    render(<PantryPage />, { wrapper });

    expect(screen.getByText(/create or join a household/)).toBeInTheDocument();
  });

  it('renders pantry items when household exists', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [SAMPLE_ITEM],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PantryPage />, { wrapper });

    await waitFor(() => {
      expect(screen.getByText('Whole Milk')).toBeInTheDocument();
    });
  });

  it('shows empty state when no items', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PantryPage />, { wrapper });

    await waitFor(() => {
      expect(screen.getByText(/No pantry items yet/)).toBeInTheDocument();
    });
  });

  it('shows freshness badge', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [SAMPLE_ITEM],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PantryPage />, { wrapper });

    await waitFor(() => {
      expect(screen.getByText('Fresh')).toBeInTheDocument();
      expect(screen.getByText('11d left')).toBeInTheDocument();
    });
  });

  it('shows overdue text for negative days', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [
        {
          ...SAMPLE_ITEM,
          _id: 'pi-2',
          freshnessEstimate: {
            ...SAMPLE_ITEM.freshnessEstimate,
            daysRemaining: -2,
            urgency: 'expired',
          },
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PantryPage />, { wrapper });

    await waitFor(() => {
      expect(screen.getByText('2d overdue')).toBeInTheDocument();
      expect(screen.getByText('Expired')).toBeInTheDocument();
    });
  });

  it('shows transition buttons for active items', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [SAMPLE_ITEM],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PantryPage />, { wrapper });

    await waitFor(() => {
      expect(screen.getByText('Open')).toBeInTheDocument();
      expect(screen.getByText('Consume')).toBeInTheDocument();
      expect(screen.getByText('Discard')).toBeInTheDocument();
    });
  });

  it('handles transition click', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [SAMPLE_ITEM],
      pagination: { cursor: null, hasMore: false },
    });
    mockTransitionPantryItem.mockResolvedValue({
      ...SAMPLE_ITEM,
      status: 'opened',
    });

    render(<PantryPage />, { wrapper });

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

    fireEvent.click(screen.getByText('Open'));

    await waitFor(() => {
      expect(mockTransitionPantryItem).toHaveBeenCalledWith('hh1', 'pi-1', { status: 'opened' });
    });
  });

  it('handles delete with confirmation', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [SAMPLE_ITEM],
      pagination: { cursor: null, hasMore: false },
    });
    mockDeletePantryItem.mockResolvedValue(undefined);
    vi.spyOn(window, 'confirm').mockReturnValue(true);

    render(<PantryPage />, { wrapper });

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

    fireEvent.click(screen.getByText('Delete'));

    await waitFor(() => {
      expect(mockDeletePantryItem).toHaveBeenCalledWith('hh1', 'pi-1');
    });
  });

  it('does not delete when confirm is cancelled', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [SAMPLE_ITEM],
      pagination: { cursor: null, hasMore: false },
    });
    vi.spyOn(window, 'confirm').mockReturnValue(false);

    render(<PantryPage />, { wrapper });

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

    fireEvent.click(screen.getByText('Delete'));

    expect(mockDeletePantryItem).not.toHaveBeenCalled();
  });

  it('filters by storage tab', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PantryPage />, { wrapper });

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

    fireEvent.click(screen.getByText('Fridge'));

    await waitFor(() => {
      expect(mockListPantryItems).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({ storageLocation: 'fridge' }),
      );
    });
  });

  it('filters by status select', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PantryPage />, { wrapper });

    await waitFor(() => {
      expect(screen.getByDisplayValue('All statuses')).toBeInTheDocument();
    });

    fireEvent.change(screen.getByDisplayValue('All statuses'), { target: { value: 'opened' } });

    await waitFor(() => {
      expect(mockListPantryItems).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({ status: 'opened' }),
      );
    });
  });

  it('shows error when fetch fails', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockRejectedValue(new Error('Network error'));

    render(<PantryPage />, { wrapper });

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

  it('shows error when transition fails', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [SAMPLE_ITEM],
      pagination: { cursor: null, hasMore: false },
    });
    mockTransitionPantryItem.mockRejectedValue(new Error('Transition failed'));

    render(<PantryPage />);

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

    fireEvent.click(screen.getByText('Open'));

    await waitFor(() => {
      expect(screen.getByText('Transition failed')).toBeInTheDocument();
    });
  });

  it('shows filter empty state message', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPantryItems.mockResolvedValue({
      data: [],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PantryPage />);

    // Click a filter tab to trigger filter state
    await waitFor(() => {
      expect(screen.getByText('Fridge')).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText('Fridge'));

    await waitFor(() => {
      expect(screen.getByText(/No items match your filters/)).toBeInTheDocument();
    });
  });
});
