/** @vitest-environment jsdom */

import { act, render } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';

import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent';
import type { RpgCreationResultView } from '../../../packages/shared/src/contracts/rpgCreationResultView';
import type { CustomWorldProfileRecord } from '../../../packages/shared/src/contracts/runtime';
import { type CustomWorldProfile, WorldType } from '../../types';
import { useRpgCreationEnterWorld } from './useRpgCreationEnterWorld';

function buildProfile(params: {
  id: string;
  name: string;
  imageSrc: string;
}): CustomWorldProfile {
  return {
    id: params.id,
    settingText: params.name,
    name: params.name,
    subtitle: params.name,
    summary: params.name,
    tone: '测试',
    playerGoal: '测试',
    templateWorldType: WorldType.WUXIA,
    compatibilityTemplateWorldType: WorldType.WUXIA,
    majorFactions: [],
    coreConflicts: [],
    attributeSchema: {
      id: `${params.id}-attribute-schema`,
      worldId: params.id,
      schemaVersion: 1,
      generatedFrom: {
        worldType: WorldType.CUSTOM,
        worldName: params.name,
        settingSummary: params.name,
        tone: '测试',
        conflictCore: '测试',
      },
      slots: [],
    },
    playableNpcs: [
      {
        id: `${params.id}-role`,
        name: '可扮演角色',
        title: '测试角色',
        role: '主角',
        description: '测试角色',
        backstory: '测试背景',
        personality: '测试性格',
        motivation: '测试动机',
        combatStyle: '测试战斗风格',
        initialAffinity: 18,
        relationshipHooks: [],
        tags: [],
        backstoryReveal: {
          publicSummary: '测试角色',
          privateChatUnlockAffinity: 60,
          chapters: [],
        },
        skills: [],
        initialItems: [],
        imageSrc: params.imageSrc,
      },
    ],
    storyNpcs: [],
    items: [],
    landmarks: [],
    generationMode: 'full',
    generationStatus: 'complete',
  };
}

function buildSession(
  stage: CustomWorldAgentSessionSnapshot['stage'] = 'ready_to_publish',
): CustomWorldAgentSessionSnapshot {
  return {
    sessionId: 'session-1',
    currentTurn: 1,
    anchorContent: {
      worldPromise: null,
      playerFantasy: null,
      themeBoundary: null,
      playerEntryPoint: null,
      coreConflict: null,
      keyRelationships: null,
      hiddenLines: null,
      iconicElements: null,
    },
    progressPercent: 100,
    lastAssistantReply: '',
    stage,
    focusCardId: null,
    creatorIntent: null,
    creatorIntentReadiness: {
      isReady: true,
      completedKeys: [],
      missingKeys: [],
    },
    anchorPack: null,
    lockState: null,
    draftProfile: null,
    messages: [],
    draftCards: [],
    pendingClarifications: [],
    suggestedActions: [],
    recommendedReplies: [],
    qualityFindings: [],
    assetCoverage: {
      roleAssets: [],
      sceneAssets: [],
      allRoleAssetsReady: true,
      allSceneAssetsReady: true,
    },
    resultPreview: null,
    updatedAt: '2026-04-25T00:00:00.000Z',
  };
}

function buildResultView(params: {
  stage?: CustomWorldAgentSessionSnapshot['stage'];
  profile: CustomWorldProfile | null;
  canEnterWorld?: boolean;
}): RpgCreationResultView {
  const stage = params.stage ?? 'ready_to_publish';
  const profileRecord = params.profile
    ? (structuredClone(params.profile) as unknown as CustomWorldProfileRecord)
    : null;
  return {
    session: buildSession(stage),
    profile: profileRecord,
    profileSource: profileRecord ? 'result_preview' : 'none',
    targetStage: 'custom-world-result',
    generationViewSource: null,
    resultViewSource: profileRecord ? 'agent-draft' : null,
    canAutosaveLibrary: true,
    canSyncResultProfile: stage !== 'published',
    publishReady: true,
    canEnterWorld: params.canEnterWorld ?? stage === 'published',
    blockerCount: 0,
    recoveryAction: 'open_result',
  };
}

describe('useRpgCreationEnterWorld', () => {
  it('Agent 草稿测试进入游戏时优先使用结果页当前 profile，而不是回退到会话快照', async () => {
    const resultProfile = buildProfile({
      id: 'draft-profile',
      name: '结果页真相源',
      imageSrc: '/generated-characters/draft-role/portrait.png',
    });
    const handleCustomWorldSelect = vi.fn();
    const setGeneratedCustomWorldProfile = vi.fn();
    const executePublishWorld = vi.fn(async () => buildSession());
    const syncAgentCreationResultView = vi.fn();
    const syncAgentDraftResultProfile = vi.fn(async () => ({
      profile: resultProfile,
      view: null,
    }));

    function Harness() {
      const { enterWorldForTestFromCurrentResult } = useRpgCreationEnterWorld({
        isAgentDraftResultView: true,
        activeAgentSessionId: 'session-1',
        generatedCustomWorldProfile: resultProfile,
        handleCustomWorldSelect,
        syncAgentDraftResultProfile,
        executePublishWorld,
        syncAgentCreationResultView,
        setGeneratedCustomWorldProfile,
      });

      return (
        <button
          type="button"
          onClick={() => void enterWorldForTestFromCurrentResult()}
        >
          进入
        </button>
      );
    }

    const { getByText } = render(<Harness />);
    await act(async () => {
      getByText('进入').click();
    });

    expect(executePublishWorld).not.toHaveBeenCalled();
    expect(handleCustomWorldSelect).toHaveBeenCalledWith(resultProfile, {
      mode: 'play',
      disablePersistence: true,
      returnStage: 'custom-world-result',
    });
    expect(setGeneratedCustomWorldProfile).toHaveBeenCalledWith(resultProfile);
    expect(
      handleCustomWorldSelect.mock.calls[0]?.[0].playableNpcs[0]?.imageSrc,
    ).toBe('/generated-characters/draft-role/portrait.png');
  });

  it('Agent 草稿发布时先保存当前结果页 profile，再发送 publish_world 并回读结果页', async () => {
    const resultProfile = buildProfile({
      id: 'draft-profile',
      name: '发布前填写内容',
      imageSrc: '/generated-characters/draft-role/portrait.png',
    });
    const syncedProfile = buildProfile({
      id: 'draft-profile',
      name: '已保存的填写内容',
      imageSrc: '/generated-characters/draft-role/synced.png',
    });
    const publishedProfile = buildProfile({
      id: 'draft-profile',
      name: '已发布世界',
      imageSrc: '/generated-characters/draft-role/published.png',
    });
    const callOrder: string[] = [];
    const handleCustomWorldSelect = vi.fn();
    const setGeneratedCustomWorldProfile = vi.fn();
    const syncAgentDraftResultProfile = vi.fn(async () => {
      callOrder.push('save');
      return {
        profile: syncedProfile,
        view: buildResultView({
          stage: 'ready_to_publish',
          profile: syncedProfile,
          canEnterWorld: false,
        }),
      };
    });
    const executePublishWorld = vi.fn(async () => {
      callOrder.push('publish');
      return buildSession('published');
    });
    const syncAgentCreationResultView = vi.fn(async () => {
      callOrder.push('reload');
      return buildResultView({
        stage: 'published',
        profile: publishedProfile,
        canEnterWorld: true,
      });
    });

    function Harness() {
      const { publishCurrentResult } = useRpgCreationEnterWorld({
        isAgentDraftResultView: true,
        activeAgentSessionId: 'session-1',
        currentAgentSessionStage: 'ready_to_publish',
        generatedCustomWorldProfile: resultProfile,
        handleCustomWorldSelect,
        syncAgentDraftResultProfile,
        executePublishWorld,
        syncAgentCreationResultView,
        setGeneratedCustomWorldProfile,
      });

      return (
        <button type="button" onClick={() => void publishCurrentResult()}>
          发布
        </button>
      );
    }

    const { getByText } = render(<Harness />);
    await act(async () => {
      getByText('发布').click();
    });

    expect(callOrder).toEqual(['save', 'publish', 'reload']);
    expect(syncAgentDraftResultProfile).toHaveBeenCalledWith(resultProfile);
    expect(executePublishWorld).toHaveBeenCalledTimes(1);
    expect(syncAgentCreationResultView).toHaveBeenCalledWith('session-1');
    expect(setGeneratedCustomWorldProfile).toHaveBeenCalledWith(syncedProfile);
    expect(
      setGeneratedCustomWorldProfile.mock.calls.at(-1)?.[0]?.id,
    ).toBe('draft-profile');
    expect(
      setGeneratedCustomWorldProfile.mock.calls.at(-1)?.[0]?.playableNpcs[0]
        ?.imageSrc,
    ).toBe('/generated-characters/draft-role/published.png');
    expect(handleCustomWorldSelect).not.toHaveBeenCalled();
  });

  it('Agent 会话已发布后点击进入世界不再重复发送 publish_world', async () => {
    const resultProfile = buildProfile({
      id: 'published-profile',
      name: '已发布世界',
      imageSrc: '/generated-characters/published-role/portrait.png',
    });
    const publishedView = buildResultView({
      stage: 'published',
      profile: resultProfile,
      canEnterWorld: true,
    });
    const handleCustomWorldSelect = vi.fn();
    const setGeneratedCustomWorldProfile = vi.fn();
    const executePublishWorld = vi.fn(async () => buildSession('published'));
    const syncAgentCreationResultView = vi.fn(async () => publishedView);
    const syncAgentDraftResultProfile = vi.fn(async () => ({
      profile: resultProfile,
      view: null,
    }));

    function Harness() {
      const { enterWorldFromCurrentResult } = useRpgCreationEnterWorld({
        isAgentDraftResultView: true,
        activeAgentSessionId: 'session-1',
        currentAgentSessionStage: 'published',
        generatedCustomWorldProfile: resultProfile,
        handleCustomWorldSelect,
        syncAgentDraftResultProfile,
        executePublishWorld,
        syncAgentCreationResultView,
        setGeneratedCustomWorldProfile,
      });

      return (
        <button
          type="button"
          onClick={() => void enterWorldFromCurrentResult()}
        >
          进入世界
        </button>
      );
    }

    const { getByText } = render(<Harness />);
    await act(async () => {
      getByText('进入世界').click();
    });

    expect(syncAgentDraftResultProfile).not.toHaveBeenCalled();
    expect(executePublishWorld).not.toHaveBeenCalled();
    expect(syncAgentCreationResultView).toHaveBeenCalledWith('session-1');
    expect(setGeneratedCustomWorldProfile).toHaveBeenCalledTimes(1);
    expect(setGeneratedCustomWorldProfile.mock.calls[0]?.[0]?.id).toBe(
      'published-profile',
    );
    expect(handleCustomWorldSelect).toHaveBeenCalledTimes(1);
    expect(handleCustomWorldSelect.mock.calls[0]?.[0]?.id).toBe(
      'published-profile',
    );
  });


  it('正式进入世界回读结果页字段更少时不降级当前完整 profile', async () => {
    const resultProfile = {
      ...buildProfile({
        id: 'draft-profile-rich-assets',
        name: '星砂废都',
        imageSrc: '/generated-characters/draft-role/portrait.png',
      }),
      cover: {
        sourceType: 'generated' as const,
        imageSrc: '/generated-custom-world-covers/star-waste/cover.webp',
        characterRoleIds: ['draft-profile-rich-assets-role'],
      },
      openingCg: {
        id: 'opening-cg-stardust',
        status: 'ready' as const,
        storyboardImageSrc: '/generated-custom-world-scenes/opening/storyboard.png',
        videoSrc: '/generated-custom-world-scenes/opening/opening.mp4',
        imageModel: 'gpt-image-2' as const,
        videoModel: 'doubao-seedance-2-0-fast-260128',
        aspectRatio: '16:9' as const,
        imageSize: '2k' as const,
        videoResolution: '480p' as const,
        durationSeconds: 15 as const,
        pointCost: 80 as const,
        estimatedWaitMinutes: 10 as const,
        updatedAt: '2026-05-21T00:00:00.000Z',
      },
      sceneChapterBlueprints: [
        {
          id: 'scene-chapter-stardust',
          sceneId: 'landmark-stardust',
          title: '钟楼第一夜',
          summary: '钟楼第一夜。',
          sceneTaskDescription: '进入钟楼。',
          linkedThreadIds: [],
          linkedLandmarkIds: ['landmark-stardust'],
          acts: [
            {
              id: 'act-stardust-opening',
              sceneId: 'landmark-stardust',
              title: '第一幕',
              summary: '砂眠带玩家进入坠星钟楼。',
              stageCoverage: ['opening' as const],
              backgroundImageSrc:
                '/assets/custom-world/act-stardust-opening.png',
              backgroundAssetId: 'asset-act-stardust-opening',
              encounterNpcIds: ['draft-profile-rich-assets-role'],
              primaryNpcId: 'draft-profile-rich-assets-role',
              oppositeNpcId: 'draft-profile-rich-assets-role',
              eventDescription: '钟楼旧铃忽然自鸣。',
              linkedThreadIds: [],
              advanceRule: 'after_primary_contact' as const,
              actGoal: '进入钟楼。',
              transitionHook: '星砂开始倒流。',
            },
          ],
        },
      ],
    } satisfies CustomWorldProfile;
    const stalePublishedProfile = {
      ...resultProfile,
      name: '星砂废都',
      cover: null,
      openingCg: null,
      playableNpcs: [],
      sceneChapterBlueprints: null,
    } satisfies CustomWorldProfile;
    const handleCustomWorldSelect = vi.fn();
    const setGeneratedCustomWorldProfile = vi.fn();
    const syncAgentDraftResultProfile = vi.fn(async () => ({
      profile: resultProfile,
      view: buildResultView({
        stage: 'ready_to_publish',
        profile: resultProfile,
        canEnterWorld: false,
      }),
    }));
    const executePublishWorld = vi.fn(async () => buildSession('published'));
    const syncAgentCreationResultView = vi.fn(async () =>
      buildResultView({
        stage: 'published',
        profile: stalePublishedProfile,
        canEnterWorld: true,
      }),
    );

    function Harness() {
      const { enterWorldFromCurrentResult } = useRpgCreationEnterWorld({
        isAgentDraftResultView: true,
        activeAgentSessionId: 'session-1',
        currentAgentSessionStage: 'ready_to_publish',
        generatedCustomWorldProfile: resultProfile,
        handleCustomWorldSelect,
        syncAgentDraftResultProfile,
        executePublishWorld,
        syncAgentCreationResultView,
        setGeneratedCustomWorldProfile,
      });

      return (
        <button
          type="button"
          onClick={() => void enterWorldFromCurrentResult()}
        >
          进入世界
        </button>
      );
    }

    const { getByText } = render(<Harness />);
    await act(async () => {
      getByText('进入世界').click();
    });

    const launchedProfile = handleCustomWorldSelect.mock.calls[0]?.[0];
    expect(launchedProfile?.id).toBe('draft-profile-rich-assets');
    expect(launchedProfile?.cover?.imageSrc).toBe(
      '/generated-custom-world-covers/star-waste/cover.webp',
    );
    expect(launchedProfile?.openingCg?.videoSrc).toBe(
      '/generated-custom-world-scenes/opening/opening.mp4',
    );
    expect(launchedProfile?.playableNpcs[0]?.imageSrc).toBe(
      '/generated-characters/draft-role/portrait.png',
    );
    expect(
      launchedProfile?.sceneChapterBlueprints?.[0]?.acts[0]
        ?.backgroundImageSrc,
    ).toBe('/assets/custom-world/act-stardust-opening.png');
  });
});
