import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import { DynamicListColumn } from '../DynamicDataList.model';
import { DynamicListRow, DynamicListRowProps } from './DynamicListRow';

interface DynamicListRowTestData {
  id: number;
  desc: string;
  title: string;
  position?: number;
}

const defaultData: DynamicListRowTestData = {
  id: 0,
  desc: 'desc',
  title: 'title',
};

const defaultColumns: DynamicListColumn<DynamicListRowTestData>[] = [
  {
    propertyName: 'id',
    label: 'Id',
  },
  {
    propertyName: 'title',
    label: 'Title',
  },
  {
    propertyName: 'desc',
    label: 'Description',
  },
];

const dataWithPosition: DynamicListRowTestData = {
  id: 0,
  desc: 'tes',
  title: 'a title',
  position: 3,
};

const defaultProps: DynamicListRowProps<DynamicListRowTestData> = {
  data: defaultData,
  columns: defaultColumns,
  columnSizes: '',
  columnGap: '',
  rowHeight: '',
  actionSize: '',
  positionKey: 'position',
  horizontalTextAlign: 'left',
  verticalTextAlign: 'center',
  allowRemove: false,
};

describe('DynamicListRow', () => {
  it('renders the component without crashing', () => {
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        data={defaultProps.data}
      />,
    );

    expect(container).toBeTruthy();
  });

  it('most styling props, except gridTemplateColumns, should be undefined by default', () => {
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        rowHeight={defaultProps.rowHeight}
        data={defaultProps.data}
      />,
    );

    const row = container.querySelector('.container') as HTMLElement;

    expect(row.style.gridColumnGap).toBe('');
    expect(row.style.alignItems).toBe('');
    expect(row.style.gridAutoRows).toBe(
      `minmax(50px, ${defaultProps.rowHeight})`,
    );
    expect(row.style.gridTemplateColumns).toBe(defaultProps.columnSizes);
    expect(row.style.justifyItems).toBe('');
  });

  it('sets styling props when passed in', () => {
    const { container } = render(<DynamicListRow {...defaultProps} />);

    const row = container.querySelector('.container') as HTMLElement;

    expect(row.style.gridColumnGap).toBe(defaultProps.columnGap);
    expect(row.style.alignItems).toBe(defaultProps.verticalTextAlign);
    expect(row.style.gridAutoRows).toBe(
      `minmax(50px, ${defaultProps.rowHeight})`,
    );
    expect(row.style.gridTemplateColumns).toBe(defaultProps.columnSizes);
    expect(row.style.justifyItems).toBe(defaultProps.horizontalTextAlign);
  });

  it(`Button component should have type of 'button'`, () => {
    const { container } = render(
      <DynamicListRow
        {...defaultProps}
        allowRemove={true}
        showActionColumn={true}
      />,
    );

    const button = container.querySelector('button');

    expect(button?.type).toBe('button');
  });

  it('renders the value for every column if set', () => {
    const { container } = render(<DynamicListRow {...defaultProps} />);

    const columns = container.querySelectorAll('.column');

    columns.forEach((column, idx) => {
      expect(column.textContent).toBe(
        String(defaultData[defaultColumns[idx].propertyName!]),
      );
    });
  });

  it(`renders the position column if 'showPositionColumn' is set to true`, () => {
    const dataWithPosition: DynamicListRowTestData = {
      id: 0,
      desc: 'tes',
      title: 'a title',
      position: 3,
    };

    const { container } = render(
      <DynamicListRow
        columnSizes={''}
        columns={defaultColumns}
        data={dataWithPosition}
        positionKey={'position'}
        showPositionColumn={true}
      />,
    );

    const position = container.querySelector('.position');

    expect(position).toBeInTheDocument();
  });

  it(`doesn't render the position column if 'showPositionColumn' is false`, () => {
    const dataWithPosition: DynamicListRowTestData = {
      id: 0,
      desc: 'tes',
      title: 'a title',
      position: 3,
    };

    const { container } = render(
      <DynamicListRow
        columnSizes={''}
        columns={defaultColumns}
        data={dataWithPosition}
        positionKey={'position'}
        showPositionColumn={false}
      />,
    );

    const position = container.querySelector('.position');

    expect(position).not.toBeInTheDocument();
  });

  it('uses custom renderer', () => {
    const columnsWithRenderFunc: DynamicListColumn<DynamicListRowTestData>[] = [
      {
        propertyName: 'title',
        label: 'Title',
        render: (value: unknown) => `Changed: ${value}`,
      },
    ];

    const { container } = render(
      <DynamicListRow
        columns={columnsWithRenderFunc}
        columnSizes={defaultProps.columnSizes}
        data={defaultProps.data}
      />,
    );
    const actualColumns = container.querySelectorAll('.column');

    expect(actualColumns[0].textContent).toBe('Changed: title');
  });

  it(`doesn't allow dragging if showPositionColumn is 'false'`, () => {
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        data={dataWithPosition}
        allowDragging={false}
        showPositionColumn={true}
        positionKey={'position'}
      />,
    );

    const dragIcon = container.querySelector('.draggable');
    expect(dragIcon).not.toBeInTheDocument();
  });

  it(`only allows dragging if both showPositionColumn and allowDragging are 'true'`, () => {
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        data={dataWithPosition}
        allowDragging={true}
        showPositionColumn={true}
        positionKey={'position'}
      />,
    );

    const dragIcon = container.querySelector('.draggable');

    expect(dragIcon).toBeInTheDocument();
  });

  it(`doesn't display an action button by default`, () => {
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        data={defaultProps.data}
      />,
    );

    const button = container.querySelector('button');

    expect(button).not.toBeInTheDocument();
  });

  it(`displays an action button if 'showActionColumn' is set to true`, () => {
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        data={defaultProps.data}
        allowRemove={true}
        showActionColumn={true}
      />,
    );

    const button = container.querySelector('button');

    expect(button).toBeInTheDocument();
  });

  it(`emits onActionClicked with row data when the action button is clicked`, () => {
    const spy = vi.fn();
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        data={defaultProps.data}
        allowRemove={true}
        onActionClicked={spy}
        showActionColumn={true}
      />,
    );

    const button = container.querySelector('button') as HTMLButtonElement;

    fireEvent.click(button);

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith(defaultData);
  });

  it('emits onPositionInputChanged with the current position and the new target position', () => {
    const inputSpy = vi.fn();
    const mockNewPosition = '9';
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        data={dataWithPosition}
        onPositionInputChanged={inputSpy}
        positionKey={'position'}
        showPositionColumn={true}
      />,
    );

    const input = container.querySelector('input') as HTMLInputElement;

    fireEvent.change(input, { target: { value: mockNewPosition } });
    fireEvent.keyDown(input, { key: 'Enter' });

    expect(inputSpy).toHaveBeenCalledTimes(1);
    expect(inputSpy).toHaveBeenCalledWith(
      dataWithPosition.position,
      Number(mockNewPosition),
    );

    fireEvent.change(input, { target: { value: mockNewPosition } });
    fireEvent.blur(input);

    expect(inputSpy).toHaveBeenCalledTimes(2);
    expect(inputSpy).toHaveBeenCalledWith(
      dataWithPosition.position,
      Number(mockNewPosition),
    );
  });

  it('onPositionInputChanged is not emitted if current position and new position are the same', () => {
    const inputSpy = vi.fn();
    const mockNewPosition = dataWithPosition.position;
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        data={dataWithPosition}
        onPositionInputChanged={inputSpy}
        positionKey={'position'}
        showPositionColumn={true}
      />,
    );

    const input = container.querySelector('input') as HTMLInputElement;

    fireEvent.change(input, { target: { value: String(mockNewPosition) } });
    fireEvent.keyDown(input, { key: 'Enter' });

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

  it('input will update if value is a valid number', () => {
    const mockNewPosition = 5;
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        data={dataWithPosition}
        positionKey={'position'}
        showPositionColumn={true}
      />,
    );

    let input = container.querySelector('input') as HTMLInputElement;

    expect(input.value).toBe(String(dataWithPosition.position));

    fireEvent.change(input, { target: { value: String(mockNewPosition) } });

    input = container.querySelector('input') as HTMLInputElement;

    expect(input.value).toBe(String(mockNewPosition));
  });

  it('input will not update if value is not a valid number', () => {
    const mockNewPosition = 'NaN';
    const { container } = render(
      <DynamicListRow
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        data={dataWithPosition}
        positionKey={'position'}
        showPositionColumn={true}
      />,
    );

    let input = container.querySelector('input') as HTMLInputElement;

    expect(input.value).toBe(String(dataWithPosition.position));

    fireEvent.change(input, { target: { value: mockNewPosition } });

    input = container.querySelector('input') as HTMLInputElement;

    expect(input.value).toBe(String(dataWithPosition.position));
  });

  describe('DynamicListRow column text alignments', () => {
    const alignments: {
      horizontal: 'left' | 'right' | 'center' | undefined;
      vertical: 'center' | 'start' | 'end' | undefined;
    }[] = [
      { horizontal: 'center', vertical: 'center' },
      { horizontal: 'left', vertical: 'start' },
      { horizontal: 'right', vertical: 'end' },
      { horizontal: undefined, vertical: undefined },
    ];

    alignments.forEach(({ horizontal, vertical }) => {
      it(`should apply the correct styles for justify-content: ${horizontal}, align-items: ${vertical}`, () => {
        const { container } = render(
          <DynamicListRow
            columns={defaultColumns}
            columnSizes={defaultProps.columnSizes}
            data={dataWithPosition}
            positionKey={'position'}
            showPositionColumn={true}
            horizontalTextAlign={horizontal}
            verticalTextAlign={vertical}
          />,
        );

        const wrapperDivs = container.querySelectorAll('.wrapper');

        wrapperDivs.forEach((node) => {
          const element = node as HTMLElement;

          expect(element.style.justifyContent).toBe(horizontal || '');
          expect(element.style.alignItems).toBe(vertical || '');
        });
      });
    });
  });

  describe('tooltip', () => {
    it(`renders a tooltip using the 'title' html attribute by default`, () => {
      const { container } = render(
        <DynamicListRow
          columns={defaultColumns}
          columnSizes={defaultProps.columnSizes}
          data={defaultProps.data}
        />,
      );

      const columns = container.querySelectorAll('.column');

      columns.forEach((column) => {
        expect(column.getAttribute('title')).toBe(column.textContent);
      });
    });

    it(`does not render a title attribute if tooltip column property is set to 'false'`, () => {
      const mockColumns = defaultColumns.map((obj) => ({
        ...obj,
        tooltip: false,
      }));

      const { container } = render(
        <DynamicListRow
          columns={mockColumns}
          columnSizes={defaultProps.columnSizes}
          data={defaultProps.data}
        />,
      );

      const columns = container.querySelectorAll('.column');

      columns.forEach((column) => {
        expect(column.getAttribute('title')).toBeNull();
      });
    });
  });
});
