/* @vitest-environment jsdom */

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

import { readSavedSettings } from '../persistence/gameSettingsStorage';
import type { GameState, StoryMoment } from '../types';
import type { BottomTab } from '../types/navigation';
import { useRpgSessionPersistence } from './rpg-session';
import { useGameSettings } from './useGameSettings';

const storageMocks = vi.hoisted(() => ({
  getSettings: vi.fn(),
  putSettings: vi.fn(),
  getSaveSnapshot: vi.fn(),
  putSaveSnapshot: vi.fn(),
  deleteSaveSnapshot: vi.fn(),
}));

vi.mock('../services/rpg-entry', () => ({
  getRpgProfileSettings: storageMocks.getSettings,
  putRpgProfileSettings: storageMocks.putSettings,
}));

vi.mock('../services/rpg-runtime', () => ({
  rpgSnapshotClient: {
    getSnapshot: storageMocks.getSaveSnapshot,
    putSnapshot: storageMocks.putSaveSnapshot,
    deleteSnapshot: storageMocks.deleteSaveSnapshot,
  },
}));

function SettingsHarness({ authenticatedUserId }: { authenticatedUserId: string | null }) {
  const settings = useGameSettings(authenticatedUserId);

  return (
    <div>
      <div data-testid="music-volume">{settings.musicVolume.toFixed(2)}</div>
      <button
        type="button"
        onClick={() => {
          settings.setMusicVolume(0.6);
        }}
      >
        设置音量
      </button>
    </div>
  );
}

function PersistenceHarness({
  authenticatedUserId,
}: {
  authenticatedUserId: string | null;
}) {
  const persistence = useRpgSessionPersistence({
    authenticatedUserId,
    gameState: {} as GameState,
    bottomTab: 'adventure' as BottomTab,
    currentStory: null as StoryMoment | null,
    isLoading: false,
    setGameState: () => {},
    setBottomTab: () => {},
    hydrateStoryState: () => {},
    resetStoryState: () => {},
  });

  return (
    <div>
      <div data-testid="saved-game">{persistence.hasSavedGame ? 'yes' : 'no'}</div>
      <div data-testid="hydrating">
        {persistence.isHydratingSnapshot ? 'yes' : 'no'}
      </div>
    </div>
  );
}

beforeEach(() => {
  vi.clearAllMocks();
  vi.useRealTimers();
  window.localStorage.clear();
  storageMocks.getSettings.mockResolvedValue({
    musicVolume: 0.42,
    platformTheme: 'light',
  });
  storageMocks.putSettings.mockResolvedValue({
    musicVolume: 0.6,
    platformTheme: 'light',
  });
  storageMocks.getSaveSnapshot.mockResolvedValue(null);
  storageMocks.putSaveSnapshot.mockResolvedValue(null);
  storageMocks.deleteSaveSnapshot.mockResolvedValue({
    ok: true,
  });
});

test('unauthenticated settings use local cache and skip remote runtime settings requests', async () => {
  window.localStorage.setItem(
    'tavernrealms.settings.v1',
    JSON.stringify({
      version: 1,
      musicVolume: 0.33,
      platformTheme: 'dark',
    }),
  );

  render(<SettingsHarness authenticatedUserId={null} />);

  expect(screen.getByTestId('music-volume').textContent).toBe('0.33');
  expect(storageMocks.getSettings).not.toHaveBeenCalled();

  act(() => {
    screen.getByRole('button', { name: '设置音量' }).click();
  });

  expect(storageMocks.putSettings).not.toHaveBeenCalled();
  expect(readSavedSettings().musicVolume).toBeCloseTo(0.6);
});

test('authenticated settings hydrate from remote settings and sync later changes back to the server', async () => {
  storageMocks.getSettings.mockResolvedValue({
    musicVolume: 0.8,
    platformTheme: 'dark',
  });

  render(<SettingsHarness authenticatedUserId="user-1" />);

  await waitFor(() => {
    expect(storageMocks.getSettings).toHaveBeenCalledTimes(1);
  });
  expect(screen.getByTestId('music-volume').textContent).toBe('0.80');

  vi.useFakeTimers();

  act(() => {
    screen.getByRole('button', { name: '设置音量' }).click();
  });

  await act(async () => {
    vi.advanceTimersByTime(200);
    await Promise.resolve();
  });

  expect(storageMocks.putSettings).toHaveBeenCalledTimes(1);
  expect(storageMocks.putSettings).toHaveBeenCalledWith(
    { musicVolume: 0.6, platformTheme: 'dark' },
    expect.objectContaining({
      signal: expect.any(AbortSignal),
    }),
  );
  expect(readSavedSettings().musicVolume).toBeCloseTo(0.6);
});

test('unauthenticated runtime skips remote snapshot hydration', async () => {
  render(<PersistenceHarness authenticatedUserId={null} />);

  await waitFor(() => {
    expect(screen.getByTestId('hydrating').textContent).toBe('no');
  });

  expect(screen.getByTestId('saved-game').textContent).toBe('no');
  expect(storageMocks.getSaveSnapshot).not.toHaveBeenCalled();
});
