import React from 'react';
import { render, WithRoute } from 'lib/testHelpers';
import Table, {
  TableProps,
  TimestampCell,
  SizeCell,
  LinkCell,
  TagCell,
} from 'components/common/NewTable';
import { screen, waitFor } from '@testing-library/dom';
import { ColumnDef, Row } from '@tanstack/react-table';
import userEvent from '@testing-library/user-event';
import { formatTimestamp } from 'lib/dateTimeHelpers';
import { act } from '@testing-library/react';
import { ConnectorState, ConsumerGroupState } from 'generated-sources';

const mockedUsedNavigate = jest.fn();

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: () => mockedUsedNavigate,
}));

type Datum = typeof data[0];

const data = [
  {
    timestamp: 1660034383725,
    text: 'lorem',
    selectable: false,
    size: 1234,
    tag: ConnectorState.RUNNING,
  },
  {
    timestamp: 1660034399999,
    text: 'ipsum',
    selectable: true,
    size: 3,
    tag: ConnectorState.FAILED,
  },
  {
    timestamp: 1660034399922,
    text: 'dolor',
    selectable: true,
    size: 50000,
    tag: ConsumerGroupState.EMPTY,
  },
  {
    timestamp: 1660034199922,
    text: 'sit',
    selectable: false,
    size: 1_312_323,
    tag: 'some_string',
  },
];

const columns: ColumnDef<Datum>[] = [
  {
    header: 'DateTime',
    accessorKey: 'timestamp',
    cell: TimestampCell,
  },
  {
    header: 'Text',
    accessorKey: 'text',
    cell: LinkCell,
  },
  {
    header: 'Size',
    accessorKey: 'size',
    cell: SizeCell,
  },
  {
    header: 'Tag',
    accessorKey: 'tag',
    cell: TagCell,
  },
];

const ExpandedRow: React.FC = () => <div>I am expanded row</div>;

interface Props extends TableProps<Datum> {
  path?: string;
}

const renderComponent = (props: Partial<Props> = {}) => {
  render(
    <WithRoute path="/*">
      <Table
        columns={columns}
        data={data}
        renderSubComponent={ExpandedRow}
        {...props}
      />
    </WithRoute>,
    { initialEntries: [props.path || ''] }
  );
};

describe('Table', () => {
  it('renders table', () => {
    renderComponent();
    expect(screen.getByRole('table')).toBeInTheDocument();
    expect(screen.getAllByRole('row').length).toEqual(data.length + 1);
  });

  it('renders empty table', () => {
    renderComponent({ data: [] });
    expect(screen.getByRole('table')).toBeInTheDocument();
    expect(screen.getAllByRole('row').length).toEqual(2);
    expect(screen.getByText('No rows found')).toBeInTheDocument();
  });

  it('renders empty table with custom message', () => {
    const emptyMessage = 'Super custom message';
    renderComponent({ data: [], emptyMessage });
    expect(screen.getByRole('table')).toBeInTheDocument();
    expect(screen.getAllByRole('row').length).toEqual(2);
    expect(screen.getByText(emptyMessage)).toBeInTheDocument();
  });

  it('renders SizeCell', () => {
    renderComponent();
    expect(screen.getByText('1KB')).toBeInTheDocument();
    expect(screen.getByText('3Bytes')).toBeInTheDocument();
    expect(screen.getByText('49KB')).toBeInTheDocument();
    expect(screen.getByText('1MB')).toBeInTheDocument();
  });

  it('renders TimestampCell', () => {
    renderComponent();
    expect(
      screen.getByText(formatTimestamp(data[0].timestamp))
    ).toBeInTheDocument();
  });

  describe('LinkCell', () => {
    it('renders link', () => {
      renderComponent();
      expect(screen.getByRole('link', { name: 'lorem' })).toBeInTheDocument();
    });

    it('link click stops propagation', () => {
      const onRowClick = jest.fn();
      renderComponent({ onRowClick });
      const link = screen.getByRole('link', { name: 'lorem' });
      userEvent.click(link);
      expect(onRowClick).not.toHaveBeenCalled();
    });
  });

  describe('ExpanderCell', () => {
    it('renders button', () => {
      renderComponent({ getRowCanExpand: () => true });
      const btns = screen.getAllByRole('button', { name: 'Expand row' });
      expect(btns.length).toEqual(data.length);

      expect(screen.queryByText('I am expanded row')).not.toBeInTheDocument();
      userEvent.click(btns[2]);
      expect(screen.getByText('I am expanded row')).toBeInTheDocument();
      userEvent.click(btns[0]);
      expect(screen.getAllByText('I am expanded row').length).toEqual(2);
    });

    it('does not render button', () => {
      renderComponent({ getRowCanExpand: () => false });
      expect(
        screen.queryByRole('button', { name: 'Expand row' })
      ).not.toBeInTheDocument();
      expect(screen.queryByText('I am expanded row')).not.toBeInTheDocument();
    });
  });

  it('renders TagCell', () => {
    renderComponent();
    expect(screen.getByText(data[0].tag)).toBeInTheDocument();
    expect(screen.getByText(data[1].tag)).toBeInTheDocument();
    expect(screen.getByText(data[2].tag)).toBeInTheDocument();
    expect(screen.getByText(data[3].tag)).toBeInTheDocument();
  });

  describe('Pagination', () => {
    it('does not render page buttons', () => {
      renderComponent();
      expect(
        screen.queryByRole('button', { name: 'Next' })
      ).not.toBeInTheDocument();
    });

    it('renders page buttons', async () => {
      renderComponent({ path: '?perPage=1' });
      // Check it renders header row and only one data row
      expect(screen.getAllByRole('row').length).toEqual(2);
      expect(screen.getByText('lorem')).toBeInTheDocument();

      // Check it renders page buttons
      const firstBtn = screen.getByRole('button', { name: '⇤' });
      const prevBtn = screen.getByRole('button', { name: '← Previous' });
      const nextBtn = screen.getByRole('button', { name: 'Next →' });
      const lastBtn = screen.getByRole('button', { name: '⇥' });

      expect(firstBtn).toBeInTheDocument();
      expect(firstBtn).toBeDisabled();
      expect(prevBtn).toBeInTheDocument();
      expect(prevBtn).toBeDisabled();
      expect(nextBtn).toBeInTheDocument();
      expect(nextBtn).toBeEnabled();
      expect(lastBtn).toBeInTheDocument();
      expect(lastBtn).toBeEnabled();

      userEvent.click(nextBtn);
      expect(screen.getByText('ipsum')).toBeInTheDocument();
      expect(prevBtn).toBeEnabled();
      expect(firstBtn).toBeEnabled();

      userEvent.click(lastBtn);
      expect(screen.getByText('sit')).toBeInTheDocument();
      expect(lastBtn).toBeDisabled();
      expect(nextBtn).toBeDisabled();

      userEvent.click(prevBtn);
      expect(screen.getByText('dolor')).toBeInTheDocument();

      userEvent.click(firstBtn);
      expect(screen.getByText('lorem')).toBeInTheDocument();
    });

    it('renders go to page input', async () => {
      renderComponent({ path: '?perPage=1' });
      // Check it renders header row and only one data row
      expect(screen.getAllByRole('row').length).toEqual(2);
      expect(screen.getByText('lorem')).toBeInTheDocument();
      const input = screen.getByRole('spinbutton', { name: 'Go to page:' });
      expect(input).toBeInTheDocument();

      userEvent.clear(input);
      userEvent.type(input, '2');
      expect(screen.getByText('ipsum')).toBeInTheDocument();
    });
  });

  describe('Sorting', () => {
    it('sort rows', async () => {
      await act(() =>
        renderComponent({
          path: '/?sortBy=text&&sortDirection=desc',
          enableSorting: true,
        })
      );
      expect(screen.getAllByRole('row').length).toEqual(data.length + 1);
      const th = screen.getByRole('columnheader', { name: 'Text' });
      expect(th).toBeInTheDocument();

      let rows = [];
      // Check initial sort order by text column is descending
      rows = screen.getAllByRole('row');
      expect(rows[4].textContent?.indexOf('dolor')).toBeGreaterThan(-1);
      expect(rows[3].textContent?.indexOf('ipsum')).toBeGreaterThan(-1);
      expect(rows[2].textContent?.indexOf('lorem')).toBeGreaterThan(-1);
      expect(rows[1].textContent?.indexOf('sit')).toBeGreaterThan(-1);

      // Disable sorting by text column
      await waitFor(() => userEvent.click(th));
      rows = screen.getAllByRole('row');
      expect(rows[1].textContent?.indexOf('lorem')).toBeGreaterThan(-1);
      expect(rows[2].textContent?.indexOf('ipsum')).toBeGreaterThan(-1);
      expect(rows[3].textContent?.indexOf('dolor')).toBeGreaterThan(-1);
      expect(rows[4].textContent?.indexOf('sit')).toBeGreaterThan(-1);

      // Sort by text column ascending
      await waitFor(() => userEvent.click(th));
      rows = screen.getAllByRole('row');
      expect(rows[1].textContent?.indexOf('dolor')).toBeGreaterThan(-1);
      expect(rows[2].textContent?.indexOf('ipsum')).toBeGreaterThan(-1);
      expect(rows[3].textContent?.indexOf('lorem')).toBeGreaterThan(-1);
      expect(rows[4].textContent?.indexOf('sit')).toBeGreaterThan(-1);
    });
  });

  describe('Row Selecting', () => {
    beforeEach(() => {
      renderComponent({
        enableRowSelection: (row: Row<Datum>) => row.original.selectable,
        batchActionsBar: () => <div>I am Action Bar</div>,
      });
    });
    it('renders selectable rows', () => {
      expect(screen.getAllByRole('row').length).toEqual(data.length + 1);
      const checkboxes = screen.getAllByRole('checkbox');
      expect(checkboxes.length).toEqual(data.length + 1);
      expect(checkboxes[1]).toBeDisabled();
      expect(checkboxes[2]).toBeEnabled();
      expect(checkboxes[3]).toBeEnabled();
      expect(checkboxes[4]).toBeDisabled();
    });

    it('renders action bar', () => {
      expect(screen.getAllByRole('row').length).toEqual(data.length + 1);
      expect(screen.queryByText('I am Action Bar')).not.toBeInTheDocument();
      const checkboxes = screen.getAllByRole('checkbox');
      expect(checkboxes.length).toEqual(data.length + 1);
      userEvent.click(checkboxes[2]);
      expect(screen.getByText('I am Action Bar')).toBeInTheDocument();
    });
  });
  describe('Clickable Row', () => {
    const onRowClick = jest.fn();
    it('handles onRowClick', () => {
      renderComponent({ onRowClick });
      const rows = screen.getAllByRole('row');
      expect(rows.length).toEqual(data.length + 1);
      userEvent.click(rows[1]);
      expect(onRowClick).toHaveBeenCalledTimes(1);
    });
    it('does nothing unless onRowClick is provided', () => {
      renderComponent();
      const rows = screen.getAllByRole('row');
      expect(rows.length).toEqual(data.length + 1);
      userEvent.click(rows[1]);
    });
    it('does not handle onRowClick if enableRowSelection', () => {
      renderComponent({ onRowClick, enableRowSelection: true });
      const rows = screen.getAllByRole('row');
      expect(rows.length).toEqual(data.length + 1);
      userEvent.click(rows[1]);
      expect(onRowClick).not.toHaveBeenCalled();
    });
    it('does not handle onRowClick if expandable rows', () => {
      renderComponent({ onRowClick, getRowCanExpand: () => true });
      const rows = screen.getAllByRole('row');
      expect(rows.length).toEqual(data.length + 1);
      userEvent.click(rows[1]);
      expect(onRowClick).not.toHaveBeenCalled();
    });
  });
});
