import React, { useContext } from 'react';
import { act, render, screen } from '@testing-library/react';
import { MessageContext, MessageProvider } from './MessageContext';
import { WebSocketContext } from './WebSocketContext';
import { AccountContext } from './AccountContext';
import { ConversationContext } from './ConversationContext';
import { trackEvent } from '../utils/analytics';

jest.mock('../../axiosApi', () => ({
  axiosInstance: {
    get: jest.fn().mockResolvedValue({ data: [] }),
  },
}));

jest.mock('../utils/analytics', () => ({
  AnalyticsEvents: {
    CONVERSATION_CREATED: 'Conversation Created',
    ACTIVITY_STAGE_COMPLETED: 'Activity Stage Completed',
  },
  trackEvent: jest.fn(),
}));

type Capture = { callback: ((message: string) => void) | null };

const buildHarness = () => {
  const capture: Capture = { callback: null };
  const subscribe = (_channel: string, cb: (message: string) => void) => {
    capture.callback = cb;
  };
  const unsubscribe = jest.fn();

  const Harness = ({ reconnectGeneration = 0 }: { reconnectGeneration?: number }) => (
    <AccountContext.Provider
      value={{ account: { email: 'test@example.com' } as never, setAccount: () => {} }}
    >
      <ConversationContext.Provider
        value={{
          conversations: [],
          setConversations: () => {},
          selectedConversation: 1,
          setSelectedConversation: () => {},
          deleteConversation: () => {},
        }}
      >
        <WebSocketContext.Provider
          value={[subscribe, unsubscribe, null, jest.fn(), true, 'CONNECTED', reconnectGeneration] as never}
        >
          <MessageProvider>
            <Probe />
          </MessageProvider>
        </WebSocketContext.Provider>
      </ConversationContext.Provider>
    </AccountContext.Provider>
  );

  return { Harness, capture };
};

const Probe = () => {
  const ctx = useContext(MessageContext);
  return (
    <div>
      <span data-testid="stage">{ctx.activityStage ?? 'null'}</span>
      <span data-testid="label">{ctx.activityLabel ?? 'null'}</span>
      <span data-testid="detail">{ctx.activityDetail ?? 'null'}</span>
      <span data-testid="history-count">{ctx.activityHistory.length}</span>
      <span data-testid="stream-interrupted">{ctx.streamInterrupted ? 'yes' : 'no'}</span>
      <button type="button" onClick={() => ctx.clearActivity()}>
        clear
      </button>
    </div>
  );
};

const send = (capture: Capture, message: string) => {
  act(() => {
    capture.callback?.(message);
  });
};

const sendStatus = (
  capture: Capture,
  stage: string,
  label: string,
  detail: string | null = null,
) => {
  send(
    capture,
    JSON.stringify({ v: 1, type: 'status', data: { stage, label, detail } }),
  );
};

describe('MessageContext activity status (#96)', () => {
  beforeEach(() => {
    (trackEvent as jest.Mock).mockClear();
  });

  it('applies the first status frame as the current stage/label/detail', async () => {
    const { Harness, capture } = buildHarness();
    render(<Harness />);
    await act(async () => {});

    sendStatus(capture, 'searching', 'Searching the web', 'query: cats');

    expect(screen.getByTestId('stage')).toHaveTextContent('searching');
    expect(screen.getByTestId('label')).toHaveTextContent('Searching the web');
    expect(screen.getByTestId('detail')).toHaveTextContent('query: cats');
    expect(screen.getByTestId('history-count')).toHaveTextContent('0');
  });

  it('rolls a completed stage into history and tracks ACTIVITY_STAGE_COMPLETED (no label/detail text)', async () => {
    const { Harness, capture } = buildHarness();
    render(<Harness />);
    await act(async () => {});

    sendStatus(capture, 'searching', 'Searching the web');
    sendStatus(capture, 'reading', 'Reading sources');

    expect(screen.getByTestId('stage')).toHaveTextContent('reading');
    expect(screen.getByTestId('label')).toHaveTextContent('Reading sources');
    expect(screen.getByTestId('history-count')).toHaveTextContent('1');

    expect(trackEvent).toHaveBeenCalledWith(
      'Activity Stage Completed',
      expect.objectContaining({ stage: 'searching', durationMs: expect.any(Number) }),
    );
    const call = (trackEvent as jest.Mock).mock.calls.find(
      ([name]) => name === 'Activity Stage Completed',
    );
    expect(Object.keys(call![1])).toEqual(['stage', 'durationMs']);
  });

  it('clears the label display (but not the stage) once the writing stage starts', async () => {
    const { Harness, capture } = buildHarness();
    render(<Harness />);
    await act(async () => {});

    sendStatus(capture, 'searching', 'Searching the web');
    sendStatus(capture, 'writing', 'Writing the answer');

    expect(screen.getByTestId('stage')).toHaveTextContent('writing');
    expect(screen.getByTestId('label')).toHaveTextContent('null');
  });

  it('clears activity on END_OF_THE_STREAM', async () => {
    const { Harness, capture } = buildHarness();
    render(<Harness />);
    await act(async () => {});

    sendStatus(capture, 'searching', 'Searching the web');
    sendStatus(capture, 'writing', 'Writing the answer');
    send(capture, 'END_OF_THE_STREAM_ENDER_GAME_42');

    expect(screen.getByTestId('stage')).toHaveTextContent('null');
    expect(screen.getByTestId('label')).toHaveTextContent('null');
    expect(screen.getByTestId('detail')).toHaveTextContent('null');
    expect(screen.getByTestId('history-count')).toHaveTextContent('0');
  });

  it('ignores malformed status frames without throwing', async () => {
    const { Harness, capture } = buildHarness();
    render(<Harness />);
    await act(async () => {});

    send(
      capture,
      JSON.stringify({ v: 1, type: 'status', data: { stage: 'searching' } }),
    );

    expect(screen.getByTestId('stage')).toHaveTextContent('null');
  });

  it('clears activity via clearActivity()', async () => {
    const { Harness, capture } = buildHarness();
    render(<Harness />);
    await act(async () => {});

    sendStatus(capture, 'searching', 'Searching the web');
    expect(screen.getByTestId('stage')).toHaveTextContent('searching');

    act(() => {
      screen.getByRole('button', { name: 'clear' }).click();
    });

    expect(screen.getByTestId('stage')).toHaveTextContent('null');
  });

  it('clears activity on the streamInterrupted reconnect path', async () => {
    const { Harness, capture } = buildHarness();
    const { rerender } = render(<Harness reconnectGeneration={0} />);
    await act(async () => {});

    // Simulate an in-flight generating turn so the reconnect effect treats it as interrupted.
    send(capture, 'CONVERSATION_ID');
    sendStatus(capture, 'searching', 'Searching the web');
    expect(screen.getByTestId('stage')).toHaveTextContent('searching');

    rerender(<Harness reconnectGeneration={1} />);
    await act(async () => {});

    expect(screen.getByTestId('stream-interrupted')).toHaveTextContent('yes');
    expect(screen.getByTestId('stage')).toHaveTextContent('null');
  });
});
