import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type React from 'react';

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

const { mockListPurchases, mockCreatePurchase, mockReceivePurchase, mockDeletePurchase } =
  vi.hoisted(() => ({
    mockListPurchases: vi.fn(),
    mockCreatePurchase: vi.fn(),
    mockReceivePurchase: vi.fn(),
    mockDeletePurchase: vi.fn(),
  }));

const { mockListStores } = vi.hoisted(() => ({ mockListStores: vi.fn() }));
const { mockListMedicines, mockListMedicineProducts } = vi.hoisted(() => ({
  mockListMedicines: vi.fn(),
  mockListMedicineProducts: vi.fn(),
}));

vi.mock('@/lib/useApi', () => ({ useApi: mockUseApi }));
vi.mock('@/services/purchases', () => ({
  listPurchases: mockListPurchases,
  createPurchase: mockCreatePurchase,
  receivePurchase: mockReceivePurchase,
  deletePurchase: mockDeletePurchase,
}));
vi.mock('@/services/stores', () => ({ listStores: mockListStores }));
vi.mock('@/services/medicines', () => ({
  listMedicines: mockListMedicines,
  listMedicineProducts: mockListMedicineProducts,
}));
vi.mock('next/link', () => ({
  default: (props: { href?: string; children: React.ReactNode }) => props.children,
}));

import PurchasesPage from '../../../../src/app/(dashboard)/purchases/page';

const emptyResponse = { data: [], pagination: { cursor: null, hasMore: false } };

beforeEach(() => {
  vi.clearAllMocks();
  mockListStores.mockResolvedValue(emptyResponse);
  mockListMedicines.mockResolvedValue(emptyResponse);
});

describe('PurchasesPage', () => {
  it('shows loading skeleton when session loading', () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: true });
    render(<PurchasesPage />);
    expect(screen.getByText('Purchases')).toBeInTheDocument();
    expect(screen.queryByText('Record Purchase')).not.toBeInTheDocument();
  });

  it('shows household prompt when no householdId', () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false });
    render(<PurchasesPage />);
    expect(screen.getByText(/create or join a household/)).toBeInTheDocument();
  });

  it('shows empty state when no purchases', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);
    render(<PurchasesPage />);

    await waitFor(() => expect(screen.getByText(/No purchases recorded yet/)).toBeInTheDocument());
  });

  it('shows Record Purchase button', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);
    render(<PurchasesPage />);

    await waitFor(() => expect(screen.getByText('Record Purchase')).toBeInTheDocument());
  });

  it('shows error when purchases fail to load', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockRejectedValue(new Error('Network failure'));
    render(<PurchasesPage />);

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

  it('renders ordered and received sections', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue({
      data: [
        {
          _id: 'p1',
          status: 'ordered',
          purchasedAt: '2026-01-01T00:00:00.000Z',
          items: [],
          householdId: 'hh1',
          createdBy: 'u1',
          createdAt: '2026-01-01T00:00:00.000Z',
          updatedAt: '2026-01-01T00:00:00.000Z',
        },
        {
          _id: 'p2',
          status: 'in_cabinet',
          purchasedAt: '2026-01-02T00:00:00.000Z',
          items: [],
          householdId: 'hh1',
          createdBy: 'u1',
          createdAt: '2026-01-02T00:00:00.000Z',
          updatedAt: '2026-01-02T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    render(<PurchasesPage />);

    await waitFor(() => expect(screen.getByText('Pending arrival')).toBeInTheDocument());
    // "Received" appears as a section heading (h2) — use getAllByText since PurchaseCard may also render it
    expect(screen.getAllByText('Received').length).toBeGreaterThan(0);
  });

  it('toggles the create form', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);
    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));
    // After clicking, both the header toggle and the form show "Cancel"
    expect(screen.getAllByRole('button', { name: 'Cancel' }).length).toBeGreaterThan(0);
  });

  it('shows Load more button when hasMore', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue({
      data: [],
      pagination: { cursor: 'cur1', hasMore: true },
    });
    render(<PurchasesPage />);

    await waitFor(() => expect(screen.getByText('Load more')).toBeInTheDocument());
  });

  it('shows validation error when no store selected', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'Walgreens',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

    await waitFor(() => screen.getByText('Save Purchase'));
    // Submit without selecting a store
    fireEvent.submit(screen.getByRole('button', { name: 'Save Purchase' }).closest('form')!);

    await waitFor(() => expect(screen.getByText('Please select a store.')).toBeInTheDocument());
  });

  it('shows validation error when no items', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'Walgreens',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

    await waitFor(() => screen.getByText('Save Purchase'));
    fireEvent.change(screen.getByDisplayValue('Select store'), { target: { value: 'st-1' } });
    fireEvent.submit(screen.getByRole('button', { name: 'Save Purchase' }).closest('form')!);

    await waitFor(() =>
      expect(
        screen.getByText('Add at least one item with a name and quantity.'),
      ).toBeInTheDocument(),
    );
  });

  it('receives a purchase after confirmation', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue({
      data: [
        {
          _id: 'p1',
          status: 'ordered',
          purchasedAt: '2026-01-01T00:00:00.000Z',
          items: [],
          householdId: 'hh1',
          createdBy: 'u1',
          createdAt: '2026-01-01T00:00:00.000Z',
          updatedAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockReceivePurchase.mockResolvedValue({});

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Mark as received'));
    await userEvent.click(screen.getByText('Mark as received'));

    expect(mockReceivePurchase).toHaveBeenCalledWith('hh1', 'p1');
  });

  it('deletes a purchase after confirmation', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue({
      data: [
        {
          _id: 'p1',
          status: 'ordered',
          purchasedAt: '2026-01-01T00:00:00.000Z',
          items: [],
          householdId: 'hh1',
          createdBy: 'u1',
          createdAt: '2026-01-01T00:00:00.000Z',
          updatedAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockDeletePurchase.mockResolvedValue({});

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Cancel order'));
    await userEvent.click(screen.getByText('Cancel order'));

    expect(mockDeletePurchase).toHaveBeenCalledWith('hh1', 'p1');
  });

  it('shows error when receive fails', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue({
      data: [
        {
          _id: 'p1',
          status: 'ordered',
          purchasedAt: '2026-01-01T00:00:00.000Z',
          items: [],
          householdId: 'hh1',
          createdBy: 'u1',
          createdAt: '2026-01-01T00:00:00.000Z',
          updatedAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockReceivePurchase.mockRejectedValue(new Error('Receive failed'));

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Mark as received'));
    await userEvent.click(screen.getByText('Mark as received'));

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

  it('shows error when delete fails', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue({
      data: [
        {
          _id: 'p1',
          status: 'ordered',
          purchasedAt: '2026-01-01T00:00:00.000Z',
          items: [],
          householdId: 'hh1',
          createdBy: 'u1',
          createdAt: '2026-01-01T00:00:00.000Z',
          updatedAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockDeletePurchase.mockRejectedValue(new Error('Delete failed'));

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Cancel order'));
    await userEvent.click(screen.getByText('Cancel order'));

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

  it('creates a purchase successfully', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);
    mockListMedicineProducts.mockResolvedValue(emptyResponse);
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'Walgreens',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockCreatePurchase.mockResolvedValue({
      _id: 'p-new',
      status: 'in_cabinet',
      purchasedAt: '2026-01-01T00:00:00.000Z',
      items: [],
      householdId: 'hh1',
      createdBy: 'u1',
      createdAt: '2026-01-01T00:00:00.000Z',
      updatedAt: '2026-01-01T00:00:00.000Z',
    });

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

    await waitFor(() => screen.getByText('Save Purchase'));
    fireEvent.change(screen.getByDisplayValue('Select store'), { target: { value: 'st-1' } });
    fireEvent.change(screen.getByPlaceholderText('Brand / product name'), {
      target: { value: 'Aspirin' },
    });
    fireEvent.change(screen.getByPlaceholderText('90'), { target: { value: '30' } });
    fireEvent.submit(screen.getByRole('button', { name: 'Save Purchase' }).closest('form')!);

    await waitFor(() =>
      expect(mockCreatePurchase).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({ storeId: 'st-1' }),
      ),
    );
    // Form hides after success
    await waitFor(() => expect(screen.queryByText('Save Purchase')).not.toBeInTheDocument());
  });

  it('cancels the create form', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

    await waitFor(() => screen.getAllByRole('button', { name: 'Cancel' }));
    // Click the form's Cancel button (inside the form)
    const cancelBtns = screen.getAllByRole('button', { name: 'Cancel' });
    await userEvent.click(cancelBtns[cancelBtns.length - 1]!);

    expect(screen.queryByText('Save Purchase')).not.toBeInTheDocument();
  });

  it('renders purchase items with price data', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue({
      data: [
        {
          _id: 'p1',
          status: 'in_cabinet',
          storeName: 'Walgreens',
          purchasedAt: '2026-01-01T00:00:00.000Z',
          items: [
            {
              name: 'Aspirin',
              quantity: 30,
              unit: 'tablet',
              actualPrice: 5.99,
              currency: 'USD',
            },
          ],
          householdId: 'hh1',
          createdBy: 'u1',
          createdAt: '2026-01-01T00:00:00.000Z',
          updatedAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PurchasesPage />);

    await waitFor(() => expect(screen.getByText('Aspirin')).toBeInTheDocument());
    expect(screen.getByText(/5.99/)).toBeInTheDocument();
  });

  it('loads more purchases when Load more is clicked', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue({
      data: [],
      pagination: { cursor: 'cur1', hasMore: true },
    });

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Load more'));
    await userEvent.click(screen.getByText('Load more'));

    expect(mockListPurchases).toHaveBeenCalledTimes(2);
  });

  it('toggles online order checkbox and changes notes', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

    await waitFor(() => screen.getByLabelText('Online order (pending arrival)'));
    fireEvent.click(screen.getByLabelText('Online order (pending arrival)'));
    fireEvent.change(document.querySelector('input[maxLength="1000"]') as HTMLElement, {
      target: { value: 'Some notes' },
    });

    expect(screen.getByLabelText('Online order (pending arrival)')).toBeChecked();
  });

  it('adds and removes purchase items', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

    await waitFor(() => screen.getByText('Save Purchase'));
    const addItemBtn = screen.getByRole('button', { name: 'Add item' });
    await userEvent.click(addItemBtn);

    // Now 2 items — remove button shows
    await waitFor(() => screen.getAllByText('Remove').length > 0);
    await userEvent.click(screen.getAllByText('Remove')[0]!);

    expect(screen.queryByText('Remove')).not.toBeInTheDocument();
  });

  it('changes item price and currency fields', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

    await waitFor(() => screen.getByPlaceholderText('9.99'));
    fireEvent.change(screen.getByPlaceholderText('9.99'), { target: { value: '5.99' } });
    fireEvent.change(screen.getByPlaceholderText('USD'), { target: { value: 'EUR' } });

    expect(screen.getByPlaceholderText('9.99')).toBeInTheDocument();
  });

  it('changes item name and unit fields', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

    await waitFor(() => screen.getByPlaceholderText('Brand / product name'));
    fireEvent.change(screen.getByPlaceholderText('Brand / product name'), {
      target: { value: 'Aspirin' },
    });
    fireEvent.change(screen.getByPlaceholderText('90'), { target: { value: '30' } });
    fireEvent.change(screen.getByPlaceholderText('tablet'), { target: { value: 'capsule' } });

    expect(screen.getByPlaceholderText('Brand / product name')).toBeInTheDocument();
  });

  it('selects a medicine and product in the form', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);
    mockListMedicines.mockResolvedValue({
      data: [{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg' }],
      pagination: { cursor: null, hasMore: false },
    });
    mockListMedicineProducts.mockResolvedValue({
      data: [{ _id: 'prod-1', brand: 'Glucophage', packageSize: 60, packageUnit: 'tablet' }],
      pagination: { cursor: null, hasMore: false },
    });

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

    await waitFor(() => screen.getByText('Save Purchase'));
    // Select a medicine
    fireEvent.change(screen.getByDisplayValue('Select medicine'), { target: { value: 'med-1' } });

    // Wait for products to load
    await waitFor(() => screen.getByDisplayValue('Select product'));

    // Select a product
    fireEvent.change(screen.getByDisplayValue('Select product'), { target: { value: 'prod-1' } });

    // Product details should auto-fill the form
    expect(screen.getByDisplayValue('60')).toBeInTheDocument();
  });

  it('handles product list failure gracefully', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);
    mockListMedicines.mockResolvedValue({
      data: [{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg' }],
      pagination: { cursor: null, hasMore: false },
    });
    mockListMedicineProducts.mockRejectedValue(new Error('Products failed'));

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

    await waitFor(() => screen.getByText('Save Purchase'));
    fireEvent.change(screen.getByDisplayValue('Select medicine'), { target: { value: 'med-1' } });

    // Wait for products loading to finish (error is swallowed)
    await waitFor(() => expect(mockListMedicineProducts).toHaveBeenCalled());
  });

  it('shows no stores message when store list is empty in form', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockResolvedValue(emptyResponse);
    mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });

    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Record Purchase'));
    await userEvent.click(screen.getByText('Record Purchase'));

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

  it('dismisses error', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListPurchases.mockRejectedValue(new Error('Oops'));
    render(<PurchasesPage />);

    await waitFor(() => screen.getByText('Oops'));
    await userEvent.click(screen.getByText('Dismiss'));
    expect(screen.queryByText('Oops')).not.toBeInTheDocument();
  });
});
