import React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useConfig, getDefaultsFromConfigSchema, type Order, type Patient } from '@openmrs/esm-framework';
import { configSchema, type Config } from '../../config-schema';
import { useLabOrders } from '../../laboratory.resource';
import OrdersDataTable from './orders-data-table.component';

vi.mock('../../laboratory.resource', () => ({
  useLabOrders: vi.fn(),
}));

const mockUseConfig = vi.mocked(useConfig<Config>);
const mockUseLabOrders = vi.mocked(useLabOrders);

function mockUseLabOrdersImplementation(props: Parameters<typeof useLabOrders>[0]) {
  const mockPatient1: Partial<Patient> = {
    uuid: 'patient-uuid-1',
    display: 'PAT-001 - Pete Seeger',
    identifiers: props.includePatientId
      ? [
          {
            uuid: 'identifier-uuid-1',
            identifier: 'PAT-001',
            preferred: true,
            voided: false,
            identifierType: {
              uuid: 'identifier-type-uuid-1',
            },
          },
          {
            uuid: 'identifier-uuid-2',
            identifier: 'BAD-ID-NOT-PREFERRED',
            preferred: false,
            voided: false,
            identifierType: {
              uuid: 'identifier-type-uuid-1',
            },
          },
        ]
      : undefined,
    person: {
      uuid: 'person-uuid-1',
      display: 'Pete Seeger',
      age: 70,
      gender: 'M',
    },
  };
  const mockPatient2: Partial<Patient> = {
    uuid: 'patient-uuid-2',
    display: 'PAT-002 - Bob Dylan',
    identifiers: props.includePatientId
      ? [
          {
            uuid: 'identifier-uuid-3',
            identifier: 'BAD-ID-WRONG-TYPE',
            preferred: true,
            voided: false,
            identifierType: {
              uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
            },
          },
          {
            uuid: 'identifier-uuid-4',
            identifier: 'PAT-002',
            preferred: true,
            voided: false,
            identifierType: {
              uuid: 'identifier-type-uuid-1',
            },
          },
        ]
      : undefined,
    person: {
      uuid: 'person-uuid-2',
      display: 'Bob Dylan',
      age: 60,
      gender: 'M',
    },
  };

  const mockOrderer = {
    uuid: 'orderer-uuid-1',
    display: 'Dr. John Doe',
    person: {
      display: 'Dr. John Doe',
    },
  };

  const labOrders = [
    {
      uuid: 'order-uuid-1',
      orderNumber: 'ORD-001',
      patient: mockPatient1 as Patient,
      dateActivated: '2021-01-01',
      fulfillerStatus: 'RECEIVED',
      urgency: 'STAT',
      orderer: mockOrderer,
      instructions: 'Inspect banjo & check tuning',
      fulfillerComment: null,
      display: 'Banjo Inspection',
    },
    {
      uuid: 'order-uuid-2',
      orderNumber: 'ORD-002',
      patient: mockPatient1 as Patient,
      dateActivated: '2021-01-01',
      fulfillerStatus: 'RECEIVED',
      urgency: 'ROUTINE',
      orderer: mockOrderer,
      instructions: 'Give it a strum',
      fulfillerComment: null,
      display: 'Guitar Inspection',
    },
    {
      uuid: 'order-uuid-3',
      orderNumber: 'ORD-003',
      patient: mockPatient2 as Patient,
      dateActivated: '2021-01-01',
      fulfillerStatus: 'RECEIVED',
      urgency: 'ROUTINE',
      orderer: mockOrderer,
      instructions: 'Make some noise',
      fulfillerComment: null,
      display: 'Sound Check',
    },
  ]
    .filter((order) => !props.status || order.fulfillerStatus === props.status)
    .filter((order) => !props.excludeCanceled || order.fulfillerStatus !== 'CANCELLED') as Array<Order>;
  return {
    labOrders,
    isLoading: false,
    isError: false,
    mutate: vi.fn(),
    isValidating: false,
  };
}

describe('OrdersDataTable', () => {
  beforeEach(() => {
    mockUseLabOrders.mockImplementation(mockUseLabOrdersImplementation);
  });

  it('should render one row per patient and show lab details', async () => {
    mockUseConfig.mockReturnValue({
      ...getDefaultsFromConfigSchema(configSchema),
    });

    render(<OrdersDataTable />);
    const table = screen.getByRole('table');
    expect(table).toBeInTheDocument();
    const rows = screen.getAllByRole('row');
    expect(rows).toHaveLength(5);
    const dataRows = rows.slice(1).filter((row) => !row.classList.contains('hiddenRow'));
    expect(dataRows).toHaveLength(2);
    const headerRow = rows[0];
    expect(headerRow).toHaveTextContent('Patient');
    expect(headerRow).toHaveTextContent('Age');
    expect(headerRow).toHaveTextContent('Sex');
    expect(headerRow).toHaveTextContent('Total Orders');
    expect(headerRow).toHaveTextContent('Urgency');
    const row1 = dataRows[0];
    expect(row1).toHaveTextContent('Pete Seeger');
    expect(row1).toHaveTextContent('70');
    expect(row1).toHaveTextContent('M');
    expect(row1).toHaveTextContent('2');
    expect(row1).toHaveTextContent('1 Stat');
    expect(row1).toHaveTextContent('1 Routine');
    // STAT should be sorted before ROUTINE
    expect(row1.innerHTML.indexOf('Stat')).toBeLessThan(row1.innerHTML.indexOf('Routine'));
    const row2 = dataRows[1];
    expect(row2).toHaveTextContent('Bob Dylan');
    expect(row2).toHaveTextContent('60');
    expect(row2).toHaveTextContent('M');
    expect(row2).toHaveTextContent('1');
    expect(row2).toHaveTextContent('1 Routine');

    const user = userEvent.setup();
    await user.click(within(row1).getByLabelText('Expand current row'));

    const orderDetailsTables = within(table).getAllByRole('table');
    expect(orderDetailsTables).toHaveLength(2);
    const orderDetailsTable1 = orderDetailsTables[0];
    const orderDetailsTable2 = orderDetailsTables[1];
    expect(orderDetailsTable1).toHaveTextContent('Banjo Inspection');
    expect(orderDetailsTable1).toHaveTextContent('Inspect banjo & check tuning');
    expect(orderDetailsTable1).toHaveTextContent('Dr. John Doe');
    expect(orderDetailsTable1).toHaveTextContent('01-Jan-2021');
    expect(orderDetailsTable1).toHaveTextContent('Received');
    expect(orderDetailsTable1).toHaveTextContent('Stat');
    expect(orderDetailsTable2).toHaveTextContent('Guitar Inspection');
    expect(orderDetailsTable2).toHaveTextContent('Give it a strum');
    expect(orderDetailsTable2).toHaveTextContent('Dr. John Doe');
    expect(orderDetailsTable2).toHaveTextContent('01-Jan-2021');
    expect(orderDetailsTable2).toHaveTextContent('Received');
    expect(orderDetailsTable2).toHaveTextContent('Routine');
  });

  it('renders the toolbar search at the same large size as the date range picker', () => {
    mockUseConfig.mockReturnValue({
      ...getDefaultsFromConfigSchema(configSchema),
    });

    render(<OrdersDataTable />);

    const search = screen.getByPlaceholderText('Search this list').closest('.cds--search');
    expect(search).toHaveClass('cds--search--lg');
    expect(search).not.toHaveClass('cds--search--sm');
  });

  it('applies the lab toolbar sizing class to the toolbar search', () => {
    mockUseConfig.mockReturnValue({
      ...getDefaultsFromConfigSchema(configSchema),
    });

    render(<OrdersDataTable />);

    expect(screen.getByPlaceholderText('Search this list').closest('.cds--search')).toHaveClass('toolbarSearch');
  });

  it('should render an empty urgency cell when all orders have null urgency', () => {
    mockUseLabOrders.mockReturnValueOnce({
      labOrders: [
        {
          uuid: 'order-uuid-null',
          orderNumber: 'ORD-NULL',
          patient: {
            uuid: 'patient-uuid-3',
            display: 'PAT-003 - Jane Smith',
            person: { uuid: 'person-uuid-3', display: 'Jane Smith', age: 30, gender: 'F' },
          } as Patient,
          dateActivated: '2021-01-01',
          fulfillerStatus: 'RECEIVED',
          urgency: null,
          orderer: { uuid: 'orderer-uuid-1', display: 'Dr. John Doe', person: { display: 'Dr. John Doe' } },
          instructions: '',
          fulfillerComment: null,
          display: 'Null Urgency Test',
        },
      ] as unknown as Array<Order>,
      isLoading: false,
      isError: false,
      mutate: vi.fn(),
      isValidating: false,
    });
    mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema) });

    render(<OrdersDataTable />);
    const rows = screen.getAllByRole('row');
    const dataRows = rows.slice(1).filter((row) => !row.classList.contains('hiddenRow'));
    expect(dataRows).toHaveLength(1);
    expect(within(dataRows[0]).queryByText(/Routine|Stat|Scheduled/i)).not.toBeInTheDocument();
  });

  it('should render ON_SCHEDULED_DATE urgency as "Scheduled"', () => {
    mockUseLabOrders.mockReturnValueOnce({
      labOrders: [
        {
          uuid: 'order-uuid-scheduled',
          orderNumber: 'ORD-SCHED',
          patient: {
            uuid: 'patient-uuid-3',
            display: 'PAT-003 - Jane Smith',
            person: { uuid: 'person-uuid-3', display: 'Jane Smith', age: 45, gender: 'F' },
          } as Patient,
          dateActivated: '2021-01-01',
          fulfillerStatus: 'RECEIVED',
          urgency: 'ON_SCHEDULED_DATE',
          orderer: { uuid: 'orderer-uuid-1', display: 'Dr. John Doe', person: { display: 'Dr. John Doe' } },
          instructions: '',
          fulfillerComment: null,
          display: 'Scheduled Test',
        },
      ] as unknown as Array<Order>,
      isLoading: false,
      isError: false,
      mutate: vi.fn(),
      isValidating: false,
    });
    mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema) });

    render(<OrdersDataTable />);
    const rows = screen.getAllByRole('row');
    const dataRows = rows.slice(1).filter((row) => !row.classList.contains('hiddenRow'));
    expect(dataRows).toHaveLength(1);
    expect(dataRows[0]).toHaveTextContent('1 Scheduled');
  });

  it('should show patient identifier if it is configured', () => {
    mockUseConfig.mockReturnValue({
      ...getDefaultsFromConfigSchema(configSchema),
      labTableColumns: ['patientId', 'age', 'totalOrders'],
      patientIdIdentifierTypeUuid: 'identifier-type-uuid-1',
    });
    render(<OrdersDataTable />);
    const rows = screen.getAllByRole('row');
    expect(rows).toHaveLength(5);
    const dataRows = rows.slice(1).filter((row) => !row.classList.contains('hiddenRow'));
    expect(dataRows).toHaveLength(2);
    const row1 = dataRows[0];
    expect(row1).toHaveTextContent('PAT-001');
    expect(row1).toHaveTextContent('70');
    expect(row1).toHaveTextContent('2');
    const row2 = dataRows[1];
    expect(row2).toHaveTextContent('PAT-002');
    expect(row2).toHaveTextContent('60');
    expect(row2).toHaveTextContent('1');
    expect(screen.queryByText(/BAD-ID/)).not.toBeInTheDocument();
  });
});

describe('patient identifier column', () => {
  const TYPE_UUID = 'identifier-type-uuid-1';
  const OTHER_UUID = 'other-type-uuid';

  const baseConfig: Config = {
    ...(getDefaultsFromConfigSchema(configSchema) as Config),
    labTableColumns: ['patientId', 'age', 'totalOrders'],
    patientIdIdentifierTypeUuid: TYPE_UUID,
  };

  const makeOrder = (identifiers: Patient['identifiers']) => ({
    labOrders: [
      {
        uuid: 'order-uuid-x',
        orderNumber: 'ORD-X',
        patient: {
          uuid: 'patient-uuid-x',
          display: 'Test Patient',
          identifiers,
          person: { uuid: 'person-uuid-x', display: 'Test Patient', age: 40, gender: 'F' },
        } as Patient,
        dateActivated: '2021-01-01',
        fulfillerStatus: 'RECEIVED',
        urgency: 'ROUTINE',
        orderer: { uuid: 'orderer-uuid-x', display: 'Dr. Test', person: { display: 'Dr. Test' } },
        instructions: '',
        fulfillerComment: null,
        display: 'Test Order',
      },
    ] as unknown as Array<Order>,
    isLoading: false,
    isError: false,
    mutate: vi.fn(),
    isValidating: false,
  });

  it('shows "--" when the patient has no identifiers', () => {
    mockUseLabOrders.mockReturnValueOnce(makeOrder(undefined));
    mockUseConfig.mockReturnValue(baseConfig);

    render(<OrdersDataTable />);
    const row = screen
      .getAllByRole('row')
      .slice(1)
      .find((r) => !r.classList.contains('hiddenRow'));
    expect(row).toHaveTextContent('--');
  });

  it('shows "--" when no identifier matches the configured type UUID and there is no preferred fallback', () => {
    mockUseLabOrders.mockReturnValueOnce(
      makeOrder([
        {
          uuid: 'id-1',
          identifier: 'WRONG-TYPE',
          preferred: false,
          voided: false,
          identifierType: { uuid: OTHER_UUID },
        },
      ]),
    );
    mockUseConfig.mockReturnValue(baseConfig);

    render(<OrdersDataTable />);
    const row = screen
      .getAllByRole('row')
      .slice(1)
      .find((r) => !r.classList.contains('hiddenRow'));
    expect(row).toHaveTextContent('--');
    expect(row).not.toHaveTextContent('WRONG-TYPE');
  });

  it('shows the configured-type identifier when usePreferredPatientIdentifier is false', () => {
    mockUseLabOrders.mockReturnValueOnce(
      makeOrder([
        {
          uuid: 'id-1',
          identifier: 'NON-PREF-MATCH',
          preferred: false,
          voided: false,
          identifierType: { uuid: TYPE_UUID },
        },
        {
          uuid: 'id-2',
          identifier: 'PREF-NO-MATCH',
          preferred: true,
          voided: false,
          identifierType: { uuid: OTHER_UUID },
        },
      ]),
    );
    mockUseConfig.mockReturnValue({ ...baseConfig, usePreferredPatientIdentifier: false });

    render(<OrdersDataTable />);
    const row = screen
      .getAllByRole('row')
      .slice(1)
      .find((r) => !r.classList.contains('hiddenRow'));
    expect(row).toHaveTextContent('NON-PREF-MATCH');
    expect(row).not.toHaveTextContent('PREF-NO-MATCH');
  });

  it('shows only the preferred matching identifier when usePreferredPatientIdentifier is true', () => {
    mockUseLabOrders.mockReturnValueOnce(
      makeOrder([
        {
          uuid: 'id-1',
          identifier: 'NON-PREF-MATCH',
          preferred: false,
          voided: false,
          identifierType: { uuid: TYPE_UUID },
        },
        { uuid: 'id-2', identifier: 'PREF-MATCH', preferred: true, voided: false, identifierType: { uuid: TYPE_UUID } },
      ]),
    );
    mockUseConfig.mockReturnValue({ ...baseConfig, usePreferredPatientIdentifier: true });

    render(<OrdersDataTable />);
    const row = screen
      .getAllByRole('row')
      .slice(1)
      .find((r) => !r.classList.contains('hiddenRow'));
    expect(row).toHaveTextContent('PREF-MATCH');
    expect(row).not.toHaveTextContent('NON-PREF-MATCH');
  });

  it('shows "--" when usePreferredPatientIdentifier is true but no preferred identifier matches the type', () => {
    mockUseLabOrders.mockReturnValueOnce(
      makeOrder([
        {
          uuid: 'id-1',
          identifier: 'NON-PREF-MATCH',
          preferred: false,
          voided: false,
          identifierType: { uuid: TYPE_UUID },
        },
      ]),
    );
    mockUseConfig.mockReturnValue({ ...baseConfig, usePreferredPatientIdentifier: true });

    render(<OrdersDataTable />);
    const row = screen
      .getAllByRole('row')
      .slice(1)
      .find((r) => !r.classList.contains('hiddenRow'));
    expect(row).toHaveTextContent('--');
    expect(row).not.toHaveTextContent('NON-PREF-MATCH');
  });

  it('does not show voided identifiers even if they match the type UUID', () => {
    mockUseLabOrders.mockReturnValueOnce(
      makeOrder([
        {
          uuid: 'id-1',
          identifier: 'VOIDED-MATCH',
          preferred: true,
          voided: true,
          identifierType: { uuid: TYPE_UUID },
        },
      ]),
    );
    mockUseConfig.mockReturnValue(baseConfig);

    render(<OrdersDataTable />);
    const row = screen
      .getAllByRole('row')
      .slice(1)
      .find((r) => !r.classList.contains('hiddenRow'));
    expect(row).toHaveTextContent('--');
    expect(row).not.toHaveTextContent('VOIDED-MATCH');
  });

  it('falls back to the preferred identifier when no identifier matches the configured type UUID', () => {
    mockUseLabOrders.mockReturnValueOnce(
      makeOrder([
        {
          uuid: 'id-1',
          identifier: 'PREF-FALLBACK',
          preferred: true,
          voided: false,
          identifierType: { uuid: OTHER_UUID },
        },
      ]),
    );
    mockUseConfig.mockReturnValue({ ...baseConfig, usePreferredPatientIdentifier: false });

    render(<OrdersDataTable />);
    const row = screen
      .getAllByRole('row')
      .slice(1)
      .find((r) => !r.classList.contains('hiddenRow'));
    expect(row).toHaveTextContent('PREF-FALLBACK');
  });

  it('shows the type-UUID match and not the preferred-fallback when both exist', () => {
    mockUseLabOrders.mockReturnValueOnce(
      makeOrder([
        {
          uuid: 'id-1',
          identifier: 'PREF-OTHER',
          preferred: true,
          voided: false,
          identifierType: { uuid: OTHER_UUID },
        },
        {
          uuid: 'id-2',
          identifier: 'CONFIGURED',
          preferred: false,
          voided: false,
          identifierType: { uuid: TYPE_UUID },
        },
      ]),
    );
    mockUseConfig.mockReturnValue({ ...baseConfig, usePreferredPatientIdentifier: false });

    render(<OrdersDataTable />);
    const row = screen
      .getAllByRole('row')
      .slice(1)
      .find((r) => !r.classList.contains('hiddenRow'));
    expect(row).toHaveTextContent('CONFIGURED');
    expect(row).not.toHaveTextContent('PREF-OTHER');
  });
});
