import { render } from '@testing-library/react';
import { Field } from 'formik';
import React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { BulkEditFieldConfigMap } from './BulkEdit.model';
import { BulkEditFormFieldsConfigConverter } from './FormFieldsConfigConverter';

vi.mock('../../FormElements', () => ({
  CheckboxField: vi.fn(({ name, label }) => (
    <div data-test-id="CheckboxField">{`${name}-${label}`}</div>
  )),
  CustomTagsField: vi.fn(({ name, label }) => (
    <div data-test-id="CustomTagsField">{`${name}-${label}`}</div>
  )),
  SingleLineTextField: vi.fn(({ name, label }) => (
    <div data-test-id="SingleLineTextField">{`${name}-${label}`}</div>
  )),
}));

vi.mock('../../FieldSelection', () => ({
  FieldSelection: vi.fn(({ children, onFieldRemoved, onFieldAdded }) => (
    <div
      data-test-id="FieldSelection"
      data-on-field-removed={!!onFieldRemoved}
      data-on-field-added={!!onFieldAdded}
    >
      {children}
    </div>
  )),
}));

const mockSetFieldValue = vi.fn();
const mockSetFieldTouched = vi.fn();
const mockSetErrors = vi.fn();
const mockValidateForm = vi.fn();

vi.mock('formik', () => ({
  Field: vi.fn(({ name, label, as: Component, validate }) => (
    <div data-test-id={`Field-${name}`} data-has-validation={!!validate}>
      <Component name={name} label={label} />
    </div>
  )),
  useFormikContext: vi.fn(() => ({
    setFieldValue: mockSetFieldValue,
    setFieldTouched: mockSetFieldTouched,
    setErrors: mockSetErrors,
    errors: {},
    validateForm: mockValidateForm,
    values: {},
  })),
}));

describe('BulkEditFormFieldsConfigConverter', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('renders a Formik Field wrapping SingleLineTextField for String type', () => {
    const config: BulkEditFieldConfigMap = {
      title: {
        type: 'String',
        label: 'Title',
        originalFieldName: 'title',
        action: 'set',
      },
    };

    const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));

    expect(getByTestId('Field-title')).toBeInTheDocument();
    expect(getByTestId('Field-title')).toHaveAttribute(
      'data-has-validation',
      'true',
    );
    expect(getByTestId('SingleLineTextField')).toHaveTextContent('title-Title');
    expect(getByTestId('FieldSelection')).toHaveAttribute(
      'data-on-field-removed',
      'true',
    );
    expect(getByTestId('FieldSelection')).toHaveAttribute(
      'data-on-field-added',
      'true',
    );
  });

  it('renders a Formik Field wrapping CheckboxField for Boolean type', () => {
    const config: BulkEditFieldConfigMap = {
      isArchived: {
        type: 'Boolean',
        label: 'Is Archived',
        originalFieldName: 'isArchived',
        action: 'set',
      },
    };

    const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));

    expect(getByTestId('Field-isArchived')).toBeInTheDocument();
    expect(getByTestId('Field-isArchived')).toHaveAttribute(
      'data-has-validation',
      'true',
    );
    expect(getByTestId('CheckboxField')).toHaveTextContent(
      'isArchived-Is Archived',
    );
  });

  it('renders a Formik Field wrapping CustomTagsField for Array type', () => {
    const config: BulkEditFieldConfigMap = {
      tags: {
        type: [
          {
            name: 'String!',
          },
        ],
        label: 'Tags',
        originalFieldName: 'tags',
        action: 'relatedEntitiesToAdd',
      },
    };

    const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));

    expect(getByTestId('Field-tags')).toBeInTheDocument();
    expect(getByTestId('Field-tags')).toHaveAttribute(
      'data-has-validation',
      'true',
    );
    expect(getByTestId('CustomTagsField')).toHaveTextContent('tags-Tags');
  });

  it('renders multiple Formik Fields for multiple fields', () => {
    const config: BulkEditFieldConfigMap = {
      title: {
        type: 'String',
        label: 'Title',
        originalFieldName: 'title',
        action: 'set',
      },
      isArchived: {
        type: 'Boolean',
        label: 'Is Archived',
        originalFieldName: 'isArchived',
        action: 'set',
      },
      tags: {
        type: [
          {
            name: 'String!',
          },
        ],
        label: 'Tags',
        originalFieldName: 'tags',
        action: 'relatedEntitiesToAdd',
      },
    };

    const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));

    expect(getByTestId('Field-title')).toBeInTheDocument();
    expect(getByTestId('SingleLineTextField')).toHaveTextContent('title-Title');

    expect(getByTestId('Field-isArchived')).toBeInTheDocument();
    expect(getByTestId('CheckboxField')).toHaveTextContent(
      'isArchived-Is Archived',
    );

    expect(getByTestId('Field-tags')).toBeInTheDocument();
    expect(getByTestId('CustomTagsField')).toHaveTextContent('tags-Tags');

    // Check that all fields have validation
    expect(getByTestId('Field-title')).toHaveAttribute(
      'data-has-validation',
      'true',
    );
    expect(getByTestId('Field-isArchived')).toHaveAttribute(
      'data-has-validation',
      'true',
    );
    expect(getByTestId('Field-tags')).toHaveAttribute(
      'data-has-validation',
      'true',
    );
  });

  it('logs a warning for unsupported field types', () => {
    const consoleWarnSpy = vi.spyOn(console, 'warn').mockReturnValue(undefined);
    const config: BulkEditFieldConfigMap = {
      unsupportedField: {
        type: 'UnsupportedType' as any,
        label: 'Unsupported Field',
        originalFieldName: 'unsupportedField',
        action: 'set',
      },
    };

    const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));

    expect(consoleWarnSpy).toHaveBeenCalledWith(
      'Bulk Edit: No component found for field type: UnsupportedType',
    );
    expect(getByTestId('FieldSelection')).toBeInTheDocument();
    expect(getByTestId('FieldSelection')).toHaveAttribute(
      'data-on-field-removed',
      'true',
    );
    expect(getByTestId('FieldSelection')).toHaveAttribute(
      'data-on-field-added',
      'true',
    );

    consoleWarnSpy.mockRestore();
  });

  it('handles empty configuration gracefully', () => {
    const config: BulkEditFieldConfigMap = {};

    const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));

    expect(getByTestId('FieldSelection')).toBeInTheDocument();
    expect(getByTestId('FieldSelection')).toHaveAttribute(
      'data-on-field-removed',
      'true',
    );
    expect(getByTestId('FieldSelection')).toHaveAttribute(
      'data-on-field-added',
      'true',
    );
  });

  it('validates field values correctly', () => {
    const config: BulkEditFieldConfigMap = {
      title: {
        type: 'String',
        label: 'Title',
        originalFieldName: 'title',
        action: 'set',
      },
    };

    render(BulkEditFormFieldsConfigConverter(config));

    // Get the validation function from the Field mock
    const mockField = vi.mocked(Field);
    const fieldCall = mockField.mock.calls[0][0] as any;
    const validateFn = fieldCall.validate;

    // Test validation for empty values
    expect(validateFn(null)).toBe('Please provide a value');
    expect(validateFn(undefined)).toBe('Please provide a value');
    expect(validateFn('')).toBe('Please provide a value');
    expect(validateFn([])).toBe('Please provide a value');

    // Test validation for valid values
    expect(validateFn('valid value')).toBeUndefined();
    expect(validateFn(['valid', 'array'])).toBeUndefined();
  });

  it('runs custom validation and returns error message on failure', () => {
    const customValidation = {
      validateSync: vi.fn().mockImplementation(() => {
        throw new Error('Custom validation failed');
      }),
    };

    const config: BulkEditFieldConfigMap = {
      title: {
        type: 'String',
        label: 'Title',
        originalFieldName: 'title',
        action: 'set',
        customValidation: customValidation as any,
      },
    };

    render(BulkEditFormFieldsConfigConverter(config));

    const mockField = vi.mocked(Field);
    const fieldCall = mockField.mock.calls[0][0] as any;
    const validateFn = fieldCall.validate;

    expect(validateFn('some value')).toBe('Custom validation failed');
    expect(customValidation.validateSync).toHaveBeenCalledWith('some value');
  });

  it('returns "Invalid value" when custom validation throws a non-Error', () => {
    const customValidation = {
      validateSync: vi.fn().mockImplementation(() => {
        throw 'non-error thrown'; // eslint-disable-line no-throw-literal
      }),
    };

    const config: BulkEditFieldConfigMap = {
      title: {
        type: 'String',
        label: 'Title',
        originalFieldName: 'title',
        action: 'set',
        customValidation: customValidation as any,
      },
    };

    render(BulkEditFormFieldsConfigConverter(config));

    const mockField = vi.mocked(Field);
    const fieldCall = mockField.mock.calls[0][0] as any;
    const validateFn = fieldCall.validate;

    expect(validateFn('some value')).toBe('Invalid value');
  });

  it('falls through to empty-value check when custom validation passes', () => {
    const customValidation = {
      validateSync: vi.fn(), // passes without throwing
    };

    const config: BulkEditFieldConfigMap = {
      title: {
        type: 'String',
        label: 'Title',
        originalFieldName: 'title',
        action: 'set',
        customValidation: customValidation as any,
      },
    };

    render(BulkEditFormFieldsConfigConverter(config));

    const mockField = vi.mocked(Field);
    const fieldCall = mockField.mock.calls[0][0] as any;
    const validateFn = fieldCall.validate;

    // Custom validation passes, but value is empty -> should return empty-value error
    expect(validateFn('')).toBe('Please provide a value');
    expect(customValidation.validateSync).toHaveBeenCalledWith('');

    // Custom validation passes, value is valid -> should return undefined
    expect(validateFn('valid value')).toBeUndefined();
  });

  it('supports BigFloat type with SingleLineTextField', () => {
    const config: BulkEditFieldConfigMap = {
      price: {
        type: 'BigFloat',
        label: 'Price',
        originalFieldName: 'price',
        action: 'set',
      },
    };

    const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));

    expect(getByTestId('Field-price')).toBeInTheDocument();
    expect(getByTestId('SingleLineTextField')).toHaveTextContent('price-Price');
  });

  it('calls useFormikContext hooks correctly', () => {
    const config: BulkEditFieldConfigMap = {
      title: {
        type: 'String',
        label: 'Title',
        originalFieldName: 'title',
        action: 'set',
      },
    };

    render(BulkEditFormFieldsConfigConverter(config));

    // Verify that useFormikContext was called
    expect(mockSetFieldValue).toBeDefined();
    expect(mockSetFieldTouched).toBeDefined();
    expect(mockSetErrors).toBeDefined();
    expect(mockValidateForm).toBeDefined();
  });

  it('handles field clearing functionality through FieldSelection callbacks', () => {
    const config: BulkEditFieldConfigMap = {
      title: {
        type: 'String',
        label: 'Title',
        originalFieldName: 'title',
        action: 'set',
      },
    };

    const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));
    const fieldSelection = getByTestId('FieldSelection');

    // Verify that the FieldSelection component receives the callback props
    expect(fieldSelection).toHaveAttribute('data-on-field-removed', 'true');
    expect(fieldSelection).toHaveAttribute('data-on-field-added', 'true');
  });
});
