/* @vitest-environment jsdom */

import { act, render, screen, waitFor } from '@testing-library/react';
import { useEffect, useRef, useState } from 'react';
import { expect, test, vi } from 'vitest';

import { usePlatformCreationAgentFlowController } from './usePlatformCreationAgentFlowController';

type TestSession = {
  sessionId: string;
  messages: Array<{
    id: string;
    role: string;
    kind?: string;
    text: string;
    createdAt?: string;
  }>;
};

type TestMessagePayload = {
  clientMessageId: string;
  text: string;
};

function TestHarness({
  streamMessage,
}: {
  streamMessage: (
    sessionId: string,
    payload: TestMessagePayload,
    options?: { onUpdate?: (text: string) => void },
  ) => Promise<TestSession>;
}) {
  const hasOpenedRef = useRef(false);
  const flow = usePlatformCreationAgentFlowController<
    TestSession,
    Record<string, never>,
    { session: TestSession },
    TestMessagePayload,
    { action: string },
    { session: TestSession }
  >({
    client: {
      createSession: async () => ({
        session: {
          sessionId: 'session-1',
          messages: [],
        },
      }),
      getSession: async () => ({
        session: {
          sessionId: 'session-1',
          messages: [],
        },
      }),
      streamMessage,
      executeAction: async () => ({
        session: {
          sessionId: 'session-1',
          messages: [],
        },
      }),
      selectSession: (response) => response.session,
    },
    createPayload: {},
    workspaceStage: 'match3d-agent-workspace',
    resultStage: 'match3d-result',
    platformStage: 'platform',
    isCompileAction: () => false,
    resolveErrorMessage: (error, fallback) =>
      error instanceof Error ? error.message : fallback,
    errorMessages: {
      open: '打开失败',
      restoreMissingSession: '缺少会话',
      restore: '恢复失败',
      submit: '发送失败',
      execute: '执行失败',
    },
    enterCreateTab: () => {},
    setSelectionStage: () => {},
  });

  useEffect(() => {
    if (hasOpenedRef.current) {
      return;
    }
    hasOpenedRef.current = true;
    void flow.openWorkspace({});
  }, [flow]);

  return (
    <div>
      <button
        type="button"
        onClick={() => {
          void flow.submitMessage({
            clientMessageId: 'client-message-1',
            text: '做一个办公室文具方洞挑战',
          });
        }}
      >
        发送
      </button>
      <div data-testid="messages">
        {flow.session?.messages.map((message) => (
          <div
            key={message.id}
          >{`${message.role}:${message.kind}:${message.text}`}</div>
        ))}
      </div>
      {flow.error ? <div>{flow.error}</div> : null}
    </div>
  );
}

type ActionTestSession = TestSession & {
  draft?: { profileId: string } | null;
};

function ActionErrorHarness({
  onActionError,
}: {
  onActionError: (params: {
    session: ActionTestSession;
    setSession: (session: ActionTestSession) => void;
  }) => void;
}) {
  const [stage, setStage] = useState('platform');
  const hasOpenedRef = useRef(false);
  const flow = usePlatformCreationAgentFlowController<
    ActionTestSession,
    Record<string, never>,
    { session: ActionTestSession },
    TestMessagePayload,
    { action: string },
    { session: ActionTestSession }
  >({
    client: {
      createSession: async () => ({
        session: {
          sessionId: 'session-1',
          messages: [],
          draft: { profileId: 'profile-draft-1' },
        },
      }),
      getSession: async () => ({
        session: {
          sessionId: 'session-1',
          messages: [],
          draft: { profileId: 'profile-draft-1' },
        },
      }),
      streamMessage: async () => ({
        sessionId: 'session-1',
        messages: [],
        draft: { profileId: 'profile-draft-1' },
      }),
      executeAction: async () => {
        throw new Error('模型生成失败');
      },
      selectSession: (response) => response.session,
    },
    createPayload: {},
    workspaceStage: 'match3d-agent-workspace',
    resultStage: 'match3d-result',
    platformStage: 'platform',
    isCompileAction: () => true,
    resolveErrorMessage: (error, fallback) =>
      error instanceof Error ? error.message : fallback,
    errorMessages: {
      open: '打开失败',
      restoreMissingSession: '缺少会话',
      restore: '恢复失败',
      submit: '发送失败',
      execute: '执行失败',
    },
    enterCreateTab: () => {},
    setSelectionStage: setStage,
    onActionError: ({ session, setSession }) => {
      onActionError({ session, setSession });
    },
  });

  useEffect(() => {
    if (hasOpenedRef.current) {
      return;
    }
    hasOpenedRef.current = true;
    void flow.openWorkspace({});
  }, [flow]);

  return (
    <div>
      <button
        type="button"
        onClick={() => {
          void flow.executeAction({ action: 'match3d_compile_draft' });
        }}
      >
        执行
      </button>
      <div data-testid="stage">{stage}</div>
      <div data-testid="profile">
        {flow.session?.draft?.profileId ?? 'missing'}
      </div>
      {flow.error ? <div>{flow.error}</div> : null}
    </div>
  );
}

function ActionCompleteHarness({
  onActionComplete,
}: {
  onActionComplete: () => { openResult?: boolean } | void;
}) {
  const [stage, setStage] = useState('platform');
  const hasOpenedRef = useRef(false);
  const flow = usePlatformCreationAgentFlowController<
    ActionTestSession,
    Record<string, never>,
    { session: ActionTestSession },
    TestMessagePayload,
    { action: string },
    { session: ActionTestSession }
  >({
    client: {
      createSession: async () => ({
        session: {
          sessionId: 'session-1',
          messages: [],
          draft: { profileId: 'profile-draft-1' },
        },
      }),
      getSession: async () => ({
        session: {
          sessionId: 'session-1',
          messages: [],
          draft: { profileId: 'profile-draft-1' },
        },
      }),
      streamMessage: async () => ({
        sessionId: 'session-1',
        messages: [],
        draft: { profileId: 'profile-draft-1' },
      }),
      executeAction: async () => ({
        session: {
          sessionId: 'session-1',
          messages: [],
          draft: { profileId: 'profile-ready-1' },
        },
      }),
      selectSession: (response) => response.session,
    },
    createPayload: {},
    workspaceStage: 'match3d-agent-workspace',
    resultStage: 'match3d-result',
    platformStage: 'platform',
    isCompileAction: () => true,
    resolveErrorMessage: (error, fallback) =>
      error instanceof Error ? error.message : fallback,
    errorMessages: {
      open: '打开失败',
      restoreMissingSession: '缺少会话',
      restore: '恢复失败',
      submit: '发送失败',
      execute: '执行失败',
    },
    enterCreateTab: () => {},
    setSelectionStage: setStage,
    onActionComplete: ({ setSession, response }) => {
      setSession(response.session);
      return onActionComplete();
    },
  });

  useEffect(() => {
    if (hasOpenedRef.current) {
      return;
    }
    hasOpenedRef.current = true;
    void flow.openWorkspace({});
  }, [flow]);

  return (
    <div>
      <button
        type="button"
        onClick={() => {
          void flow.executeAction({ action: 'match3d_compile_draft' });
        }}
      >
        执行
      </button>
      <div data-testid="stage">{stage}</div>
      <div data-testid="profile">
        {flow.session?.draft?.profileId ?? 'missing'}
      </div>
    </div>
  );
}

function SessionChangeHarness({
  onSessionChanged,
}: {
  onSessionChanged: (session: TestSession | null) => void;
}) {
  const flow = usePlatformCreationAgentFlowController<
    TestSession,
    Record<string, never>,
    { session: TestSession },
    TestMessagePayload,
    { action: string },
    { session: TestSession }
  >({
    client: {
      createSession: async () => ({
        session: {
          sessionId: 'session-open',
          messages: [],
        },
      }),
      getSession: async () => ({
        session: {
          sessionId: 'session-restore',
          messages: [],
        },
      }),
      streamMessage: async () => ({
        sessionId: 'session-open',
        messages: [],
      }),
      executeAction: async () => ({
        session: {
          sessionId: 'session-compile',
          messages: [],
        },
      }),
      selectSession: (response) => response.session,
    },
    createPayload: {},
    workspaceStage: 'match3d-agent-workspace',
    resultStage: 'match3d-result',
    platformStage: 'platform',
    isCompileAction: (payload) => payload.action === 'match3d_compile_draft',
    resolveErrorMessage: (error, fallback) =>
      error instanceof Error ? error.message : fallback,
    errorMessages: {
      open: '打开失败',
      restoreMissingSession: '缺少会话',
      restore: '恢复失败',
      submit: '发送失败',
      execute: '执行失败',
    },
    enterCreateTab: () => {},
    setSelectionStage: () => {},
    onSessionChanged,
    onActionComplete: ({ response, setSession }) => {
      setSession(response.session);
    },
  });

  return (
    <div>
      <button type="button" onClick={() => void flow.openWorkspace({})}>
        打开
      </button>
      <button
        type="button"
        onClick={() => void flow.restoreDraft('session-restore')}
      >
        恢复
      </button>
      <button
        type="button"
        onClick={() =>
          void flow.executeAction({ action: 'match3d_compile_draft' })
        }
      >
        编译
      </button>
    </div>
  );
}

function SessionSetterIdentityHarness({
  onSetterIdentity,
}: {
  onSetterIdentity: (setter: unknown) => void;
}) {
  const [renderCount, setRenderCount] = useState(0);
  const flow = usePlatformCreationAgentFlowController<
    TestSession,
    Record<string, never>,
    { session: TestSession },
    TestMessagePayload,
    { action: string },
    { session: TestSession }
  >({
    client: {
      createSession: async () => ({
        session: {
          sessionId: 'session-open',
          messages: [],
        },
      }),
      getSession: async () => ({
        session: {
          sessionId: 'session-restore',
          messages: [],
        },
      }),
      streamMessage: async () => ({
        sessionId: 'session-open',
        messages: [],
      }),
      executeAction: async () => ({
        session: {
          sessionId: 'session-compile',
          messages: [],
        },
      }),
      selectSession: (response) => response.session,
    },
    createPayload: {},
    workspaceStage: 'match3d-agent-workspace',
    resultStage: 'match3d-result',
    platformStage: 'platform',
    isCompileAction: () => false,
    resolveErrorMessage: (error, fallback) =>
      error instanceof Error ? error.message : fallback,
    errorMessages: {
      open: '打开失败',
      restoreMissingSession: '缺少会话',
      restore: '恢复失败',
      submit: '发送失败',
      execute: '执行失败',
    },
    enterCreateTab: () => {},
    setSelectionStage: () => {},
    onSessionChanged: () => {},
  });

  useEffect(() => {
    onSetterIdentity(flow.setSession);
  });

  return (
    <button
      type="button"
      onClick={() => setRenderCount((current) => current + 1)}
    >
      重渲染 {renderCount}
    </button>
  );
}

test('creation agent flow preserves streamed assistant text when stream fails', async () => {
  const streamMessage = vi.fn(async (_sessionId, _payload, options) => {
    options?.onUpdate?.('先把方洞万能的反差定住。');
    throw new Error('方洞挑战聊天生成失败：LLM 请求超时');
  });

  render(<TestHarness streamMessage={streamMessage} />);

  await waitFor(() => {
    expect(screen.getByRole('button', { name: '发送' })).toBeTruthy();
  });

  await act(async () => {
    screen.getByRole('button', { name: '发送' }).click();
  });

  await waitFor(() => {
    expect(screen.getByText('方洞挑战聊天生成失败：LLM 请求超时')).toBeTruthy();
  });

  expect(screen.getByTestId('messages').textContent).toContain(
    'user:chat:做一个办公室文具方洞挑战',
  );
  expect(screen.getByTestId('messages').textContent).toContain(
    'assistant:warning:先把方洞万能的反差定住。',
  );
});

test('creation agent flow exposes session setter after compile action fails', async () => {
  const onActionError = vi.fn(
    ({
      setSession,
    }: {
      session: ActionTestSession;
      setSession: (session: ActionTestSession) => void;
    }) => {
      setSession({
        sessionId: 'session-1',
        messages: [],
        draft: { profileId: 'profile-after-failure' },
      });
    },
  );

  render(<ActionErrorHarness onActionError={onActionError} />);

  await waitFor(() => {
    expect(screen.getByRole('button', { name: '执行' })).toBeTruthy();
  });

  await act(async () => {
    screen.getByRole('button', { name: '执行' }).click();
  });

  await waitFor(() => {
    expect(screen.getByText('模型生成失败')).toBeTruthy();
  });

  expect(onActionError).toHaveBeenCalledTimes(1);
  expect(screen.getByTestId('profile').textContent).toBe(
    'profile-after-failure',
  );
  expect(screen.getByTestId('stage').textContent).toBe(
    'match3d-agent-workspace',
  );
});

test('creation agent flow suppresses compile result stage for background completion', async () => {
  const onActionComplete = vi.fn(() => ({ openResult: false }));

  render(<ActionCompleteHarness onActionComplete={onActionComplete} />);

  await waitFor(() => {
    expect(screen.getByTestId('stage').textContent).toBe(
      'match3d-agent-workspace',
    );
  });

  await act(async () => {
    screen.getByRole('button', { name: '执行' }).click();
  });

  await waitFor(() => {
    expect(onActionComplete).toHaveBeenCalledTimes(1);
  });

  expect(screen.getByTestId('profile').textContent).toBe('profile-ready-1');
  expect(screen.getByTestId('stage').textContent).toBe(
    'match3d-agent-workspace',
  );
});

test('creation agent flow notifies session changes after open restore and compile', async () => {
  const onSessionChanged = vi.fn();

  render(<SessionChangeHarness onSessionChanged={onSessionChanged} />);

  await act(async () => {
    screen.getByRole('button', { name: '打开' }).click();
  });
  await act(async () => {
    screen.getByRole('button', { name: '恢复' }).click();
  });
  await act(async () => {
    screen.getByRole('button', { name: '编译' }).click();
  });

  await waitFor(() => {
    expect(onSessionChanged).toHaveBeenCalledTimes(3);
  });

  expect(
    onSessionChanged.mock.calls.map(([session]) => session?.sessionId),
  ).toEqual(['session-open', 'session-restore', 'session-compile']);
});

test('creation agent flow keeps session setter stable across parent rerenders', async () => {
  const onSetterIdentity = vi.fn();

  render(<SessionSetterIdentityHarness onSetterIdentity={onSetterIdentity} />);

  await waitFor(() => {
    expect(onSetterIdentity).toHaveBeenCalledTimes(1);
  });
  const initialSetter = onSetterIdentity.mock.calls[0]?.[0];

  await act(async () => {
    screen.getByRole('button', { name: /重渲染/u }).click();
  });

  await waitFor(() => {
    expect(onSetterIdentity).toHaveBeenCalledTimes(2);
  });

  expect(onSetterIdentity.mock.calls[1]?.[0]).toBe(initialSetter);
});
