import { render, screen, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import userEvent from '@testing-library/user-event';
import { FormBuilder, FormField } from './FormBuilder';

describe('FormBuilder Component', () => {
  const mockOnSubmit = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
  });

  const basicFields: FormField[] = [
    {
      name: 'name',
      type: 'text',
      label: 'Name',
      required: true,
    },
  ];

  it('renders form with fields', () => {
    render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);

    expect(screen.getByLabelText(/Name/)).toBeInTheDocument();
    expect(screen.getByText('Submit')).toBeInTheDocument();
  });

  it('uses custom submit label', () => {
    render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} submitLabel="Save" />);

    expect(screen.getByText('Save')).toBeInTheDocument();
  });

  it('renders text input field', () => {
    render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText(/Name/);
    expect(input).toHaveAttribute('type', 'text');
  });

  it('renders email input field', () => {
    const fields: FormField[] = [
      {
        name: 'email',
        type: 'email',
        label: 'Email',
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText('Email');
    expect(input).toHaveAttribute('type', 'email');
  });

  it('renders password input field', () => {
    const fields: FormField[] = [
      {
        name: 'password',
        type: 'password',
        label: 'Password',
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText('Password');
    expect(input).toHaveAttribute('type', 'password');
  });

  it('renders number input field', () => {
    const fields: FormField[] = [
      {
        name: 'age',
        type: 'number',
        label: 'Age',
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText('Age');
    expect(input).toHaveAttribute('type', 'number');
  });

  it('renders textarea field', () => {
    const fields: FormField[] = [
      {
        name: 'description',
        type: 'textarea',
        label: 'Description',
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    const textarea = screen.getByLabelText('Description');
    expect(textarea.tagName).toBe('TEXTAREA');
  });

  it('renders select field', () => {
    const fields: FormField[] = [
      {
        name: 'country',
        type: 'select',
        label: 'Country',
        options: [
          { value: 'us', label: 'United States' },
          { value: 'fr', label: 'France' },
        ],
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    expect(screen.getByText(/Country/)).toBeInTheDocument();
  });

  it('renders date picker field', () => {
    const fields: FormField[] = [
      {
        name: 'birthday',
        type: 'date',
        label: 'Birthday',
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    expect(screen.getByText(/Birthday/)).toBeInTheDocument();
  });

  it('renders file upload field', () => {
    const fields: FormField[] = [
      {
        name: 'avatar',
        type: 'file',
        label: 'Avatar',
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    expect(screen.getByText(/Avatar/)).toBeInTheDocument();
  });

  it('shows required indicator', () => {
    render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);

    const label = screen.getByText(/Name/);
    expect(label.textContent).toContain('*');
  });

  it('updates form data when field value changes', async () => {
    const user = userEvent.setup();
    render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText(/Name/);
    await user.type(input, 'John Doe');

    expect(input).toHaveValue('John Doe');
  });

  it('validates required fields', async () => {
    const user = userEvent.setup();
    render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);

    const submitButton = screen.getByText('Submit');
    await user.click(submitButton);

    await waitFor(() => {
      expect(screen.getByText(/is required/)).toBeInTheDocument();
    });

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

  it('validates email format', async () => {
    const user = userEvent.setup();
    const fields: FormField[] = [
      {
        name: 'email',
        type: 'email',
        label: 'Email',
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText('Email');
    await user.type(input, 'invalid-email');

    await user.tab(); // Trigger blur

    await waitFor(() => {
      expect(screen.getByText(/valid email address/)).toBeInTheDocument();
    });
  });

  it('calls custom validation function', async () => {
    const user = userEvent.setup();
    const customValidation = vi.fn((value) => {
      if (value.length < 5) {
        return 'Must be at least 5 characters';
      }
      return null;
    });

    const fields: FormField[] = [
      {
        name: 'username',
        type: 'text',
        label: 'Username',
        validation: customValidation,
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText('Username');
    await user.type(input, 'abc');

    await user.tab(); // Trigger blur

    await waitFor(() => {
      expect(customValidation).toHaveBeenCalled();
      expect(screen.getByText(/at least 5 characters/)).toBeInTheDocument();
    });
  });

  it('submits form with valid data', async () => {
    const user = userEvent.setup();
    render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText(/Name/);
    await user.type(input, 'John Doe');

    const submitButton = screen.getByText('Submit');
    await user.click(submitButton);

    await waitFor(() => {
      expect(mockOnSubmit).toHaveBeenCalledWith({
        name: 'John Doe',
      });
    });
  });

  it('uses default values', () => {
    const fields: FormField[] = [
      {
        name: 'name',
        type: 'text',
        label: 'Name',
        defaultValue: 'Default Name',
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText(/Name/);
    expect(input).toHaveValue('Default Name');
  });

  it('disables form when disabled prop is true', () => {
    render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} disabled />);

    const input = screen.getByLabelText(/Name/);
    expect(input).toBeDisabled();

    const submitButton = screen.getByText('Submit');
    expect(submitButton).toBeDisabled();
  });

  it('disables individual fields', () => {
    const fields: FormField[] = [
      {
        name: 'name',
        type: 'text',
        label: 'Name',
        disabled: true,
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText(/Name/);
    expect(input).toBeDisabled();
  });

  it('shows placeholder text', () => {
    const fields: FormField[] = [
      {
        name: 'name',
        type: 'text',
        label: 'Name',
        placeholder: 'Enter your name',
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    const input = screen.getByLabelText(/Name/);
    expect(input).toHaveAttribute('placeholder', 'Enter your name');
  });

  it('handles multiple fields', () => {
    const fields: FormField[] = [
      {
        name: 'firstName',
        type: 'text',
        label: 'First Name',
      },
      {
        name: 'lastName',
        type: 'text',
        label: 'Last Name',
      },
      {
        name: 'email',
        type: 'email',
        label: 'Email',
      },
    ];

    render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);

    expect(screen.getByLabelText('First Name')).toBeInTheDocument();
    expect(screen.getByLabelText('Last Name')).toBeInTheDocument();
    expect(screen.getByLabelText('Email')).toBeInTheDocument();
  });
});

