import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ConfigPanel } from './ConfigPanel';
import {
  mockTriggers,
  mockActions,
  createMockTriggerNode,
  createMockActionNode,
  createMockEdge,
  TRIGGER_NODE_ID,
  ACTION_NODE_ID,
  mockFilterGroup,
} from '../test/helpers';

// ============================================================================
// Mock Setup
// ============================================================================

// Hoisted mock state
const { mockTriggersState, mockActionsState, mockDynamicOptionsState } = vi.hoisted(() => ({
  mockTriggersState: {
    triggers: [] as typeof mockTriggers,
    loading: false,
    error: null as string | null,
    refetch: vi.fn(),
  },
  mockActionsState: {
    actions: [] as typeof mockActions,
    loading: false,
    error: null as string | null,
    refetch: vi.fn(),
  },
  mockDynamicOptionsState: {
    dynamicOptions: {} as Record<string, Array<{ value: string; label: string }>>,
    loadingFields: {} as Record<string, boolean>,
    fieldErrors: {} as Record<string, string>,
    queueFetch: vi.fn(),
  },
}));

// Mock DataContext hooks
vi.mock('../context/DataContext', () => ({
  useTriggers: () => mockTriggersState,
  useActions: () => mockActionsState,
}));

// Mock custom hooks
vi.mock('../hooks/useAvailableContext', () => ({
  useAvailableContext: vi.fn(() => ({
    groups: [
      {
        namespace: 'Trigger',
        label: 'User Registration',
        variables: [
          { name: 'user_id', label: 'User ID', type: 'integer' },
          { name: 'user_email', label: 'User email', type: 'string' },
        ],
      },
    ],
  })),
}));

vi.mock('../hooks/useDynamicOptions', () => ({
  useDynamicOptions: () => mockDynamicOptionsState,
}));

// Mock child components
vi.mock('./config-panel/TriggerConfigContent', () => ({
  TriggerConfigContent: ({
    trigger,
    isLoading,
    error,
    onRetry,
    filters,
    onFiltersChange,
  }: {
    trigger: unknown;
    isLoading: boolean;
    error: string | null;
    onRetry: () => void;
    filters: unknown;
    onFiltersChange: (filters: unknown) => void;
  }) => (
    <div data-testid="trigger-config-content">
      {isLoading && <span data-testid="trigger-loading">Loading...</span>}
      {error && (
        <span data-testid="trigger-error">
          {error}
          <button onClick={onRetry}>Retry</button>
        </span>
      )}
      {trigger ? (
        <span data-testid="trigger-info">{String((trigger as { label: string }).label)}</span>
      ) : null}
      <div data-testid="filters-info">{filters ? 'Has filters' : 'No filters'}</div>
      <button
        data-testid="modify-filters"
        onClick={() => onFiltersChange({ logic: 'AND', conditions: [] })}
      >
        Modify Filters
      </button>
    </div>
  ),
}));

vi.mock('./config-panel/ActionConfigContent', () => ({
  ActionConfigContent: ({
    action,
    isLoading,
    error,
    onRetry,
    actionConfig,
    onFieldChange,
  }: {
    action: unknown;
    isLoading: boolean;
    error: string | null;
    onRetry: () => void;
    actionConfig: Record<string, unknown>;
    onFieldChange: (field: string, value: unknown) => void;
  }) => (
    <div data-testid="action-config-content">
      {isLoading && <span data-testid="action-loading">Loading...</span>}
      {error && (
        <span data-testid="action-error">
          {error}
          <button onClick={onRetry}>Retry</button>
        </span>
      )}
      {action ? (
        <span data-testid="action-info">{String((action as { label: string }).label)}</span>
      ) : null}
      <input
        data-testid="to-field"
        value={(actionConfig.to as string) || ''}
        onChange={(e) => onFieldChange('to', e.target.value)}
      />
    </div>
  ),
}));

vi.mock('./ServiceIcon', () => ({
  ServiceIcon: ({ service }: { service: string }) => (
    <span data-testid="service-icon" data-service={service}>
      Icon
    </span>
  ),
}));

// Mock i18n
vi.mock('@/lib/i18n', () => ({
  __: (str: string) => str,
}));

// ============================================================================
// Test Helpers
// ============================================================================

function createTriggerNodeProps() {
  return {
    id: TRIGGER_NODE_ID,
    type: 'trigger' as const,
    name: 'User Registration',
    icon: 'user',
    x: 250,
    y: 80,
    status: 'valid' as const,
    actionType: 'wordpress/user_registration',
    appService: 'wordpress',
    config: {},
  };
}

function createActionNodeProps() {
  return {
    id: ACTION_NODE_ID,
    type: 'action' as const,
    name: 'Send Email',
    icon: 'mail',
    x: 250,
    y: 240,
    status: 'valid' as const,
    actionType: 'wordpress/send_email',
    appService: 'wordpress',
    config: {
      to: 'test@example.com',
      subject: 'Welcome',
    },
    label: 'send_email_1',
  };
}

function createOperatorNodeProps() {
  return {
    id: 'node-operator-1',
    type: 'operator' as const,
    name: 'Condition',
    icon: 'git-branch',
    x: 250,
    y: 160,
    status: 'valid' as const,
    config: {},
  };
}

const defaultNodes = [createMockTriggerNode(), createMockActionNode()];
const defaultEdges = [createMockEdge(TRIGGER_NODE_ID, ACTION_NODE_ID)];

// ============================================================================
// Tests
// ============================================================================

describe('ConfigPanel', () => {
  const defaultProps = {
    nodes: defaultNodes,
    edges: defaultEdges,
    onClose: vi.fn(),
    onUpdate: vi.fn(),
    onDelete: vi.fn(),
  };

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

    // Reset mock state
    Object.assign(mockTriggersState, {
      triggers: mockTriggers,
      loading: false,
      error: null,
      refetch: vi.fn(),
    });

    Object.assign(mockActionsState, {
      actions: mockActions,
      loading: false,
      error: null,
      refetch: vi.fn(),
    });

    Object.assign(mockDynamicOptionsState, {
      dynamicOptions: {},
      loadingFields: {},
      fieldErrors: {},
      queueFetch: vi.fn(),
    });
  });

  // ==========================================================================
  // Header Tests
  // ==========================================================================

  describe('header', () => {
    it('renders node name in header', () => {
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      // Use heading role to avoid matching text in TriggerConfigContent mock
      expect(screen.getByRole('heading', { name: 'User Registration' })).toBeInTheDocument();
    });

    it('renders service icon for trigger node', () => {
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      expect(screen.getByTestId('service-icon')).toHaveAttribute('data-service', 'wordpress');
    });

    it('renders service icon for action node', () => {
      render(<ConfigPanel {...defaultProps} node={createActionNodeProps()} />);
      expect(screen.getByTestId('service-icon')).toHaveAttribute('data-service', 'wordpress');
    });

    it('renders close button', () => {
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      // Close button has X icon - find by role
      const closeButton = screen
        .getAllByRole('button')
        .find((btn) => btn.querySelector('svg.lucide-x'));
      expect(closeButton).toBeInTheDocument();
    });

    it('renders delete button when onDelete is provided', () => {
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      // Delete button has Trash2 icon (class is lucide-trash2, not lucide-trash-2)
      const deleteButton = screen.getByTitle('Delete node');
      expect(deleteButton).toBeInTheDocument();
    });

    it('does not render delete button when onDelete is not provided', () => {
      const { onDelete: _onDelete, ...propsWithoutDelete } = defaultProps;
      render(<ConfigPanel {...propsWithoutDelete} node={createTriggerNodeProps()} />);
      const deleteButton = screen.queryByTitle('Delete node');
      expect(deleteButton).not.toBeInTheDocument();
    });

    it('calls onDelete when delete button is clicked', async () => {
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      const deleteButton = screen.getByTitle('Delete node');
      await userEvent.click(deleteButton);
      expect(defaultProps.onDelete).toHaveBeenCalledWith(TRIGGER_NODE_ID);
    });
  });

  // ==========================================================================
  // Trigger Mode Tests
  // ==========================================================================

  describe('trigger mode', () => {
    it('renders TriggerConfigContent for trigger nodes', () => {
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      expect(screen.getByTestId('trigger-config-content')).toBeInTheDocument();
    });

    it('does not render ActionConfigContent for trigger nodes', () => {
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      expect(screen.queryByTestId('action-config-content')).not.toBeInTheDocument();
    });

    it('shows loading state when triggers are loading', () => {
      mockTriggersState.loading = true;
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      expect(screen.getByTestId('trigger-loading')).toBeInTheDocument();
    });

    it('shows error state with retry button', () => {
      mockTriggersState.error = 'Failed to load triggers';
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      expect(screen.getByTestId('trigger-error')).toBeInTheDocument();
      expect(screen.getByText('Failed to load triggers')).toBeInTheDocument();
    });

    it('calls refetch when retry is clicked', async () => {
      mockTriggersState.error = 'Failed to load triggers';
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      await userEvent.click(screen.getByText('Retry'));
      expect(mockTriggersState.refetch).toHaveBeenCalled();
    });

    it('displays trigger info when loaded', () => {
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);
      expect(screen.getByTestId('trigger-info')).toHaveTextContent('User Registration');
    });

    it('loads existing filters from node config', () => {
      const nodeWithFilters = {
        ...createTriggerNodeProps(),
        config: { filters: mockFilterGroup },
      };
      render(<ConfigPanel {...defaultProps} node={nodeWithFilters} />);
      expect(screen.getByTestId('filters-info')).toHaveTextContent('Has filters');
    });

    it('calls onFiltersChange when filters are modified', async () => {
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);

      // Initially no filters
      expect(screen.getByTestId('filters-info')).toHaveTextContent('No filters');

      // Modify filters
      await userEvent.click(screen.getByTestId('modify-filters'));

      // Filter info should update (mock just changes local state)
      expect(screen.getByTestId('filters-info')).toHaveTextContent('Has filters');
    });
  });

  // ==========================================================================
  // Action Mode Tests
  // ==========================================================================

  describe('action mode', () => {
    it('renders ActionConfigContent for action nodes', () => {
      render(<ConfigPanel {...defaultProps} node={createActionNodeProps()} />);
      expect(screen.getByTestId('action-config-content')).toBeInTheDocument();
    });

    it('does not render TriggerConfigContent for action nodes', () => {
      render(<ConfigPanel {...defaultProps} node={createActionNodeProps()} />);
      expect(screen.queryByTestId('trigger-config-content')).not.toBeInTheDocument();
    });

    it('shows loading state when actions are loading', () => {
      mockActionsState.loading = true;
      render(<ConfigPanel {...defaultProps} node={createActionNodeProps()} />);
      expect(screen.getByTestId('action-loading')).toBeInTheDocument();
    });

    it('shows error state with retry button', () => {
      mockActionsState.error = 'Failed to load actions';
      render(<ConfigPanel {...defaultProps} node={createActionNodeProps()} />);
      expect(screen.getByTestId('action-error')).toBeInTheDocument();
    });

    it('calls refetch when retry is clicked', async () => {
      mockActionsState.error = 'Failed to load actions';
      render(<ConfigPanel {...defaultProps} node={createActionNodeProps()} />);
      await userEvent.click(screen.getByText('Retry'));
      expect(mockActionsState.refetch).toHaveBeenCalled();
    });

    it('displays action info when loaded', () => {
      render(<ConfigPanel {...defaultProps} node={createActionNodeProps()} />);
      expect(screen.getByTestId('action-info')).toHaveTextContent('Send Email');
    });

    it('loads existing config into form', () => {
      render(<ConfigPanel {...defaultProps} node={createActionNodeProps()} />);
      expect(screen.getByTestId('to-field')).toHaveValue('test@example.com');
    });
  });

  // ==========================================================================
  // Operator Mode Tests
  // ==========================================================================

  describe('operator mode', () => {
    it('shows coming soon message for operator nodes', () => {
      render(<ConfigPanel {...defaultProps} node={createOperatorNodeProps()} />);
      expect(screen.getByText('Operator configuration coming soon')).toBeInTheDocument();
    });

    it('does not render trigger or action content for operator nodes', () => {
      render(<ConfigPanel {...defaultProps} node={createOperatorNodeProps()} />);
      expect(screen.queryByTestId('trigger-config-content')).not.toBeInTheDocument();
      expect(screen.queryByTestId('action-config-content')).not.toBeInTheDocument();
    });
  });

  // ==========================================================================
  // Close Button Tests
  // ==========================================================================

  describe('close button', () => {
    it('renders close button', () => {
      render(<ConfigPanel {...defaultProps} node={createTriggerNodeProps()} />);

      const closeButton = screen
        .getAllByRole('button')
        .find((btn) => btn.querySelector('svg.lucide-x'));
      expect(closeButton).toBeInTheDocument();
    });
  });

  // ==========================================================================
  // Node Change Tests
  // ==========================================================================

  describe('node changes', () => {
    it('loads new config when node changes', () => {
      const { rerender } = render(<ConfigPanel {...defaultProps} node={createActionNodeProps()} />);

      expect(screen.getByTestId('to-field')).toHaveValue('test@example.com');

      // Change to a different node with different config
      const newNode = {
        ...createActionNodeProps(),
        id: 'node-action-2',
        config: { to: 'other@example.com' },
      };
      rerender(<ConfigPanel {...defaultProps} node={newNode} />);

      expect(screen.getByTestId('to-field')).toHaveValue('other@example.com');
    });
  });
});
