import { fireEvent, render } from '@testing-library/react';
import React, { ChangeEvent, useState } from 'react';
import { act } from 'react-dom/test-utils';
import { describe, expect, it, vi } from 'vitest';
import * as Yup from 'yup';
import { ObjectSchemaDefinition } from '../../FormStation';
import {
  DynamicListColumn,
  DynamicListDataEntryRenderer,
} from '../DynamicDataList.model';
import {
  DynamicListDataEntry,
  DynamicListDataEntryProps,
} from './DynamicListDataEntry';

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

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

const defaultProps: DynamicListDataEntryProps<TestData> = {
  columns: defaultColumns,
  columnSizes: '',
  columnGap: '',
  rowHeight: '',
  actionSize: '',
  horizontalTextAlign: 'left',
  verticalTextAlign: 'center',
  positionKey: 'position',
  allowReordering: false,
};

const MockRenderer: DynamicListDataEntryRenderer = (
  currentValue: unknown,
  error: string | undefined,
  onValueChange: (value: unknown) => void,
) => {
  const [state, setState] = useState<string>();
  const onChangeHandler = (e: ChangeEvent<HTMLInputElement>): void => {
    setState(e.target.value);
    onValueChange(e.target.value);
  };

  return (
    <input type={'text'} value={state} onChange={(e) => onChangeHandler(e)} />
  );
};

const columnWithRenderer: DynamicListColumn<TestData>[] = [
  {
    propertyName: 'title',
    label: 'Title',
    dataEntryRender: MockRenderer,
  },
];

// Temporarily disable console.warn
vi.spyOn(console, 'warn').mockImplementation(() => null);

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

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

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

    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(<DynamicListDataEntry {...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(<DynamicListDataEntry {...defaultProps} />);

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

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

  it('renders a column for every property set', () => {
    const defaultColumnsLength = defaultColumns.length;
    const { container } = render(<DynamicListDataEntry {...defaultProps} />);

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

    expect(columns).toHaveLength(defaultColumnsLength);
  });

  it(`renders the position column if 'showPositionColumn' is set to true`, () => {
    const { container } = render(
      <DynamicListDataEntry
        columnSizes={''}
        columns={defaultColumns}
        showPositionColumn={true}
      />,
    );

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

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

  it(`doesn't render the position column if a 'showPositionColumn' is set to false`, () => {
    const { container } = render(
      <DynamicListDataEntry
        columnSizes={''}
        columns={defaultColumns}
        allowReordering={true}
        showPositionColumn={false}
      />,
    );

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

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

  it(`renders the drag icon if 'allowDragging' is true`, () => {
    const { container } = render(
      <DynamicListDataEntry
        columnSizes={''}
        columns={defaultColumns}
        positionKey={'position'}
        allowReordering={true}
        allowDragging={true}
        showPositionColumn={true}
      />,
    );

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

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

  it(`doesn't render the drag icon if 'allowDragging' is false`, () => {
    const { container } = render(
      <DynamicListDataEntry
        columnSizes={''}
        columns={defaultColumns}
        positionKey={'position'}
        allowReordering={true}
        allowDragging={false}
        showPositionColumn={true}
      />,
    );

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

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

  it(`renders "disabled" attribute for action button when set`, () => {
    const { container } = render(
      <DynamicListDataEntry {...defaultProps} disabled={true} />,
    );

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

    expect(button.disabled).toBe(true);
  });

  it(`input value should be 'newDataPosition' prop`, () => {
    const mockPosition = 56;
    const { container } = render(
      <DynamicListDataEntry
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        positionKey={'position'}
        allowReordering={true}
        newDataPosition={mockPosition}
        showPositionColumn={true}
      />,
    );

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

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

  it('does not allow a user to manually enter a position and input should be disabled', () => {
    const { container } = render(
      <DynamicListDataEntry
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        positionKey={'position'}
        allowReordering={true}
        showPositionColumn={true}
      />,
    );

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

    expect(input.disabled).toBe(true);
  });

  it('accepts default values and sets them', async () => {
    const defaultData: Partial<TestData> = {
      desc: 'Description',
    };
    const spy = vi.fn();
    const { container } = render(
      <DynamicListDataEntry
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        defaultValuesForNewData={defaultData}
        onActionClicked={spy}
      />,
    );

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

    await act(async () => {
      fireEvent.click(button);
    });

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

  it(`does not emit onActionClicked if data is invalid`, async () => {
    const spy = vi.fn();
    const { container } = render(
      <DynamicListDataEntry
        columns={defaultColumns}
        columnSizes={defaultProps.columnSizes}
        onActionClicked={spy}
        rowValidationSchema={Yup.object<ObjectSchemaDefinition<TestData>>({
          id: Yup.number().required(),
          title: Yup.string().required().max(25).label('Title'),
        })}
      />,
    );

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

    await act(async () => {
      fireEvent.click(button);
    });

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

  it(`emits onActionClicked with property name and new value if there is data to send`, async () => {
    const spy = vi.fn();
    const mockNewValue = 'test-value';

    const propertyName = columnWithRenderer[0].propertyName!;

    const { container } = render(
      <DynamicListDataEntry
        columns={columnWithRenderer}
        columnSizes={defaultProps.columnSizes}
        onActionClicked={spy}
      />,
    );

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

    act(() => {
      fireEvent.change(input, { target: { value: mockNewValue } });
    });

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

    await act(async () => {
      fireEvent.click(button);
    });

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith({ [propertyName]: mockNewValue });
  });

  it(`emits row data and new position when 'allowReordering' is true, 'newDataPosition' is defined, and is 'positionKey' is defined`, async () => {
    const spy = vi.fn();
    const mockNewValue = 'test-value';
    const propertyName = columnWithRenderer[0].propertyName!;
    const mockNewPos = 5;
    const mockPosKey = 'id';

    const { container } = render(
      <DynamicListDataEntry
        columns={[...columnWithRenderer, { propertyName: mockPosKey }]}
        columnSizes={defaultProps.columnSizes}
        onActionClicked={spy}
        positionKey={mockPosKey}
        allowReordering={true}
        newDataPosition={mockNewPos}
        showPositionColumn={true}
      />,
    );

    const inputs = container.querySelectorAll('input');
    const input = inputs[1] as HTMLInputElement;

    act(() => {
      fireEvent.change(input, { target: { value: mockNewValue } });
    });

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

    await act(async () => {
      fireEvent.click(button);
    });

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith({
      [propertyName]: mockNewValue,
      [mockPosKey]: mockNewPos,
    });
  });

  it(`emits only row data when 'allowReordering' is false, 'newDataPosition' is defined, and is 'positionKey' is defined`, async () => {
    const spy = vi.fn();
    const mockNewValue = 'test-value';
    const propertyName = columnWithRenderer[0].propertyName!;
    const mockNewPos = 5;
    const mockPosKey = 'id';

    const { container } = render(
      <DynamicListDataEntry
        columns={[...columnWithRenderer, { propertyName: mockPosKey }]}
        columnSizes={defaultProps.columnSizes}
        onActionClicked={spy}
        positionKey={mockPosKey}
        allowReordering={false}
        newDataPosition={mockNewPos}
      />,
    );

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

    act(() => {
      fireEvent.change(input, { target: { value: mockNewValue } });
    });

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

    await act(async () => {
      fireEvent.click(button);
    });

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith({
      [propertyName]: mockNewValue,
    });
  });

  it(`emits only row data when 'allowReordering' is true, 'newDataPosition' is undefined, and is 'positionKey' is defined`, async () => {
    const spy = vi.fn();
    const mockNewValue = 'test-value';
    const propertyName = columnWithRenderer[0].propertyName!;
    const mockPosKey = 'id';

    const { container } = render(
      <DynamicListDataEntry
        columns={[...columnWithRenderer, { propertyName: mockPosKey }]}
        columnSizes={defaultProps.columnSizes}
        onActionClicked={spy}
        positionKey={mockPosKey}
        allowReordering={true}
        newDataPosition={undefined}
        showPositionColumn={true}
      />,
    );

    const inputs = container.querySelectorAll('input');
    const input = inputs[1] as HTMLInputElement;

    act(() => {
      fireEvent.change(input, { target: { value: mockNewValue } });
    });

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

    await act(async () => {
      fireEvent.click(button);
    });

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith({
      [propertyName]: mockNewValue,
    });
  });

  it(`emits only row data when 'allowReordering' is true, 'newDataPosition' is defined, and is 'positionKey' is undefined`, async () => {
    const spy = vi.fn();
    const mockNewValue = 'test-value';
    const propertyName = columnWithRenderer[0].propertyName!;
    const mockPosKey = 'id';
    const mockNewPos = 5;

    const { container } = render(
      <DynamicListDataEntry
        columns={[...columnWithRenderer, { propertyName: mockPosKey }]}
        columnSizes={defaultProps.columnSizes}
        onActionClicked={spy}
        allowReordering={true}
        newDataPosition={mockNewPos}
      />,
    );

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

    act(() => {
      fireEvent.change(input, { target: { value: mockNewValue } });
    });

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

    await act(async () => {
      fireEvent.click(button);
    });

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith({
      [propertyName]: mockNewValue,
    });
  });

  it(`transform property if 'onAddTransformer' is set as a column option`, async () => {
    const spy = vi.fn();
    const mockNewValue = 'test-value1';

    const mockColumns: DynamicListColumn<TestData>[] = [
      {
        propertyName: 'title',
        label: 'Title',
        dataEntryRender: MockRenderer,
        onAddTransformer: (val) => (val as string).toUpperCase(),
      },
    ];

    const propertyName = mockColumns[0].propertyName!;

    const { container } = render(
      <DynamicListDataEntry
        columns={mockColumns}
        columnSizes={defaultProps.columnSizes}
        onActionClicked={spy}
      />,
    );

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

    act(() => {
      fireEvent.change(input, { target: { value: mockNewValue } });
    });

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

    await act(async () => {
      fireEvent.click(button);
    });

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith({
      [propertyName]: mockNewValue.toUpperCase(),
    });
  });
});
