/**
 * Tests for ActivityFeed Component
 *
 * Tests cover:
 * - Rendering with events
 * - Connection state indicator
 * - Search functionality
 * - Filter functionality
 * - Event expansion
 * - Approval actions
 * - Time-based grouping
 * - Loading state
 * - Empty state
 */

import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ActivityFeed } from '@/components/activity/ActivityFeed';
import { EventType, type ProjectEvent } from '@/lib/types/events';

// ============================================================================
// Test Data
// ============================================================================

const createMockEvent = (overrides: Partial<ProjectEvent> = {}): ProjectEvent => ({
  id: `event-${Math.random().toString(36).substr(2, 9)}`,
  type: EventType.AGENT_MESSAGE,
  timestamp: new Date().toISOString(),
  project_id: 'project-001',
  actor_id: 'agent-001',
  actor_type: 'agent',
  payload: {
    agent_instance_id: 'agent-001',
    message: 'Test message',
    message_type: 'info',
  },
  ...overrides,
});

const mockEvents: ProjectEvent[] = [
  // Today's events
  createMockEvent({
    id: 'event-001',
    type: EventType.APPROVAL_REQUESTED,
    timestamp: new Date().toISOString(),
    payload: {
      approval_id: 'apr-001',
      approval_type: 'architecture_decision',
      description: 'Approval required for API design',
      requested_by: 'Architect',
    },
  }),
  createMockEvent({
    id: 'event-002',
    type: EventType.AGENT_MESSAGE,
    timestamp: new Date(Date.now() - 1000 * 60 * 30).toISOString(),
    payload: {
      agent_instance_id: 'agent-002',
      message: 'Completed JWT implementation',
      message_type: 'info',
    },
  }),
  // Yesterday's event
  createMockEvent({
    id: 'event-003',
    type: EventType.ISSUE_CREATED,
    timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString(),
    payload: {
      issue_id: 'issue-001',
      title: 'Add rate limiting',
      priority: 'medium',
    },
  }),
  // This week's event
  createMockEvent({
    id: 'event-004',
    type: EventType.SPRINT_STARTED,
    timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3).toISOString(),
    payload: {
      sprint_id: 'sprint-001',
      sprint_name: 'Sprint 1',
      goal: 'Complete auth module',
    },
  }),
  // Older event
  createMockEvent({
    id: 'event-005',
    type: EventType.WORKFLOW_COMPLETED,
    timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24 * 10).toISOString(),
    payload: {
      workflow_id: 'wf-001',
      duration_seconds: 3600,
    },
  }),
];

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

describe('ActivityFeed', () => {
  const defaultProps = {
    events: mockEvents,
    connectionState: 'connected' as const,
  };

  describe('Rendering', () => {
    it('renders the activity feed with test id', () => {
      render(<ActivityFeed {...defaultProps} />);
      expect(screen.getByTestId('activity-feed')).toBeInTheDocument();
    });

    it('renders the header with title', () => {
      render(<ActivityFeed {...defaultProps} />);
      expect(screen.getByText('Activity Feed')).toBeInTheDocument();
    });

    it('renders custom title when provided', () => {
      render(<ActivityFeed {...defaultProps} title="Project Activity" />);
      expect(screen.getByText('Project Activity')).toBeInTheDocument();
    });

    it('hides header when showHeader is false', () => {
      render(<ActivityFeed {...defaultProps} showHeader={false} />);
      expect(screen.queryByText('Activity Feed')).not.toBeInTheDocument();
    });

    it('renders events', () => {
      render(<ActivityFeed {...defaultProps} />);
      expect(screen.getByTestId('event-item-event-001')).toBeInTheDocument();
      expect(screen.getByTestId('event-item-event-002')).toBeInTheDocument();
    });

    it('applies custom className', () => {
      render(<ActivityFeed {...defaultProps} className="custom-class" />);
      expect(screen.getByTestId('activity-feed')).toHaveClass('custom-class');
    });
  });

  describe('Connection State', () => {
    it('renders connection indicator', () => {
      render(<ActivityFeed {...defaultProps} />);
      expect(screen.getByTestId('connection-indicator')).toBeInTheDocument();
    });

    it('shows "Live" when connected', () => {
      render(<ActivityFeed {...defaultProps} connectionState="connected" />);
      expect(screen.getByText('Live')).toBeInTheDocument();
    });

    it('shows "Connecting..." when connecting', () => {
      render(<ActivityFeed {...defaultProps} connectionState="connecting" />);
      expect(screen.getByText('Connecting...')).toBeInTheDocument();
    });

    it('shows "Disconnected" when disconnected', () => {
      render(<ActivityFeed {...defaultProps} connectionState="disconnected" />);
      expect(screen.getByText('Disconnected')).toBeInTheDocument();
    });

    it('shows "Error" when error state', () => {
      render(<ActivityFeed {...defaultProps} connectionState="error" />);
      expect(screen.getByText('Error')).toBeInTheDocument();
    });

    it('shows reconnect button when disconnected', () => {
      const onReconnect = jest.fn();
      render(
        <ActivityFeed {...defaultProps} connectionState="disconnected" onReconnect={onReconnect} />
      );
      const reconnectButton = screen.getByLabelText('Reconnect');
      expect(reconnectButton).toBeInTheDocument();
    });

    it('calls onReconnect when reconnect button clicked', async () => {
      const user = userEvent.setup();
      const onReconnect = jest.fn();
      render(
        <ActivityFeed {...defaultProps} connectionState="disconnected" onReconnect={onReconnect} />
      );

      await user.click(screen.getByLabelText('Reconnect'));
      expect(onReconnect).toHaveBeenCalledTimes(1);
    });
  });

  describe('Search Functionality', () => {
    it('renders search input when enableSearch is true', () => {
      render(<ActivityFeed {...defaultProps} enableSearch />);
      expect(screen.getByTestId('search-input')).toBeInTheDocument();
    });

    it('hides search input when enableSearch is false', () => {
      render(<ActivityFeed {...defaultProps} enableSearch={false} />);
      expect(screen.queryByTestId('search-input')).not.toBeInTheDocument();
    });

    it('filters events based on search query', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} enableSearch />);

      const searchInput = screen.getByTestId('search-input');
      await user.type(searchInput, 'JWT');

      // Event with JWT in message should be visible
      expect(screen.getByText(/Completed JWT implementation/)).toBeInTheDocument();
      // Other events should be filtered out
      expect(screen.queryByText(/Approval required for API design/)).not.toBeInTheDocument();
    });

    it('shows empty state when search finds no results', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} enableSearch />);

      const searchInput = screen.getByTestId('search-input');
      await user.type(searchInput, 'nonexistent query xyz');

      expect(screen.getByTestId('empty-state')).toBeInTheDocument();
      expect(screen.getByText('No activity found')).toBeInTheDocument();
    });
  });

  describe('Filter Functionality', () => {
    it('renders filter toggle when enableFiltering is true', () => {
      render(<ActivityFeed {...defaultProps} enableFiltering />);
      expect(screen.getByTestId('filter-toggle')).toBeInTheDocument();
    });

    it('hides filter toggle when enableFiltering is false', () => {
      render(<ActivityFeed {...defaultProps} enableFiltering={false} />);
      expect(screen.queryByTestId('filter-toggle')).not.toBeInTheDocument();
    });

    it('shows filter panel when filter toggle is clicked', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} enableFiltering />);

      await user.click(screen.getByTestId('filter-toggle'));
      expect(screen.getByTestId('filter-panel')).toBeInTheDocument();
    });

    it('filters events by category when filter is selected', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} enableFiltering />);

      // Open filter panel
      await user.click(screen.getByTestId('filter-toggle'));

      // Select Issues category
      const issuesCheckbox = screen.getByLabelText(/Issues/);
      await user.click(issuesCheckbox);

      // Only issue events should be visible
      expect(screen.getByText(/Add rate limiting/)).toBeInTheDocument();
      // Agent events should be filtered out
      expect(screen.queryByText(/Completed JWT implementation/)).not.toBeInTheDocument();
    });

    it('shows pending only when filter is selected', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} enableFiltering />);

      // Open filter panel
      await user.click(screen.getByTestId('filter-toggle'));

      // Select pending only
      const pendingCheckbox = screen.getByLabelText(/Show only pending approvals/);
      await user.click(pendingCheckbox);

      // Only approval requested events should be visible
      expect(screen.getByText(/Approval required for API design/)).toBeInTheDocument();
      // Other events should be filtered out
      expect(screen.queryByText(/Completed JWT implementation/)).not.toBeInTheDocument();
    });

    it('clears filters when Clear Filters is clicked', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} enableFiltering enableSearch />);

      // Add search query
      await user.type(screen.getByTestId('search-input'), 'JWT');

      // Open filter panel and select a filter
      await user.click(screen.getByTestId('filter-toggle'));
      await user.click(screen.getByLabelText(/Issues/));

      // Clear filters
      await user.click(screen.getByText('Clear Filters'));

      // All events should be visible again
      expect(screen.getByText(/Completed JWT implementation/)).toBeInTheDocument();
      expect(screen.getByText(/Approval required for API design/)).toBeInTheDocument();
    });
  });

  describe('Event Expansion', () => {
    it('expands event details when clicked', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} />);

      const eventItem = screen.getByTestId('event-item-event-001');
      await user.click(eventItem);

      expect(screen.getByTestId('event-details')).toBeInTheDocument();
    });

    it('collapses event details when clicked again', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} />);

      const eventItem = screen.getByTestId('event-item-event-001');

      // Expand
      await user.click(eventItem);
      expect(screen.getByTestId('event-details')).toBeInTheDocument();

      // Collapse
      await user.click(eventItem);
      expect(screen.queryByTestId('event-details')).not.toBeInTheDocument();
    });

    it('shows raw payload in expanded details', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} />);

      const eventItem = screen.getByTestId('event-item-event-001');
      await user.click(eventItem);

      // Check for payload content
      expect(screen.getByText(/View raw payload/)).toBeInTheDocument();
    });
  });

  describe('Approval Actions', () => {
    it('shows approve and reject buttons for pending approvals', () => {
      render(<ActivityFeed {...defaultProps} onApprove={jest.fn()} onReject={jest.fn()} />);

      const eventItem = screen.getByTestId('event-item-event-001');
      expect(within(eventItem).getByTestId('approve-button')).toBeInTheDocument();
      expect(within(eventItem).getByTestId('reject-button')).toBeInTheDocument();
    });

    it('does not show action buttons for non-approval events', () => {
      render(<ActivityFeed {...defaultProps} onApprove={jest.fn()} onReject={jest.fn()} />);

      const eventItem = screen.getByTestId('event-item-event-002');
      expect(within(eventItem).queryByTestId('approve-button')).not.toBeInTheDocument();
    });

    it('calls onApprove when approve button clicked', async () => {
      const user = userEvent.setup();
      const onApprove = jest.fn();
      render(<ActivityFeed {...defaultProps} onApprove={onApprove} />);

      const eventItem = screen.getByTestId('event-item-event-001');
      await user.click(within(eventItem).getByTestId('approve-button'));

      expect(onApprove).toHaveBeenCalledTimes(1);
      expect(onApprove).toHaveBeenCalledWith(expect.objectContaining({ id: 'event-001' }));
    });

    it('calls onReject when reject button clicked', async () => {
      const user = userEvent.setup();
      const onReject = jest.fn();
      render(<ActivityFeed {...defaultProps} onReject={onReject} />);

      const eventItem = screen.getByTestId('event-item-event-001');
      await user.click(within(eventItem).getByTestId('reject-button'));

      expect(onReject).toHaveBeenCalledTimes(1);
      expect(onReject).toHaveBeenCalledWith(expect.objectContaining({ id: 'event-001' }));
    });

    it('shows pending count badge', () => {
      render(<ActivityFeed {...defaultProps} />);
      expect(screen.getByText('1 pending')).toBeInTheDocument();
    });
  });

  describe('Time-Based Grouping', () => {
    it('groups events by time period', () => {
      render(<ActivityFeed {...defaultProps} />);

      // Check for time period headers
      expect(screen.getByTestId('event-group-today')).toBeInTheDocument();
    });

    it('shows event count in group header', () => {
      render(<ActivityFeed {...defaultProps} />);

      const todayGroup = screen.getByTestId('event-group-today');
      // Today has 2 events in our mock data
      expect(within(todayGroup).getByText('2')).toBeInTheDocument();
    });
  });

  describe('Loading State', () => {
    it('shows loading skeleton when isLoading is true', () => {
      render(<ActivityFeed {...defaultProps} isLoading />);
      expect(screen.getByTestId('loading-skeleton')).toBeInTheDocument();
    });

    it('hides events when loading', () => {
      render(<ActivityFeed {...defaultProps} isLoading />);
      expect(screen.queryByTestId('event-item-event-001')).not.toBeInTheDocument();
    });
  });

  describe('Empty State', () => {
    it('shows empty state when no events', () => {
      render(<ActivityFeed {...defaultProps} events={[]} />);
      expect(screen.getByTestId('empty-state')).toBeInTheDocument();
    });

    it('shows appropriate message when no events and no filters', () => {
      render(<ActivityFeed {...defaultProps} events={[]} />);
      expect(screen.getByText(/Activity will appear here/)).toBeInTheDocument();
    });

    it('shows appropriate message when filtered to empty', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} enableSearch />);

      await user.type(screen.getByTestId('search-input'), 'nonexistent');

      expect(screen.getByText(/Try adjusting your search or filters/)).toBeInTheDocument();
    });
  });

  describe('Event Click Handler', () => {
    it('calls onEventClick when event is clicked', async () => {
      const user = userEvent.setup();
      const onEventClick = jest.fn();
      render(<ActivityFeed {...defaultProps} onEventClick={onEventClick} />);

      await user.click(screen.getByTestId('event-item-event-001'));

      expect(onEventClick).toHaveBeenCalledTimes(1);
      expect(onEventClick).toHaveBeenCalledWith(expect.objectContaining({ id: 'event-001' }));
    });
  });

  describe('Compact Mode', () => {
    it('applies compact styling when compact is true', () => {
      render(<ActivityFeed {...defaultProps} compact />);

      // Check for compact-specific styling (p-2 instead of p-4)
      const eventItem = screen.getByTestId('event-item-event-001');
      // The event item should have compact padding
      expect(eventItem).toHaveClass('p-2');
    });
  });

  describe('Actor Display', () => {
    it('displays User for user actor type', () => {
      const userEvent = createMockEvent({
        id: 'event-user',
        actor_type: 'user',
        type: EventType.APPROVAL_GRANTED,
        payload: {},
      });
      render(<ActivityFeed {...defaultProps} events={[userEvent]} />);
      expect(screen.getByText('User')).toBeInTheDocument();
    });

    it('displays System for system actor type', () => {
      const systemEvent = createMockEvent({
        id: 'event-system',
        actor_type: 'system',
        type: EventType.WORKFLOW_COMPLETED,
        payload: { duration_seconds: 100 },
      });
      render(<ActivityFeed {...defaultProps} events={[systemEvent]} />);
      expect(screen.getByText('System')).toBeInTheDocument();
    });
  });

  describe('Accessibility', () => {
    it('has proper ARIA labels for interactive elements', () => {
      render(
        <ActivityFeed {...defaultProps} onReconnect={jest.fn()} connectionState="disconnected" />
      );

      expect(screen.getByLabelText('Reconnect')).toBeInTheDocument();
    });

    it('event items are keyboard accessible', async () => {
      const user = userEvent.setup();
      render(<ActivityFeed {...defaultProps} />);

      const eventItem = screen.getByTestId('event-item-event-001');

      // Focus and activate with keyboard
      eventItem.focus();
      await user.keyboard('{Enter}');

      expect(screen.getByTestId('event-details')).toBeInTheDocument();
    });

    it('renders semantic HTML structure', () => {
      render(<ActivityFeed {...defaultProps} />);

      // Check for proper heading hierarchy
      const heading = screen.getByText('Today');
      expect(heading.tagName).toBe('H3');
    });
  });

  describe('Max Height', () => {
    it('applies max height styling', () => {
      const { container } = render(<ActivityFeed {...defaultProps} maxHeight={500} />);

      const scrollContainer = container.querySelector('.overflow-y-auto');
      expect(scrollContainer).toHaveStyle({ maxHeight: '500px' });
    });

    it('handles string max height', () => {
      const { container } = render(<ActivityFeed {...defaultProps} maxHeight="auto" />);

      const scrollContainer = container.querySelector('.overflow-y-auto');
      expect(scrollContainer).toHaveStyle({ maxHeight: 'auto' });
    });
  });
});
