/**
 * ProjectDashboard Component Tests
 *
 * Comprehensive tests for the main project dashboard component.
 */

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

// Mock child components to isolate ProjectDashboard testing
jest.mock('@/components/projects/ProjectHeader', () => ({
  ProjectHeader: jest.fn(({ project, onSettings }) => (
    <div data-testid="mock-project-header">
      <span>{project.name}</span>
      <button onClick={onSettings}>Settings</button>
    </div>
  )),
}));

jest.mock('@/components/projects/AgentPanel', () => ({
  AgentPanel: jest.fn(({ agents, onManageAgents, onAgentAction }) => (
    <div data-testid="mock-agent-panel">
      <span>Agents: {agents.length}</span>
      <button onClick={onManageAgents}>Manage Agents</button>
      <button onClick={() => onAgentAction('agent-001', 'view')}>View Agent</button>
    </div>
  )),
}));

jest.mock('@/components/projects/SprintProgress', () => ({
  SprintProgress: jest.fn(({ sprint }) => (
    <div data-testid="mock-sprint-progress">
      <span>{sprint.name}</span>
    </div>
  )),
}));

jest.mock('@/components/projects/IssueSummary', () => ({
  IssueSummary: jest.fn(({ summary, onViewAllIssues }) => (
    <div data-testid="mock-issue-summary">
      <span>Total: {summary.total}</span>
      <button onClick={onViewAllIssues}>View Issues</button>
    </div>
  )),
}));

jest.mock('@/components/projects/RecentActivity', () => ({
  RecentActivity: jest.fn(({ activities, onViewAll, onActionClick }) => (
    <div data-testid="mock-recent-activity">
      <span>Activities: {activities.length}</span>
      <button onClick={onViewAll}>View All</button>
      <button onClick={() => onActionClick('act-001')}>Action Click</button>
    </div>
  )),
}));

jest.mock('@/components/events/ConnectionStatus', () => ({
  ConnectionStatus: jest.fn(({ state, onReconnect }) => (
    <div data-testid="mock-connection-status">
      <span>State: {state}</span>
      <button onClick={onReconnect}>Reconnect</button>
    </div>
  )),
}));

// Mock useProjectEvents hook
const mockReconnect = jest.fn();
const mockDisconnect = jest.fn();
const mockClearEvents = jest.fn();

const mockUseProjectEventsDefault = {
  events: [] as ProjectEvent[],
  isConnected: true,
  connectionState: 'connected' as ConnectionState,
  error: null as {
    message: string;
    timestamp: string;
    code?: string;
    retryAttempt?: number;
  } | null,
  retryCount: 0,
  reconnect: mockReconnect,
  disconnect: mockDisconnect,
  clearEvents: mockClearEvents,
};

let mockUseProjectEventsResult = { ...mockUseProjectEventsDefault };

jest.mock('@/lib/hooks/useProjectEvents', () => ({
  useProjectEvents: jest.fn(() => mockUseProjectEventsResult),
}));

// Mock next/navigation
const mockPush = jest.fn();
jest.mock('next/navigation', () => ({
  useRouter: () => ({
    push: mockPush,
    back: jest.fn(),
    forward: jest.fn(),
    refresh: jest.fn(),
    replace: jest.fn(),
    prefetch: jest.fn(),
  }),
}));

describe('ProjectDashboard', () => {
  const projectId = 'test-project-123';

  beforeEach(() => {
    jest.clearAllMocks();
    mockUseProjectEventsResult = { ...mockUseProjectEventsDefault };
  });

  describe('Rendering', () => {
    it('renders the dashboard with test id', () => {
      render(<ProjectDashboard projectId={projectId} />);
      expect(screen.getByTestId('project-dashboard')).toBeInTheDocument();
    });

    it('renders ProjectHeader component', () => {
      render(<ProjectDashboard projectId={projectId} />);
      expect(screen.getByTestId('mock-project-header')).toBeInTheDocument();
      expect(screen.getByText('E-Commerce Platform Redesign')).toBeInTheDocument();
    });

    it('renders AgentPanel component', () => {
      render(<ProjectDashboard projectId={projectId} />);
      expect(screen.getByTestId('mock-agent-panel')).toBeInTheDocument();
      expect(screen.getByText('Agents: 5')).toBeInTheDocument();
    });

    it('renders SprintProgress component', () => {
      render(<ProjectDashboard projectId={projectId} />);
      expect(screen.getByTestId('mock-sprint-progress')).toBeInTheDocument();
      expect(screen.getByText('Sprint 3')).toBeInTheDocument();
    });

    it('renders IssueSummary component', () => {
      render(<ProjectDashboard projectId={projectId} />);
      expect(screen.getByTestId('mock-issue-summary')).toBeInTheDocument();
      expect(screen.getByText('Total: 70')).toBeInTheDocument();
    });

    it('renders RecentActivity component', () => {
      render(<ProjectDashboard projectId={projectId} />);
      expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
    });

    it('applies custom className', () => {
      render(<ProjectDashboard projectId={projectId} className="custom-class" />);
      expect(screen.getByTestId('project-dashboard')).toHaveClass('custom-class');
    });
  });

  describe('Connection Status', () => {
    it('does not show ConnectionStatus when connected', () => {
      mockUseProjectEventsResult.connectionState = 'connected';
      render(<ProjectDashboard projectId={projectId} />);
      expect(screen.queryByTestId('mock-connection-status')).not.toBeInTheDocument();
    });

    it('shows ConnectionStatus when disconnected', () => {
      mockUseProjectEventsResult.connectionState = 'disconnected';
      render(<ProjectDashboard projectId={projectId} />);
      expect(screen.getByTestId('mock-connection-status')).toBeInTheDocument();
      expect(screen.getByText('State: disconnected')).toBeInTheDocument();
    });

    it('shows ConnectionStatus when connecting', () => {
      mockUseProjectEventsResult.connectionState = 'connecting';
      render(<ProjectDashboard projectId={projectId} />);
      expect(screen.getByTestId('mock-connection-status')).toBeInTheDocument();
    });

    it('shows ConnectionStatus when error', () => {
      mockUseProjectEventsResult.connectionState = 'error';
      mockUseProjectEventsResult.error = {
        message: 'Connection failed',
        timestamp: new Date().toISOString(),
      };
      render(<ProjectDashboard projectId={projectId} />);
      expect(screen.getByTestId('mock-connection-status')).toBeInTheDocument();
    });
  });

  describe('Navigation callbacks', () => {
    it('navigates to settings when Settings is clicked', async () => {
      const user = userEvent.setup();
      render(<ProjectDashboard projectId={projectId} />);

      await user.click(screen.getByText('Settings'));
      expect(mockPush).toHaveBeenCalledWith(`/projects/${projectId}/settings`);
    });

    it('navigates to agents page when Manage Agents is clicked', async () => {
      const user = userEvent.setup();
      render(<ProjectDashboard projectId={projectId} />);

      await user.click(screen.getByText('Manage Agents'));
      expect(mockPush).toHaveBeenCalledWith(`/projects/${projectId}/agents`);
    });

    it('navigates to issues page when View Issues is clicked', async () => {
      const user = userEvent.setup();
      render(<ProjectDashboard projectId={projectId} />);

      await user.click(screen.getByText('View Issues'));
      expect(mockPush).toHaveBeenCalledWith(`/projects/${projectId}/issues`);
    });

    it('navigates to activity page when View All activity is clicked', async () => {
      const user = userEvent.setup();
      render(<ProjectDashboard projectId={projectId} />);

      await user.click(screen.getByText('View All'));
      expect(mockPush).toHaveBeenCalledWith(`/projects/${projectId}/activity`);
    });
  });

  describe('Action callbacks', () => {
    it('handles agent action', async () => {
      const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
      const user = userEvent.setup();
      render(<ProjectDashboard projectId={projectId} />);

      await user.click(screen.getByText('View Agent'));
      expect(consoleSpy).toHaveBeenCalledWith('Agent action: view on agent-001');
      consoleSpy.mockRestore();
    });

    it('handles activity action click', async () => {
      const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
      const user = userEvent.setup();
      render(<ProjectDashboard projectId={projectId} />);

      await user.click(screen.getByText('Action Click'));
      expect(consoleSpy).toHaveBeenCalledWith('Action clicked for activity: act-001');
      consoleSpy.mockRestore();
    });
  });

  describe('SSE Events Integration', () => {
    it('merges SSE events with mock activities', () => {
      const sseEvent: ProjectEvent = {
        id: 'sse-event-1',
        type: EventType.AGENT_MESSAGE,
        timestamp: new Date().toISOString(),
        project_id: projectId,
        actor_id: 'agent-001',
        actor_type: 'agent',
        payload: {
          message: 'Test message',
          agent_name: 'Test Agent',
        },
      };

      mockUseProjectEventsResult.events = [sseEvent];
      render(<ProjectDashboard projectId={projectId} />);

      // RecentActivity should receive merged activities
      expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
    });
  });
});

// Test eventToActivity helper function behavior through component integration
describe('Event to Activity Conversion', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockUseProjectEventsResult = { ...mockUseProjectEventsDefault };
  });

  const createMockEvent = (
    type: EventType,
    payload: Record<string, unknown>,
    actorType: 'agent' | 'system' | 'user' = 'agent'
  ): ProjectEvent => ({
    id: `event-${Date.now()}`,
    type,
    timestamp: new Date().toISOString(),
    project_id: 'test-project',
    actor_id: actorType === 'system' ? null : 'actor-001',
    actor_type: actorType,
    payload,
  });

  it('handles AGENT_SPAWNED events', () => {
    const event = createMockEvent(EventType.AGENT_SPAWNED, {
      agent_name: 'Product Owner',
      role: 'product_owner',
    });
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('handles AGENT_MESSAGE events', () => {
    const event = createMockEvent(EventType.AGENT_MESSAGE, {
      message: 'Task completed',
      agent_name: 'Backend Engineer',
    });
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('handles AGENT_STATUS_CHANGED events', () => {
    const event = createMockEvent(EventType.AGENT_STATUS_CHANGED, {
      new_status: 'working',
      agent_name: 'QA Engineer',
    });
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('handles ISSUE_CREATED events', () => {
    const event = createMockEvent(EventType.ISSUE_CREATED, {
      title: 'New Feature Request',
      issue_id: 'issue-001',
    });
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('handles ISSUE_UPDATED events', () => {
    const event = createMockEvent(EventType.ISSUE_UPDATED, {
      issue_id: 'issue-001',
    });
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('handles APPROVAL_REQUESTED events', () => {
    const event = createMockEvent(EventType.APPROVAL_REQUESTED, {
      description: 'Please approve the design',
      approval_id: 'approval-001',
    });
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('handles SPRINT_STARTED events', () => {
    const event = createMockEvent(
      EventType.SPRINT_STARTED,
      {
        sprint_name: 'Sprint 4',
      },
      'system'
    );
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('handles unknown event types gracefully', () => {
    // Create an event with an unknown type prefix
    const event: ProjectEvent = {
      id: 'unknown-event',
      type: 'custom.event' as EventType,
      timestamp: new Date().toISOString(),
      project_id: 'test-project',
      actor_id: null,
      actor_type: 'system',
      payload: {},
    };
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('handles agent events with agent_name in payload', () => {
    const event = createMockEvent(EventType.AGENT_MESSAGE, {
      message: 'Hello',
      agent_name: 'Test Agent',
    });
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('defaults agent name when not in payload', () => {
    const event = createMockEvent(EventType.AGENT_MESSAGE, {
      message: 'Hello',
    });
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('handles system actor type', () => {
    const event = createMockEvent(EventType.SPRINT_STARTED, { sprint_name: 'Sprint 5' }, 'system');
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });

  it('handles user actor type', () => {
    const event = createMockEvent(EventType.APPROVAL_REQUESTED, { description: 'Approve' }, 'user');
    mockUseProjectEventsResult.events = [event];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });
});

// Test sorting and limiting of activities
describe('Activity Management', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockUseProjectEventsResult = { ...mockUseProjectEventsDefault };
  });

  it('limits activities to 10 items', () => {
    // Create more than 10 SSE events
    const manyEvents: ProjectEvent[] = Array.from({ length: 15 }, (_, i) => ({
      id: `event-${i}`,
      type: EventType.AGENT_MESSAGE,
      timestamp: new Date(Date.now() - i * 60000).toISOString(),
      project_id: 'test-project',
      actor_id: 'agent-001',
      actor_type: 'agent' as const,
      payload: { message: `Message ${i}` },
    }));

    mockUseProjectEventsResult.events = manyEvents;
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
    // The mock shows activities count - with SSE + mock = max 10
    expect(screen.getByText(/Activities:/)).toBeInTheDocument();
  });

  it('sorts activities by timestamp (newest first)', () => {
    const oldEvent: ProjectEvent = {
      id: 'old-event',
      type: EventType.AGENT_MESSAGE,
      timestamp: new Date(Date.now() - 100000).toISOString(),
      project_id: 'test-project',
      actor_id: 'agent-001',
      actor_type: 'agent',
      payload: { message: 'Old message' },
    };

    const newEvent: ProjectEvent = {
      id: 'new-event',
      type: EventType.AGENT_MESSAGE,
      timestamp: new Date().toISOString(),
      project_id: 'test-project',
      actor_id: 'agent-001',
      actor_type: 'agent',
      payload: { message: 'New message' },
    };

    mockUseProjectEventsResult.events = [oldEvent, newEvent];
    render(<ProjectDashboard projectId="test" />);
    expect(screen.getByTestId('mock-recent-activity')).toBeInTheDocument();
  });
});
