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 { mockListMedicines, mockCreateMedicine, mockDeleteMedicine } = vi.hoisted(() => ({
  mockListMedicines: vi.fn(),
  mockCreateMedicine: vi.fn(),
  mockDeleteMedicine: vi.fn(),
}));

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

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

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

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

const medicine = {
  _id: 'med-1',
  name: 'Metformin',
  form: 'tablet',
  strength: 500,
  strengthUnit: 'mg',
  category: 'prescription',
  tags: [],
};

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

describe('LibraryTab', () => {
  it('shows loading skeleton initially', () => {
    mockListMedicines.mockReturnValue(new Promise(() => {}));
    render(<LibraryTab householdId="hh1" />);
    expect(screen.queryByText('Metformin')).not.toBeInTheDocument();
  });

  it('renders medicine list after load', async () => {
    mockListMedicines.mockResolvedValue({
      data: [medicine],
      pagination: { cursor: null, hasMore: false },
    });

    render(<LibraryTab householdId="hh1" />);

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

  it('shows empty state when no medicines', async () => {
    mockListMedicines.mockResolvedValue(emptyResponse);

    render(<LibraryTab householdId="hh1" />);

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

  it('shows error when list fails', async () => {
    mockListMedicines.mockRejectedValue(new Error('Failed to load'));

    render(<LibraryTab householdId="hh1" />);

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

  it('dismisses error on Dismiss click', async () => {
    mockListMedicines.mockRejectedValue(new Error('Failed to load'));

    render(<LibraryTab householdId="hh1" />);

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

  it('toggles Add Medicine form', async () => {
    mockListMedicines.mockResolvedValue(emptyResponse);

    render(<LibraryTab householdId="hh1" />);

    await waitFor(() => screen.getByText('Add Medicine'));
    await userEvent.click(screen.getByText('Add Medicine'));
    expect(screen.getByPlaceholderText('e.g. Metformin')).toBeInTheDocument();

    await userEvent.click(screen.getAllByText('Cancel')[0]);
    expect(screen.queryByPlaceholderText('e.g. Metformin')).not.toBeInTheDocument();
  });

  it('creates medicine and refreshes list', async () => {
    mockListMedicines.mockResolvedValue(emptyResponse);
    mockCreateMedicine.mockResolvedValue({ _id: 'med-2', name: 'Aspirin' });

    render(<LibraryTab householdId="hh1" />);

    await userEvent.click(screen.getByText('Add Medicine'));

    await userEvent.type(screen.getByPlaceholderText('e.g. Metformin'), 'Aspirin');
    await userEvent.type(screen.getByPlaceholderText('500'), '100');
    await userEvent.click(screen.getByRole('button', { name: 'Create Medicine' }));

    await waitFor(() =>
      expect(mockCreateMedicine).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({ name: 'Aspirin' }),
      ),
    );
  });

  it('deletes medicine after confirmation', async () => {
    mockListMedicines.mockResolvedValue({
      data: [medicine],
      pagination: { cursor: null, hasMore: false },
    });
    mockDeleteMedicine.mockResolvedValue({});
    vi.spyOn(window, 'confirm').mockReturnValue(true);

    render(<LibraryTab householdId="hh1" />);

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

    expect(mockDeleteMedicine).toHaveBeenCalledWith('hh1', 'med-1');
  });

  it('changes form fields in add medicine form', async () => {
    mockListMedicines.mockResolvedValue(emptyResponse);

    render(<LibraryTab householdId="hh1" />);

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

    await waitFor(() => screen.getByPlaceholderText('e.g. Metformin'));

    // Change form type
    fireEvent.change(screen.getByDisplayValue('Tablet'), { target: { value: 'capsule' } });
    // Change strength unit
    fireEvent.change(screen.getByDisplayValue('mg'), { target: { value: 'mcg' } });
    // Change category
    fireEvent.change(screen.getByDisplayValue('OTC'), { target: { value: 'prescription' } });
    // Change notes (covers truthy branch)
    fireEvent.change(screen.getByPlaceholderText('Any additional notes'), {
      target: { value: 'test notes' },
    });
    // Clear notes (covers falsy branch → undefined)
    fireEvent.change(screen.getByPlaceholderText('Any additional notes'), {
      target: { value: '' },
    });

    // Verify form is still visible
    expect(screen.getByPlaceholderText('e.g. Metformin')).toBeInTheDocument();
  });

  it('filters medicines by search, category, and form', async () => {
    mockListMedicines.mockResolvedValue({
      data: [medicine],
      pagination: { cursor: null, hasMore: false },
    });

    render(<LibraryTab householdId="hh1" />);

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

    // Search filter
    fireEvent.change(screen.getByPlaceholderText('Search medicines...'), {
      target: { value: 'met' },
    });

    // Category filter
    fireEvent.change(screen.getByDisplayValue('All Categories'), {
      target: { value: 'prescription' },
    });

    // Form filter
    fireEvent.change(screen.getByDisplayValue('All Forms'), { target: { value: 'tablet' } });

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

  it('shows fallback error when non-Error is thrown on delete', async () => {
    mockListMedicines.mockResolvedValue({
      data: [medicine],
      pagination: { cursor: null, hasMore: false },
    });
    mockDeleteMedicine.mockRejectedValue('oops');
    vi.spyOn(window, 'confirm').mockReturnValue(true);

    render(<LibraryTab householdId="hh1" />);

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

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

  it('cancels CreateMedicineForm with internal Cancel button', async () => {
    mockListMedicines.mockResolvedValue(emptyResponse);

    render(<LibraryTab householdId="hh1" />);

    await waitFor(() => screen.getByText('Add Medicine'));
    await userEvent.click(screen.getByText('Add Medicine'));
    await waitFor(() => screen.getByPlaceholderText('e.g. Metformin'));

    const cancelButtons = screen.getAllByText('Cancel');
    await userEvent.click(cancelButtons[cancelButtons.length - 1]!);

    expect(screen.queryByPlaceholderText('e.g. Metformin')).not.toBeInTheDocument();
  });

  it('shows fallback error when non-Error is thrown on create medicine', async () => {
    mockListMedicines.mockResolvedValue(emptyResponse);
    mockCreateMedicine.mockRejectedValue('create failed');

    render(<LibraryTab householdId="hh1" />);

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

    await waitFor(() => screen.getByPlaceholderText('e.g. Metformin'));
    await userEvent.type(screen.getByPlaceholderText('e.g. Metformin'), 'Aspirin');
    await userEvent.type(screen.getByPlaceholderText('500'), '100');
    await userEvent.click(screen.getByRole('button', { name: 'Create Medicine' }));

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

  it('does not delete medicine if confirmation cancelled', async () => {
    mockListMedicines.mockResolvedValue({
      data: [medicine],
      pagination: { cursor: null, hasMore: false },
    });
    vi.spyOn(window, 'confirm').mockReturnValue(false);

    render(<LibraryTab householdId="hh1" />);

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

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