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 { SWRConfig } from 'swr';
import type React from 'react';

const {
  mockListCabinetItems,
  mockGetCabinetSummary,
  mockCreateCabinetItem,
  mockAdjustCabinetItemQuantity,
  mockDeleteCabinetItem,
} = vi.hoisted(() => ({
  mockListCabinetItems: vi.fn(),
  mockGetCabinetSummary: vi.fn(),
  mockCreateCabinetItem: vi.fn(),
  mockAdjustCabinetItemQuantity: vi.fn(),
  mockDeleteCabinetItem: vi.fn(),
}));

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

vi.mock('@/services/cabinet', () => ({
  listCabinetItems: mockListCabinetItems,
  getCabinetSummary: mockGetCabinetSummary,
  createCabinetItem: mockCreateCabinetItem,
  adjustCabinetItemQuantity: mockAdjustCabinetItemQuantity,
  deleteCabinetItem: mockDeleteCabinetItem,
}));

vi.mock('@/services/medicines', () => ({ listMedicines: mockListMedicines }));

vi.mock('next/link', () => ({
  default: (props: { href?: string; children: React.ReactNode }) => (
    <a href={props.href}>{props.children}</a>
  ),
}));

import { CabinetTab } from '../../../../src/app/(dashboard)/medicines/CabinetTab';

const emptySummary = { data: [] };
const emptyItems = { data: [], pagination: { cursor: null, hasMore: false } };
const emptyMeds = { data: [], pagination: { cursor: null, hasMore: false } };

const cabinetItem = {
  _id: 'ci-1',
  medicineId: 'med-1',
  medicineName: 'Metformin',
  quantity: 10,
  unit: 'tablet',
  status: 'active',
  expirationDate: null,
  unitPrice: null,
  totalPrice: null,
  storeId: null,
  notes: null,
  createdAt: '2026-01-01T00:00:00.000Z',
};

beforeEach(() => {
  vi.clearAllMocks();
  mockGetCabinetSummary.mockResolvedValue(emptySummary);
  mockListCabinetItems.mockResolvedValue(emptyItems);
  mockListMedicines.mockResolvedValue(emptyMeds);
});

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

describe('CabinetTab', () => {
  it('shows empty state when no items', async () => {
    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => expect(screen.getByText(/cabinet is empty/i)).toBeInTheDocument());
  });

  it('shows error when list fails', async () => {
    mockGetCabinetSummary.mockRejectedValue(new Error('Server error'));

    render(<CabinetTab householdId="hh1" />, { wrapper });

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

  it('dismisses error on Dismiss click', async () => {
    mockGetCabinetSummary.mockRejectedValue(new Error('Server error'));

    render(<CabinetTab householdId="hh1" />, { wrapper });

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

  it('toggles Add to Cabinet form', async () => {
    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Add to Cabinet'));
    await userEvent.click(screen.getByText('Add to Cabinet'));
    expect(screen.getByPlaceholderText('Search medicines...')).toBeInTheDocument();

    await userEvent.click(screen.getAllByText('Cancel')[0]);
    expect(screen.queryByPlaceholderText('Search medicines...')).not.toBeInTheDocument();
  });

  it('switches between Summary and All Items views', async () => {
    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));

    expect(mockListCabinetItems).toHaveBeenCalled();
  });

  it('renders cabinet items in detail view', async () => {
    mockListCabinetItems.mockResolvedValue({
      data: [cabinetItem],
      pagination: { cursor: null, hasMore: false },
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));

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

  it('adjusts item quantity', async () => {
    mockListCabinetItems.mockResolvedValue({
      data: [cabinetItem],
      pagination: { cursor: null, hasMore: false },
    });
    mockAdjustCabinetItemQuantity.mockResolvedValue({ ...cabinetItem, quantity: 11 });
    mockGetCabinetSummary.mockResolvedValue(emptySummary);

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));

    await waitFor(() => screen.getByTitle('Add 1'));
    await userEvent.click(screen.getByTitle('Add 1'));

    expect(mockAdjustCabinetItemQuantity).toHaveBeenCalledWith('hh1', 'ci-1', { delta: 1 });
  });

  it('shows summary view with medicine data', async () => {
    mockGetCabinetSummary.mockResolvedValue({
      data: [
        {
          medicineId: 'med-1',
          medicineName: 'Metformin',
          medicineStrength: 500,
          medicineStrengthUnit: 'mg',
          medicineForm: 'tablet',
          totalQuantity: 30,
          unit: 'tablet',
          itemCount: 2,
          earliestExpiry: null,
        },
      ],
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => expect(screen.getByText('Metformin')).toBeInTheDocument());
    expect(screen.getByText(/2 items/)).toBeInTheDocument();
  });

  it('expands medicine in summary view', async () => {
    mockGetCabinetSummary.mockResolvedValue({
      data: [
        {
          medicineId: 'med-1',
          medicineName: 'Metformin',
          medicineStrength: 500,
          medicineStrengthUnit: 'mg',
          medicineForm: 'tablet',
          totalQuantity: 30,
          unit: 'tablet',
          itemCount: 1,
          earliestExpiry: null,
        },
      ],
    });
    mockListCabinetItems.mockResolvedValue({
      data: [cabinetItem],
      pagination: { cursor: null, hasMore: false },
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Metformin'));
    await userEvent.click(screen.getByText('Metformin').closest('button')!);

    await waitFor(() =>
      expect(mockListCabinetItems).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({ medicineId: 'med-1' }),
      ),
    );
  });

  it('submits AddToCabinetForm with medicine selection validation', async () => {
    mockCreateCabinetItem.mockResolvedValue(cabinetItem);

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Add to Cabinet'));
    await userEvent.click(screen.getByText('Add to Cabinet'));

    await waitFor(() => screen.getByPlaceholderText('Search medicines...'));

    // Submit without selecting a medicine - should show error
    fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);

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

  it('submits AddToCabinetForm successfully', async () => {
    mockListMedicines.mockResolvedValue({
      data: [
        {
          _id: 'med-1',
          name: 'Metformin',
          form: 'tablet',
          strength: 500,
          strengthUnit: 'mg',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockCreateCabinetItem.mockResolvedValue(cabinetItem);

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Add to Cabinet'));
    await userEvent.click(screen.getByText('Add to Cabinet'));

    await waitFor(() => screen.getByPlaceholderText('Search medicines...'));

    // Select a medicine (first combobox is the medicine select)
    fireEvent.change(screen.getAllByRole('combobox')[0]!, {
      target: { value: 'med-1' },
    });

    fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);

    await waitFor(() =>
      expect(mockCreateCabinetItem).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({ medicineId: 'med-1' }),
      ),
    );
  });

  it('submits AddToCabinetForm with expiration date and notes', async () => {
    mockListMedicines.mockResolvedValue({
      data: [
        {
          _id: 'med-1',
          name: 'Metformin',
          form: 'tablet',
          strength: 500,
          strengthUnit: 'mg',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockCreateCabinetItem.mockResolvedValue(cabinetItem);

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Add to Cabinet'));
    await userEvent.click(screen.getByText('Add to Cabinet'));

    await waitFor(() => screen.getByPlaceholderText('Search medicines...'));

    // Select a medicine
    fireEvent.change(screen.getAllByRole('combobox')[0]!, {
      target: { value: 'med-1' },
    });

    // Set expiration date (type="date" input, no label association)
    const dateInput = document.querySelector('input[type="date"]') as HTMLElement;
    if (dateInput) fireEvent.change(dateInput, { target: { value: '2027-12-31' } });

    // Set notes
    fireEvent.change(screen.getByPlaceholderText('Any notes about this item'), {
      target: { value: 'Store in fridge' },
    });

    fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);

    await waitFor(() =>
      expect(mockCreateCabinetItem).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({
          medicineId: 'med-1',
          notes: 'Store in fridge',
        }),
      ),
    );
  });

  it('changes quantity and unit in AddToCabinetForm', async () => {
    mockListMedicines.mockResolvedValue({
      data: [
        { _id: 'med-1', name: 'Metformin', form: 'tablet', strength: 500, strengthUnit: 'mg' },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Add to Cabinet'));
    await userEvent.click(screen.getByText('Add to Cabinet'));

    await waitFor(() => screen.getByPlaceholderText('Search medicines...'));

    // Search for medicine
    fireEvent.change(screen.getByPlaceholderText('Search medicines...'), {
      target: { value: 'Met' },
    });

    // Change quantity
    fireEvent.change(screen.getByPlaceholderText('30'), { target: { value: '60' } });

    // Change unit
    fireEvent.change(screen.getAllByRole('combobox')[1]!, { target: { value: 'capsule' } });

    expect(screen.getByPlaceholderText('Search medicines...')).toBeInTheDocument();
  });

  it('switches from detail view back to summary view', async () => {
    mockGetCabinetSummary.mockResolvedValue({
      data: [
        {
          medicineId: 'med-1',
          medicineName: 'Metformin',
          medicineStrength: 500,
          medicineStrengthUnit: 'mg',
          medicineForm: 'tablet',
          totalQuantity: 10,
          unit: 'tablet',
          itemCount: 1,
          earliestExpiry: null,
        },
      ],
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));
    // Now in detail view
    await waitFor(() => screen.getByText('Summary'));
    await userEvent.click(screen.getByText('Summary'));

    // Back in summary view — Metformin should show
    await waitFor(() => expect(screen.getByText('Metformin')).toBeInTheDocument());
  });

  it('shows expiry date for cabinet items', async () => {
    mockListCabinetItems.mockResolvedValue({
      data: [
        {
          ...cabinetItem,
          expirationDate: '2099-12-31T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));

    await waitFor(() => screen.getByText('Metformin'));
    expect(screen.getByText(/days\)/)).toBeInTheDocument();
  });

  it('filters cabinet items by status in detail view', async () => {
    mockListCabinetItems.mockResolvedValue({
      data: [cabinetItem],
      pagination: { cursor: null, hasMore: false },
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));

    await waitFor(() => screen.getByDisplayValue('All Statuses'));
    fireEvent.change(screen.getByDisplayValue('All Statuses'), { target: { value: 'active' } });

    await waitFor(() => expect(mockListCabinetItems).toHaveBeenCalledTimes(2));
  });

  it('takes 1 from cabinet item', async () => {
    mockListCabinetItems.mockResolvedValue({
      data: [cabinetItem],
      pagination: { cursor: null, hasMore: false },
    });
    mockAdjustCabinetItemQuantity.mockResolvedValue({ ...cabinetItem, quantity: 9 });
    mockGetCabinetSummary.mockResolvedValue(emptySummary);

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));

    await waitFor(() => screen.getByTitle('Take 1'));
    await userEvent.click(screen.getByTitle('Take 1'));

    expect(mockAdjustCabinetItemQuantity).toHaveBeenCalledWith('hh1', 'ci-1', { delta: -1 });
  });

  it('collapses expanded medicine when it returns 0 items', async () => {
    mockGetCabinetSummary.mockResolvedValue({
      data: [
        {
          medicineId: 'med-1',
          medicineName: 'Metformin',
          medicineStrength: 500,
          medicineStrengthUnit: 'mg',
          medicineForm: 'tablet',
          totalQuantity: 0,
          unit: 'tablet',
          itemCount: 0,
          earliestExpiry: null,
        },
      ],
    });
    mockListCabinetItems.mockResolvedValue({
      data: [],
      pagination: { cursor: null, hasMore: false },
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Metformin'));
    await userEvent.click(screen.getByText('Metformin').closest('button')!);

    await waitFor(() =>
      expect(mockListCabinetItems).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({ medicineId: 'med-1' }),
      ),
    );
  });

  it('shows error when createCabinetItem fails', async () => {
    mockCreateCabinetItem.mockRejectedValue(new Error('Create failed'));

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Add to Cabinet'));
    await userEvent.click(screen.getByText('Add to Cabinet'));

    await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
    fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);

    // Should show 'Please select a medicine' validation first (since no medicine selected)
    await waitFor(() => expect(screen.getByText('Please select a medicine')).toBeInTheDocument());
  });

  it('deletes cabinet item after confirmation', async () => {
    mockListCabinetItems.mockResolvedValue({
      data: [cabinetItem],
      pagination: { cursor: null, hasMore: false },
    });
    mockDeleteCabinetItem.mockResolvedValue({});
    vi.spyOn(window, 'confirm').mockReturnValue(true);

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));

    await waitFor(() => screen.getByTitle('Delete'));
    await userEvent.click(screen.getByTitle('Delete'));

    expect(mockDeleteCabinetItem).toHaveBeenCalledWith('hh1', 'ci-1');
  });

  it('shows error when adjust fails', async () => {
    mockListCabinetItems.mockResolvedValue({
      data: [cabinetItem],
      pagination: { cursor: null, hasMore: false },
    });
    mockAdjustCabinetItemQuantity.mockRejectedValue(new Error('Adjust failed'));

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));

    await waitFor(() => screen.getByTitle('Add 1'));
    await userEvent.click(screen.getByTitle('Add 1'));

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

  it('shows error when delete fails', async () => {
    mockListCabinetItems.mockResolvedValue({
      data: [cabinetItem],
      pagination: { cursor: null, hasMore: false },
    });
    mockDeleteCabinetItem.mockRejectedValue(new Error('Delete failed'));
    vi.spyOn(window, 'confirm').mockReturnValue(true);

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));

    await waitFor(() => screen.getByTitle('Delete'));
    await userEvent.click(screen.getByTitle('Delete'));

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

  it('cancels AddToCabinetForm with internal Cancel button', async () => {
    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Add to Cabinet'));
    await userEvent.click(screen.getByText('Add to Cabinet'));

    await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
    // Click the Cancel button inside the form (not the toggle button)
    const cancelButtons = screen.getAllByText('Cancel');
    await userEvent.click(cancelButtons[cancelButtons.length - 1]!);

    expect(screen.queryByPlaceholderText('Search medicines...')).not.toBeInTheDocument();
  });

  it('collapses expanded medicine when clicked again', async () => {
    const summary = {
      medicineId: 'med-1',
      medicineName: 'Metformin',
      medicineStrength: 500,
      medicineStrengthUnit: 'mg',
      medicineForm: 'tablet',
      totalQuantity: 10,
      unit: 'tablet',
      itemCount: 1,
      earliestExpiry: null,
    };
    mockGetCabinetSummary.mockResolvedValue({ data: [summary] });
    mockListCabinetItems.mockResolvedValue({
      data: [cabinetItem],
      pagination: { cursor: null, hasMore: false },
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Metformin'));
    // First click expands — handleExpand calls listCabinetItems
    await userEvent.click(screen.getByText('Metformin').closest('button')!);
    await waitFor(() => expect(mockListCabinetItems).toHaveBeenCalledTimes(1));
    const callsAfterExpand = mockListCabinetItems.mock.calls.length;

    // Second click collapses — no additional listCabinetItems calls
    await userEvent.click(screen.getByText('Metformin').closest('button')!);
    await waitFor(() => expect(screen.queryByTitle('Take 1')).not.toBeInTheDocument());

    expect(mockListCabinetItems.mock.calls.length).toBe(callsAfterExpand);
  });

  it('shows empty items when expand fails', async () => {
    mockGetCabinetSummary.mockResolvedValue({
      data: [
        {
          medicineId: 'med-1',
          medicineName: 'Metformin',
          medicineStrength: 500,
          medicineStrengthUnit: 'mg',
          medicineForm: 'tablet',
          totalQuantity: 10,
          unit: 'tablet',
          itemCount: 1,
          earliestExpiry: null,
        },
      ],
    });
    mockListCabinetItems.mockRejectedValue(new Error('Expand failed'));

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Metformin'));
    await userEvent.click(screen.getByText('Metformin').closest('button')!);

    await waitFor(() => expect(mockListCabinetItems).toHaveBeenCalledTimes(1));
    // No items should be shown (empty after error)
    expect(screen.queryByTitle('Take 1')).not.toBeInTheDocument();
  });

  it('shows create error when medicine is selected and create fails', async () => {
    mockListMedicines.mockResolvedValue({
      data: [
        { _id: 'med-1', name: 'Metformin', form: 'tablet', strength: 500, strengthUnit: 'mg' },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockCreateCabinetItem.mockRejectedValue(new Error('Create failed'));

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Add to Cabinet'));
    await userEvent.click(screen.getByText('Add to Cabinet'));

    await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
    fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
    fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);

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

  it('shows item notes in detail view', async () => {
    mockListCabinetItems.mockResolvedValue({
      data: [{ ...cabinetItem, notes: 'Store in fridge' }],
      pagination: { cursor: null, hasMore: false },
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('All Items'));
    await userEvent.click(screen.getByText('All Items'));

    await waitFor(() => expect(screen.getByText('Store in fridge')).toBeInTheDocument());
  });

  it('waits for medicines to load then selects medicine in form', async () => {
    mockListMedicines.mockResolvedValue({
      data: [
        { _id: 'med-1', name: 'Metformin', form: 'tablet', strength: 500, strengthUnit: 'mg' },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<CabinetTab householdId="hh1" />, { wrapper });
    await userEvent.click(screen.getByText('Add to Cabinet'));

    // Wait for medicine options to load (covers medicines.map callback)
    await waitFor(() =>
      expect(
        (screen.getAllByRole('combobox')[0] as HTMLSelectElement).options.length,
      ).toBeGreaterThan(1),
    );

    // Select a valid medicine (true branch: medForm is set → defaultUnitForForm called)
    fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });

    // Select empty value (false branch: selectedMed undefined → DosageUnit.TABLET fallback)
    fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: '' } });

    expect(screen.getByPlaceholderText('Search medicines...')).toBeInTheDocument();
  });

  it('shows fallback error when non-Error is thrown during create', async () => {
    mockListMedicines.mockResolvedValue({
      data: [
        { _id: 'med-1', name: 'Metformin', form: 'tablet', strength: 500, strengthUnit: 'mg' },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockCreateCabinetItem.mockRejectedValue('unexpected');

    render(<CabinetTab householdId="hh1" />, { wrapper });

    await waitFor(() => screen.getByText('Add to Cabinet'));
    await userEvent.click(screen.getByText('Add to Cabinet'));

    await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
    fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
    fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);

    await waitFor(() => expect(screen.getByText('Failed to add item')).toBeInTheDocument());
  });
});
