/* @vitest-environment jsdom */

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

import type { AuthUser } from '../../services/authService';
import { AuthGate } from './AuthGate';
import { useAuthUi } from './AuthUiContext';

const authMocks = vi.hoisted(() => ({
  ensureStoredAccessToken: vi.fn(),
  ensureAutoAuthUser: vi.fn(),
  getAuthLoginOptions: vi.fn(),
  getCurrentAuthUser: vi.fn(),
  loginWithPhoneCode: vi.fn(),
  sendPhoneLoginCode: vi.fn(),
  startWechatLogin: vi.fn(),
  consumeAuthCallbackResult: vi.fn(),
}));

vi.mock('../../services/apiClient', () => ({
  AUTH_STATE_EVENT: 'genarrative-auth-state-changed',
  ensureStoredAccessToken: authMocks.ensureStoredAccessToken,
}));

vi.mock('../../services/authService', () => ({
  bindWechatPhone: vi.fn(),
  changePhoneNumber: vi.fn(),
  consumeAuthCallbackResult: authMocks.consumeAuthCallbackResult,
  ensureAutoAuthUser: authMocks.ensureAutoAuthUser,
  getAuthAuditLogs: vi.fn(),
  getAuthLoginOptions: authMocks.getAuthLoginOptions,
  getAuthRiskBlocks: vi.fn(),
  getCurrentAuthUser: authMocks.getCurrentAuthUser,
  getAuthSessions: vi.fn(),
  getCaptchaChallengeFromError: vi.fn(() => null),
  liftAuthRiskBlock: vi.fn(),
  loginWithPhoneCode: authMocks.loginWithPhoneCode,
  logoutAllAuthSessions: vi.fn(),
  logoutAuthUser: vi.fn(),
  revokeAuthSession: vi.fn(),
  sendPhoneLoginCode: authMocks.sendPhoneLoginCode,
  startWechatLogin: authMocks.startWechatLogin,
}));

vi.mock('../../hooks/useGameSettings', () => ({
  useGameSettings: () => ({
    musicVolume: 0.42,
    setMusicVolume: () => {},
    platformTheme: 'light',
    setPlatformTheme: () => {},
    hasHydratedSettings: true,
    isHydratingSettings: false,
    isPersistingSettings: false,
    settingsError: null,
  }),
}));

vi.mock('./AccountModal', () => ({
  AccountModal: () => null,
}));

vi.mock('./BindPhoneScreen', () => ({
  BindPhoneScreen: () => <div>绑定手机号</div>,
}));

const mockUser: AuthUser = {
  id: 'user-1',
  username: 'tester',
  displayName: '测试玩家',
  publicUserCode: 'user-tester',
  phoneNumberMasked: '138****8000',
  loginMethod: 'phone',
  bindingStatus: 'active',
  wechatBound: false,
};

beforeEach(() => {
  vi.clearAllMocks();
  authMocks.consumeAuthCallbackResult.mockReturnValue(null);
  authMocks.ensureStoredAccessToken.mockResolvedValue('jwt-existing-token');
  authMocks.getCurrentAuthUser.mockResolvedValue({
    user: null,
    availableLoginMethods: ['phone'],
  });
  authMocks.loginWithPhoneCode.mockResolvedValue(mockUser);
  authMocks.sendPhoneLoginCode.mockResolvedValue({
    cooldownSeconds: 60,
    expiresInSeconds: 300,
  });
  authMocks.startWechatLogin.mockResolvedValue(undefined);
  authMocks.ensureAutoAuthUser.mockResolvedValue({
    user: mockUser,
    credentials: {
      username: 'guest_tester',
      password: 'auto_password',
    },
  });
});

function ProtectedActionButton({ onAuthenticated }: { onAuthenticated: () => void }) {
  const authUi = useAuthUi();

  return (
    <button
      type="button"
      onClick={() => {
        authUi?.requireAuth(onAuthenticated);
      }}
    >
      进入作品
    </button>
  );
}

function PlatformTabStateProbe() {
  const [tab, setTab] = useState<'home' | 'create'>('home');

  return (
    <div>
      <div>当前Tab：{tab === 'home' ? '首页' : '创作'}</div>
      <button type="button" onClick={() => setTab('create')}>
        创作
      </button>
    </div>
  );
}

test('auth gate keeps platform content visible when phone login is available', async () => {
  authMocks.getAuthLoginOptions.mockResolvedValue({
    availableLoginMethods: ['phone'],
  });

  render(
    <AuthGate>
      <div>应用内容</div>
    </AuthGate>,
  );

  expect(await screen.findByText('应用内容')).toBeTruthy();
  expect(screen.queryByRole('button', { name: '登录' })).toBeNull();
  expect(screen.queryByText('先登录账号，再同步你的冒险进度。')).toBeNull();
  expect(authMocks.ensureAutoAuthUser).not.toHaveBeenCalled();
});

test('auth gate waits for access token refresh before exposing restored user content', async () => {
  let resolveToken!: (token: string) => void;
  const tokenPromise = new Promise<string>((resolve) => {
    resolveToken = resolve;
  });
  authMocks.ensureStoredAccessToken.mockReturnValue(tokenPromise);
  authMocks.getCurrentAuthUser.mockResolvedValue({
    user: mockUser,
    availableLoginMethods: ['phone'],
  });

  render(
    <AuthGate>
      <div>应用内容</div>
    </AuthGate>,
  );

  expect(screen.getByText('正在校验登录状态...')).toBeTruthy();
  expect(authMocks.getCurrentAuthUser).not.toHaveBeenCalled();

  resolveToken('jwt-restored-token');

  expect(await screen.findByText('应用内容')).toBeTruthy();
  expect(authMocks.ensureStoredAccessToken).toHaveBeenCalledTimes(1);
  expect(authMocks.getCurrentAuthUser).toHaveBeenCalledTimes(1);
});

test('auth gate does not auto-create a guest account when dev guest switch is not explicitly enabled', async () => {
  authMocks.getAuthLoginOptions.mockResolvedValue({
    availableLoginMethods: [],
  });

  render(
    <AuthGate>
      <div>应用内容</div>
    </AuthGate>,
  );

  expect(await screen.findByText('应用内容')).toBeTruthy();
  expect(authMocks.ensureAutoAuthUser).not.toHaveBeenCalled();
});

test('auth gate opens a login modal for protected actions and resumes after login', async () => {
  const user = userEvent.setup();
  const onAuthenticated = vi.fn();

  authMocks.getAuthLoginOptions.mockResolvedValue({
    availableLoginMethods: ['phone'],
  });

  render(
    <AuthGate>
      <ProtectedActionButton onAuthenticated={onAuthenticated} />
    </AuthGate>,
  );

  await user.click(await screen.findByRole('button', { name: '进入作品' }));

  const dialog = screen.getByRole('dialog', { name: '登录账号' });
  expect(dialog).toBeTruthy();
  expect(screen.queryByText('先登录账号，再同步你的冒险进度。')).toBeNull();

  await user.type(within(dialog).getByLabelText('手机号'), '13800000000');
  await user.type(within(dialog).getByLabelText('验证码'), '123456');
  await user.click(within(dialog).getByRole('button', { name: '登录' }));

  await waitFor(() => {
    expect(authMocks.loginWithPhoneCode).toHaveBeenCalledWith(
      '13800000000',
      '123456',
    );
    expect(authMocks.getCurrentAuthUser).toHaveBeenCalledTimes(1);
    expect(onAuthenticated).toHaveBeenCalledTimes(1);
  });

  expect(screen.queryByRole('dialog', { name: '登录账号' })).toBeNull();
});

test('auth state refresh keeps mounted platform content and local tab state', async () => {
  const user = userEvent.setup();
  authMocks.getCurrentAuthUser.mockResolvedValue({
    user: mockUser,
    availableLoginMethods: ['phone'],
  });

  render(
    <AuthGate>
      <PlatformTabStateProbe />
    </AuthGate>,
  );

  expect(await screen.findByText('当前Tab：首页')).toBeTruthy();

  await user.click(screen.getByRole('button', { name: '创作' }));
  expect(screen.getByText('当前Tab：创作')).toBeTruthy();

  let resolveToken!: (token: string) => void;
  const tokenPromise = new Promise<string>((resolve) => {
    resolveToken = resolve;
  });
  authMocks.ensureStoredAccessToken.mockReturnValueOnce(tokenPromise);

  act(() => {
    window.dispatchEvent(new Event('genarrative-auth-state-changed'));
  });

  expect(screen.queryByText('正在校验登录状态...')).toBeNull();
  expect(screen.getByText('当前Tab：创作')).toBeTruthy();

  await act(async () => {
    resolveToken('jwt-refreshed-token');
    await tokenPromise;
  });

  await waitFor(() => {
    expect(authMocks.getCurrentAuthUser).toHaveBeenCalledTimes(2);
  });
  expect(screen.getByText('当前Tab：创作')).toBeTruthy();
});

test('auth gate shows sms send feedback in the login modal', async () => {
  const user = userEvent.setup();

  authMocks.getAuthLoginOptions.mockResolvedValue({
    availableLoginMethods: ['phone'],
  });

  render(
    <AuthGate>
      <ProtectedActionButton onAuthenticated={vi.fn()} />
    </AuthGate>,
  );

  await user.click(await screen.findByRole('button', { name: '进入作品' }));

  const dialog = screen.getByRole('dialog', { name: '登录账号' });
  await user.type(within(dialog).getByLabelText('手机号'), '13800000000');
  await user.click(within(dialog).getByRole('button', { name: '获取验证码' }));

  await waitFor(() => {
    expect(authMocks.sendPhoneLoginCode).toHaveBeenCalledWith(
      '13800000000',
      'login',
      {
        challengeId: undefined,
        answer: '',
      },
    );
  });

  expect(
    within(dialog).getByText('短信请求已提交，请留意手机短信。验证码有效期约 5 分钟。'),
  ).toBeTruthy();
  expect(within(dialog).getByRole('button', { name: '60s' })).toBeTruthy();
});
