/**
 * FilterGroupBuilder Tests
 *
 * Tests the recursive filter group builder including:
 * - AND/OR logic toggle
 * - Adding/removing conditions
 * - Adding/removing nested groups
 * - Depth-based constraints
 */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FilterGroupBuilder } from './FilterGroupBuilder';
import { FilterGroup, FilterCondition } from '../types/filter';
import { TriggerField } from '../types/trigger';

// Mock FilterConditionRow
vi.mock('./FilterConditionRow', () => ({
  FilterConditionRow: ({
    condition,
    onChange,
    onRemove,
    isOnly,
  }: {
    condition: FilterCondition;
    onChange: (c: FilterCondition) => void;
    onRemove: () => void;
    isOnly: boolean;
  }) => (
    <div data-testid="condition-row" data-field={condition.field} data-is-only={isOnly}>
      <button
        data-testid={`update-${condition.field}`}
        onClick={() => onChange({ ...condition, value: 'updated' })}
      >
        Update
      </button>
      <button data-testid={`remove-${condition.field}`} onClick={onRemove}>
        Remove
      </button>
    </div>
  ),
}));

describe('FilterGroupBuilder', () => {
  const mockAvailableFields: TriggerField[] = [
    { name: 'user_email', type: 'string', description: 'User email', example: 'user@example.com' },
    { name: 'user_role', type: 'string', description: 'User role', example: 'subscriber' },
  ];

  const mockOnChange = vi.fn();
  const mockOnRemove = vi.fn();

  const baseGroup: FilterGroup = {
    logic: 'and',
    conditions: [{ field: 'user_email', operator: 'equals', value: '' }],
  };

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

  // ==========================================================================
  // Logic Toggle Tests
  // ==========================================================================

  describe('logic toggle', () => {
    it('renders AND/OR toggle buttons', () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      expect(screen.getByRole('radio', { name: 'AND' })).toBeInTheDocument();
      expect(screen.getByRole('radio', { name: 'OR' })).toBeInTheDocument();
    });

    it('shows AND as selected for and logic', () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      expect(screen.getByRole('radio', { name: 'AND' })).toBeChecked();
    });

    it('shows OR as selected for or logic', () => {
      render(
        <FilterGroupBuilder
          group={{ ...baseGroup, logic: 'or' }}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      expect(screen.getByRole('radio', { name: 'OR' })).toBeChecked();
    });

    it('shows "All conditions must match" for AND logic', () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      expect(screen.getByText('All conditions must match')).toBeInTheDocument();
    });

    it('shows "Any condition can match" for OR logic', () => {
      render(
        <FilterGroupBuilder
          group={{ ...baseGroup, logic: 'or' }}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      expect(screen.getByText('Any condition can match')).toBeInTheDocument();
    });

    it('calls onChange when toggling to OR', async () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      await userEvent.click(screen.getByRole('radio', { name: 'OR' }));

      expect(mockOnChange).toHaveBeenCalledWith({
        ...baseGroup,
        logic: 'or',
      });
    });

    it('calls onChange when toggling to AND', async () => {
      render(
        <FilterGroupBuilder
          group={{ ...baseGroup, logic: 'or' }}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      await userEvent.click(screen.getByRole('radio', { name: 'AND' }));

      expect(mockOnChange).toHaveBeenCalledWith({
        ...baseGroup,
        logic: 'and',
      });
    });
  });

  // ==========================================================================
  // Condition Rendering Tests
  // ==========================================================================

  describe('condition rendering', () => {
    it('renders FilterConditionRow for each condition', () => {
      const group: FilterGroup = {
        logic: 'and',
        conditions: [
          { field: 'user_email', operator: 'equals', value: '' },
          { field: 'user_role', operator: 'equals', value: '' },
        ],
      };

      render(
        <FilterGroupBuilder
          group={group}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      const rows = screen.getAllByTestId('condition-row');
      expect(rows).toHaveLength(2);
    });

    it('marks single condition as isOnly', () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      expect(screen.getByTestId('condition-row')).toHaveAttribute('data-is-only', 'true');
    });

    it('marks multiple conditions as not isOnly', () => {
      const group: FilterGroup = {
        logic: 'and',
        conditions: [
          { field: 'user_email', operator: 'equals', value: '' },
          { field: 'user_role', operator: 'equals', value: '' },
        ],
      };

      render(
        <FilterGroupBuilder
          group={group}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      const rows = screen.getAllByTestId('condition-row');
      rows.forEach((row) => {
        expect(row).toHaveAttribute('data-is-only', 'false');
      });
    });
  });

  // ==========================================================================
  // Add Condition Tests
  // ==========================================================================

  describe('add condition', () => {
    it('renders Add Condition button', () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      expect(screen.getByText('Add Condition')).toBeInTheDocument();
    });

    it('adds new condition when Add Condition is clicked', async () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      await userEvent.click(screen.getByText('Add Condition'));

      expect(mockOnChange).toHaveBeenCalledWith({
        ...baseGroup,
        conditions: [
          ...baseGroup.conditions,
          { field: 'user_email', operator: 'equals', value: '' },
        ],
      });
    });
  });

  // ==========================================================================
  // Remove Condition Tests
  // ==========================================================================

  describe('remove condition', () => {
    it('calls onChange with condition removed', async () => {
      const group: FilterGroup = {
        logic: 'and',
        conditions: [
          { field: 'user_email', operator: 'equals', value: '' },
          { field: 'user_role', operator: 'equals', value: '' },
        ],
      };

      render(
        <FilterGroupBuilder
          group={group}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      await userEvent.click(screen.getByTestId('remove-user_email'));

      expect(mockOnChange).toHaveBeenCalledWith({
        ...group,
        conditions: [{ field: 'user_role', operator: 'equals', value: '' }],
      });
    });
  });

  // ==========================================================================
  // Update Condition Tests
  // ==========================================================================

  describe('update condition', () => {
    it('calls onChange with updated condition', async () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      await userEvent.click(screen.getByTestId('update-user_email'));

      expect(mockOnChange).toHaveBeenCalledWith({
        ...baseGroup,
        conditions: [{ field: 'user_email', operator: 'equals', value: 'updated' }],
      });
    });
  });

  // ==========================================================================
  // Add Group Tests
  // ==========================================================================

  describe('add nested group', () => {
    it('renders Add Group button at root level', () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
          depth={0}
        />,
      );

      expect(screen.getByText('Add Group')).toBeInTheDocument();
    });

    it('does not render Add Group button at nested level', () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
          depth={1}
        />,
      );

      expect(screen.queryByText('Add Group')).not.toBeInTheDocument();
    });

    it('adds nested group with opposite logic', async () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      await userEvent.click(screen.getByText('Add Group'));

      expect(mockOnChange).toHaveBeenCalledWith({
        ...baseGroup,
        conditions: [
          ...baseGroup.conditions,
          {
            logic: 'or', // Parent is 'and', so child is 'or'
            conditions: [{ field: 'user_email', operator: 'equals', value: '' }],
          },
        ],
      });
    });

    it('adds nested group with AND logic when parent is OR', async () => {
      render(
        <FilterGroupBuilder
          group={{ ...baseGroup, logic: 'or' }}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      await userEvent.click(screen.getByText('Add Group'));

      expect(mockOnChange).toHaveBeenCalledWith(
        expect.objectContaining({
          conditions: expect.arrayContaining([
            expect.objectContaining({
              logic: 'and',
            }),
          ]),
        }),
      );
    });
  });

  // ==========================================================================
  // Remove Group Tests
  // ==========================================================================

  describe('remove group', () => {
    it('does not render remove button at root level', () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      expect(screen.queryByTitle('Remove group')).not.toBeInTheDocument();
    });

    it('renders remove button when onRemove is provided', () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
          onRemove={mockOnRemove}
        />,
      );

      expect(screen.getByTitle('Remove group')).toBeInTheDocument();
    });

    it('calls onRemove when remove button is clicked', async () => {
      render(
        <FilterGroupBuilder
          group={baseGroup}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
          onRemove={mockOnRemove}
        />,
      );

      await userEvent.click(screen.getByTitle('Remove group'));

      expect(mockOnRemove).toHaveBeenCalled();
    });
  });

  // ==========================================================================
  // Nested Group Rendering Tests
  // ==========================================================================

  describe('nested group rendering', () => {
    it('renders nested FilterGroupBuilder for group conditions', () => {
      const groupWithNested: FilterGroup = {
        logic: 'and',
        conditions: [
          { field: 'user_email', operator: 'equals', value: '' },
          {
            logic: 'or',
            conditions: [{ field: 'user_role', operator: 'equals', value: 'admin' }],
          },
        ],
      };

      render(
        <FilterGroupBuilder
          group={groupWithNested}
          availableFields={mockAvailableFields}
          onChange={mockOnChange}
        />,
      );

      // Should have two condition rows: one in root, one in nested group
      const conditionRows = screen.getAllByTestId('condition-row');
      expect(conditionRows).toHaveLength(2);
      // The nested group will have its own AND/OR toggles
      const orRadios = screen.getAllByRole('radio', { name: 'OR' });
      expect(orRadios.length).toBe(2); // Root + nested
    });
  });
});
