/**
 * Tests for FeaturePicker Component
 *
 * TDD: These tests define the expected behavior of the FeaturePicker component.
 */

import React, { useState } from 'react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FeaturePicker, FeaturePickerProps } from '../FeaturePicker';
import { FEATURE_CATALOG, BOOLEAN_FEATURES, INTEGER_FEATURES } from '../../featureCatalog';
import type { PlanFeatureWrite } from '../../../hooks/useBillingAdmin';

// Mock features from API (similar to what useFeatures() returns)
const mockApiFeatures = [
  { id: 1, code: 'sms_enabled', name: 'SMS Enabled', description: 'Allow SMS', feature_type: 'boolean' as const },
  { id: 2, code: 'email_enabled', name: 'Email Enabled', description: 'Allow email', feature_type: 'boolean' as const },
  { id: 3, code: 'max_users', name: 'Maximum Users', description: 'Max users limit', feature_type: 'integer' as const },
  { id: 4, code: 'max_resources', name: 'Maximum Resources', description: 'Max resources', feature_type: 'integer' as const },
  { id: 5, code: 'custom_feature', name: 'Custom Feature', description: 'Not in catalog', feature_type: 'boolean' as const },
];

/**
 * Wrapper component that manages state for controlled FeaturePicker
 */
const StatefulFeaturePicker: React.FC<
  Omit<FeaturePickerProps, 'selectedFeatures' | 'onChange'> & {
    initialSelectedFeatures?: PlanFeatureWrite[];
    onChangeCapture?: (features: PlanFeatureWrite[]) => void;
  }
> = ({ initialSelectedFeatures = [], onChangeCapture, ...props }) => {
  const [selectedFeatures, setSelectedFeatures] = useState<PlanFeatureWrite[]>(initialSelectedFeatures);

  const handleChange = (features: PlanFeatureWrite[]) => {
    setSelectedFeatures(features);
    onChangeCapture?.(features);
  };

  return (
    <FeaturePicker
      {...props}
      selectedFeatures={selectedFeatures}
      onChange={handleChange}
    />
  );
};

describe('FeaturePicker', () => {
  const defaultProps = {
    features: mockApiFeatures,
    selectedFeatures: [],
    onChange: vi.fn(),
  };

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

  describe('Rendering', () => {
    it('renders boolean features in Capabilities section', () => {
      render(<FeaturePicker {...defaultProps} />);

      expect(screen.getByText('Capabilities')).toBeInTheDocument();
      expect(screen.getByText('SMS Enabled')).toBeInTheDocument();
      expect(screen.getByText('Email Enabled')).toBeInTheDocument();
    });

    it('renders integer features in Limits & Quotas section', () => {
      render(<FeaturePicker {...defaultProps} />);

      expect(screen.getByText('Limits & Quotas')).toBeInTheDocument();
      expect(screen.getByText('Maximum Users')).toBeInTheDocument();
      expect(screen.getByText('Maximum Resources')).toBeInTheDocument();
    });

    it('shows selected features as checked', () => {
      const selectedFeatures = [
        { feature_code: 'sms_enabled', bool_value: true, int_value: null },
      ];

      render(<FeaturePicker {...defaultProps} selectedFeatures={selectedFeatures} />);

      const checkbox = screen.getByRole('checkbox', { name: /sms enabled/i });
      expect(checkbox).toBeChecked();
    });

    it('shows integer values for selected integer features', () => {
      const selectedFeatures = [
        { feature_code: 'max_users', bool_value: null, int_value: 50 },
      ];

      render(<FeaturePicker {...defaultProps} selectedFeatures={selectedFeatures} />);

      const input = screen.getByDisplayValue('50');
      expect(input).toBeInTheDocument();
    });
  });

  describe('Feature Selection', () => {
    it('calls onChange when a boolean feature is selected', async () => {
      const onChange = vi.fn();
      render(<FeaturePicker {...defaultProps} onChange={onChange} />);

      const checkbox = screen.getByRole('checkbox', { name: /sms enabled/i });
      await userEvent.click(checkbox);

      expect(onChange).toHaveBeenCalledWith([
        { feature_code: 'sms_enabled', bool_value: true, int_value: null },
      ]);
    });

    it('calls onChange when a boolean feature is deselected', async () => {
      const selectedFeatures = [
        { feature_code: 'sms_enabled', bool_value: true, int_value: null },
      ];
      const onChange = vi.fn();

      render(
        <FeaturePicker {...defaultProps} selectedFeatures={selectedFeatures} onChange={onChange} />
      );

      const checkbox = screen.getByRole('checkbox', { name: /sms enabled/i });
      await userEvent.click(checkbox);

      expect(onChange).toHaveBeenCalledWith([]);
    });

    it('calls onChange when an integer feature is selected with default value 0', async () => {
      const onChange = vi.fn();
      render(<FeaturePicker {...defaultProps} onChange={onChange} />);

      const checkbox = screen.getByRole('checkbox', { name: /maximum users/i });
      await userEvent.click(checkbox);

      expect(onChange).toHaveBeenCalledWith([
        { feature_code: 'max_users', bool_value: null, int_value: 0 },
      ]);
    });

    it('calls onChange when integer value is updated', async () => {
      const initialSelectedFeatures = [
        { feature_code: 'max_users', bool_value: null, int_value: 10 },
      ];
      const onChangeCapture = vi.fn();

      render(
        <StatefulFeaturePicker
          features={mockApiFeatures}
          initialSelectedFeatures={initialSelectedFeatures}
          onChangeCapture={onChangeCapture}
        />
      );

      const input = screen.getByDisplayValue('10');
      await userEvent.clear(input);
      await userEvent.type(input, '50');

      // Should have been called multiple times as user types
      expect(onChangeCapture).toHaveBeenCalled();
      const lastCall = onChangeCapture.mock.calls[onChangeCapture.mock.calls.length - 1][0];
      expect(lastCall).toContainEqual({ feature_code: 'max_users', bool_value: null, int_value: 50 });
    });
  });

  describe('Canonical Catalog Validation', () => {
    it('shows warning badge for features not in canonical catalog', () => {
      render(<FeaturePicker {...defaultProps} />);

      // custom_feature is not in the canonical catalog
      const customFeatureRow = screen.getByText('Custom Feature').closest('label');
      expect(customFeatureRow).toBeInTheDocument();

      // Should show a warning indicator
      const warningIndicator = within(customFeatureRow!).queryByTitle(/not in canonical catalog/i);
      expect(warningIndicator).toBeInTheDocument();
    });

    it('does not show warning for canonical features', () => {
      render(<FeaturePicker {...defaultProps} />);

      // sms_enabled is in the canonical catalog
      const smsFeatureRow = screen.getByText('SMS Enabled').closest('label');
      expect(smsFeatureRow).toBeInTheDocument();

      const warningIndicator = within(smsFeatureRow!).queryByTitle(/not in canonical catalog/i);
      expect(warningIndicator).not.toBeInTheDocument();
    });
  });

  describe('Search Functionality', () => {
    it('filters features when search term is entered', async () => {
      render(<FeaturePicker {...defaultProps} />);

      const searchInput = screen.getByPlaceholderText(/search features/i);
      await userEvent.type(searchInput, 'sms');

      expect(screen.getByText('SMS Enabled')).toBeInTheDocument();
      expect(screen.queryByText('Email Enabled')).not.toBeInTheDocument();
      expect(screen.queryByText('Maximum Users')).not.toBeInTheDocument();
    });

    it('shows no results message when search has no matches', async () => {
      render(<FeaturePicker {...defaultProps} />);

      const searchInput = screen.getByPlaceholderText(/search features/i);
      await userEvent.type(searchInput, 'nonexistent');

      expect(screen.getByText(/no features found/i)).toBeInTheDocument();
    });

    it('clears search when clear button is clicked', async () => {
      render(<FeaturePicker {...defaultProps} />);

      const searchInput = screen.getByPlaceholderText(/search features/i);
      await userEvent.type(searchInput, 'sms');

      const clearButton = screen.getByRole('button', { name: /clear search/i });
      await userEvent.click(clearButton);

      expect(searchInput).toHaveValue('');
      expect(screen.getByText('Email Enabled')).toBeInTheDocument();
    });
  });

  describe('Payload Shape', () => {
    it('produces correct payload shape for boolean features', async () => {
      const onChange = vi.fn();
      render(<FeaturePicker {...defaultProps} onChange={onChange} />);

      await userEvent.click(screen.getByRole('checkbox', { name: /sms enabled/i }));
      await userEvent.click(screen.getByRole('checkbox', { name: /email enabled/i }));

      const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];

      // Verify payload shape matches PlanFeatureWrite interface
      expect(lastCall).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            feature_code: expect.any(String),
            bool_value: expect.any(Boolean),
            int_value: null,
          }),
        ])
      );
    });

    it('produces correct payload shape for integer features', async () => {
      const selectedFeatures = [
        { feature_code: 'max_users', bool_value: null, int_value: 25 },
      ];
      const onChange = vi.fn();

      render(
        <FeaturePicker {...defaultProps} selectedFeatures={selectedFeatures} onChange={onChange} />
      );

      const input = screen.getByDisplayValue('25');
      await userEvent.clear(input);
      await userEvent.type(input, '100');

      const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];

      expect(lastCall).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            feature_code: 'max_users',
            bool_value: null,
            int_value: expect.any(Number),
          }),
        ])
      );
    });

    it('produces correct payload shape for mixed selection', async () => {
      const onChangeCapture = vi.fn();
      render(
        <StatefulFeaturePicker
          features={mockApiFeatures}
          onChangeCapture={onChangeCapture}
        />
      );

      // Select a boolean feature
      await userEvent.click(screen.getByRole('checkbox', { name: /sms enabled/i }));
      // Select an integer feature
      await userEvent.click(screen.getByRole('checkbox', { name: /maximum users/i }));

      const lastCall = onChangeCapture.mock.calls[onChangeCapture.mock.calls.length - 1][0];

      expect(lastCall).toHaveLength(2);
      expect(lastCall).toContainEqual({
        feature_code: 'sms_enabled',
        bool_value: true,
        int_value: null,
      });
      expect(lastCall).toContainEqual({
        feature_code: 'max_users',
        bool_value: null,
        int_value: 0,
      });
    });
  });

  describe('Accessibility', () => {
    it('has accessible labels for all checkboxes', () => {
      render(<FeaturePicker {...defaultProps} />);

      // Each feature should have an accessible checkbox
      mockApiFeatures.forEach((feature) => {
        const checkbox = screen.getByRole('checkbox', { name: new RegExp(feature.name, 'i') });
        expect(checkbox).toBeInTheDocument();
      });
    });

    it('integer input has accessible label', () => {
      const selectedFeatures = [
        { feature_code: 'max_users', bool_value: null, int_value: 10 },
      ];

      render(<FeaturePicker {...defaultProps} selectedFeatures={selectedFeatures} />);

      const input = screen.getByDisplayValue('10');
      expect(input).toHaveAttribute('aria-label');
    });
  });
});
