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

const { mockListFills, mockPreviewFill, mockExecuteFill, mockUndoFill } = vi.hoisted(() => ({
  mockListFills: vi.fn(),
  mockPreviewFill: vi.fn(),
  mockExecuteFill: vi.fn(),
  mockUndoFill: vi.fn(),
}));

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

vi.mock('@/services/organizer', () => ({
  listFills: mockListFills,
  previewFill: mockPreviewFill,
  executeFill: mockExecuteFill,
  undoFill: mockUndoFill,
}));

vi.mock('@/services/regimens', () => ({ listRegimens: mockListRegimens }));

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

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

const activeRegimen = {
  _id: 'reg-1',
  name: 'Morning Routine',
  isActive: true,
  medications: [],
  createdAt: '2026-01-01T00:00:00.000Z',
};

beforeEach(() => {
  vi.clearAllMocks();
  mockListRegimens.mockResolvedValue(emptyRegimens);
  mockListFills.mockResolvedValue(emptyFills);
});

describe('OrganizerTab', () => {
  it('shows fill organizer and fill history sections', async () => {
    render(<OrganizerTab householdId="hh1" />);

    await waitFor(() => expect(screen.getByText('Fill Pill Organizer')).toBeInTheDocument());
    expect(screen.getByText('Fill History')).toBeInTheDocument();
  });

  it('shows no active regimens message when none exist', async () => {
    render(<OrganizerTab householdId="hh1" />);

    await waitFor(() => expect(screen.getByText(/No active regimens found/)).toBeInTheDocument());
  });

  it('shows fill form when active regimens exist', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });

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

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

  it('shows empty fill history', async () => {
    render(<OrganizerTab householdId="hh1" />);

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

  it('renders fill history entries', async () => {
    mockListFills.mockResolvedValue({
      data: [
        {
          _id: 'fill-1',
          regimenId: 'reg-1',
          regimenName: 'Morning Routine',
          status: 'completed',
          numberOfDays: 7,
          items: [],
          createdAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

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

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

  it('previews fill and shows preview result', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });
    mockPreviewFill.mockResolvedValue({
      regimenId: 'reg-1',
      numberOfDays: 7,
      hasShortages: false,
      items: [],
    });

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

    await waitFor(() => screen.getByText('Preview fill'));

    fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
      target: { value: 'reg-1' },
    });
    fireEvent.change(screen.getByDisplayValue('7'), { target: { value: '14' } });
    fireEvent.submit(screen.getByText('Preview fill').closest('form')!);

    await waitFor(() =>
      expect(mockPreviewFill).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({ regimenId: 'reg-1' }),
      ),
    );
  });

  it('shows preview result and executes fill', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });
    mockPreviewFill.mockResolvedValue({
      regimenId: 'reg-1',
      regimenName: 'Morning Routine',
      numberOfDays: 7,
      hasShortages: false,
      items: [
        {
          medicineId: 'med-1',
          medicineName: 'Metformin',
          quantityNeeded: 7,
          quantityAvailable: 30,
          isShort: false,
          shortage: 0,
          cabinetBreakdown: [],
        },
      ],
    });
    mockExecuteFill.mockResolvedValue({ _id: 'fill-new', status: 'completed' });

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

    await waitFor(() => screen.getByText('Preview fill'));
    fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
      target: { value: 'reg-1' },
    });
    fireEvent.submit(screen.getByText('Preview fill').closest('form')!);

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

    await userEvent.click(screen.getByText('Confirm fill'));

    await waitFor(() =>
      expect(mockExecuteFill).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({ regimenId: 'reg-1' }),
      ),
    );
  });

  it('shows shortage warning in preview', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });
    mockPreviewFill.mockResolvedValue({
      regimenId: 'reg-1',
      regimenName: 'Morning Routine',
      numberOfDays: 7,
      hasShortages: true,
      items: [
        {
          medicineId: 'med-1',
          medicineName: 'Metformin',
          quantityNeeded: 7,
          quantityAvailable: 3,
          isShort: true,
          shortage: 4,
          cabinetBreakdown: [],
        },
      ],
    });

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

    await waitFor(() => screen.getByText('Preview fill'));
    fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
      target: { value: 'reg-1' },
    });
    fireEvent.submit(screen.getByText('Preview fill').closest('form')!);

    await waitFor(() => screen.getByText('Shortages detected'));
    expect(screen.getByText('Allow partial fill (fill what is available)')).toBeInTheDocument();
  });

  it('shows undo error when undo fails', async () => {
    mockListFills.mockResolvedValue({
      data: [
        {
          _id: 'fill-1',
          regimenId: 'reg-1',
          regimenName: 'Morning Routine',
          status: 'completed',
          numberOfDays: 7,
          items: [],
          createdAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockUndoFill.mockRejectedValue(new Error('Undo failed'));
    vi.spyOn(window, 'confirm').mockReturnValue(true);

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

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

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

  it('filters fill history by status', async () => {
    mockListFills.mockResolvedValue({
      data: [
        {
          _id: 'fill-1',
          regimenId: 'reg-1',
          regimenName: 'Morning Routine',
          status: 'completed',
          numberOfDays: 7,
          items: [],
          createdAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

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

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

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

  it('shows Ready to fill when no shortages', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });
    mockPreviewFill.mockResolvedValue({
      regimenId: 'reg-1',
      regimenName: 'Morning Routine',
      numberOfDays: 7,
      hasShortages: false,
      items: [
        {
          medicineId: 'med-1',
          medicineName: 'Metformin',
          quantityNeeded: 7,
          quantityAvailable: 30,
          isShort: false,
          shortage: 0,
          cabinetBreakdown: [],
        },
      ],
    });

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

    await waitFor(() => screen.getByText('Preview fill'));
    fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
      target: { value: 'reg-1' },
    });
    fireEvent.submit(screen.getByText('Preview fill').closest('form')!);

    await waitFor(() => expect(screen.getByText('Ready to fill')).toBeInTheDocument());
  });

  it('renders fill items with short status', async () => {
    mockListFills.mockResolvedValue({
      data: [
        {
          _id: 'fill-1',
          regimenId: 'reg-1',
          regimenName: 'Morning Routine',
          status: 'partial',
          numberOfDays: 7,
          fillDate: '2026-01-01T00:00:00.000Z',
          items: [
            {
              medicineId: 'med-1',
              medicineName: 'Metformin',
              quantityNeeded: 7,
              quantityTaken: 3,
              wasShort: true,
              shortage: 4,
              deductions: [],
            },
          ],
          createdAt: '2026-01-01T00:00:00.000Z',
          updatedAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

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

    await waitFor(() => expect(screen.getByText(/Metformin.*short/)).toBeInTheDocument());
  });

  it('dismisses fill history error', async () => {
    mockListFills.mockRejectedValue(new Error('History error'));

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

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

  it('undoes a fill after confirmation', async () => {
    mockListFills.mockResolvedValue({
      data: [
        {
          _id: 'fill-1',
          regimenId: 'reg-1',
          regimenName: 'Morning Routine',
          status: 'completed',
          numberOfDays: 7,
          items: [],
          createdAt: '2026-01-01T00:00:00.000Z',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockUndoFill.mockResolvedValue({});
    vi.spyOn(window, 'confirm').mockReturnValue(true);

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

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

    expect(mockUndoFill).toHaveBeenCalledWith('hh1', 'fill-1');
  });

  it('cancels from preview screen', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });
    mockPreviewFill.mockResolvedValue({
      regimenId: 'reg-1',
      regimenName: 'Morning Routine',
      numberOfDays: 7,
      hasShortages: false,
      items: [],
    });

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

    await waitFor(() => screen.getByText('Preview fill'));
    fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
      target: { value: 'reg-1' },
    });
    fireEvent.submit(screen.getByText('Preview fill').closest('form')!);

    await waitFor(() => screen.getByText('Confirm fill'));
    await userEvent.click(screen.getByRole('button', { name: 'Back' }));

    // Should return to the form
    await waitFor(() => expect(screen.getByText('Preview fill')).toBeInTheDocument());
  });

  it('shows cabinet breakdown in preview items', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });
    mockPreviewFill.mockResolvedValue({
      regimenId: 'reg-1',
      regimenName: 'Morning Routine',
      numberOfDays: 7,
      hasShortages: false,
      items: [
        {
          medicineId: 'med-1',
          medicineName: 'Metformin',
          quantityNeeded: 7,
          quantityAvailable: 30,
          isShort: false,
          shortage: 0,
          cabinetBreakdown: [
            {
              cabinetItemId: 'ci-1',
              quantityToTake: 7,
              expirationDate: '2027-06-01T00:00:00.000Z',
            },
            { cabinetItemId: 'ci-2', quantityToTake: 3 },
          ],
        },
      ],
    });

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

    await waitFor(() => screen.getByText('Preview fill'));
    fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
      target: { value: 'reg-1' },
    });
    fireEvent.submit(screen.getByText('Preview fill').closest('form')!);

    await waitFor(() => expect(screen.getByText(/7 units/)).toBeInTheDocument());
  });

  it('shows fallback error when non-Error thrown during preview', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });
    mockPreviewFill.mockRejectedValue('preview failed');

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

    await waitFor(() => screen.getByText('Preview fill'));
    fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
      target: { value: 'reg-1' },
    });
    fireEvent.submit(screen.getByText('Preview fill').closest('form')!);

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

  it('shows fallback error when non-Error thrown during fill', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });
    mockPreviewFill.mockResolvedValue({
      regimenId: 'reg-1',
      regimenName: 'Morning Routine',
      numberOfDays: 7,
      hasShortages: false,
      items: [],
    });
    mockExecuteFill.mockRejectedValue('fill failed');

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

    await waitFor(() => screen.getByText('Preview fill'));
    fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
      target: { value: 'reg-1' },
    });
    fireEvent.submit(screen.getByText('Preview fill').closest('form')!);

    await waitFor(() => screen.getByText('Confirm fill'));
    await userEvent.click(screen.getByText('Confirm fill'));

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

  it('toggles allow partial checkbox in shortage preview', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });
    mockPreviewFill.mockResolvedValue({
      regimenId: 'reg-1',
      regimenName: 'Morning Routine',
      numberOfDays: 7,
      hasShortages: true,
      items: [
        {
          medicineId: 'med-1',
          medicineName: 'Metformin',
          quantityNeeded: 7,
          quantityAvailable: 3,
          isShort: true,
          shortage: 4,
          cabinetBreakdown: [],
        },
      ],
    });

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

    await waitFor(() => screen.getByText('Preview fill'));
    fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
      target: { value: 'reg-1' },
    });
    fireEvent.submit(screen.getByText('Preview fill').closest('form')!);

    await waitFor(() => screen.getByText('Allow partial fill (fill what is available)'));
    fireEvent.click(screen.getByLabelText('Allow partial fill (fill what is available)'));

    expect(screen.getByLabelText('Allow partial fill (fill what is available)')).toBeChecked();
  });

  it('changes notes field in the organizer form', async () => {
    mockListRegimens.mockResolvedValue({
      data: [activeRegimen],
      pagination: { cursor: null, hasMore: false },
    });

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

    await waitFor(() => screen.getByPlaceholderText('Any notes for this fill'));
    fireEvent.change(screen.getByPlaceholderText('Any notes for this fill'), {
      target: { value: 'Fill for next week' },
    });

    expect(screen.getByDisplayValue('Fill for next week')).toBeInTheDocument();
  });
});
