// REQ-USR-001 / REQ-USR-002: UserDetailPage 页面集成 + 路由接线
// create/edit 贯通 + 提交回流 + 错误就近 + 取数失败（BR3/BR12/BR16/BR17/D4/D5）
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Routes, Route, useLocation } from 'react-router-dom';
import { renderShell } from './renderShell';

const messageSpy = { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn() };
vi.mock('antd', async () => {
  const actual = await vi.importActual<typeof import('antd')>('antd');
  return {
    ...actual,
    App: Object.assign(actual.App, { useApp: () => ({ message: messageSpy }) }),
  };
});

vi.mock('../../src/api/usrApi', () => ({
  createUser: vi.fn(),
  updateUser: vi.fn(),
  getUserDetail: vi.fn(),
  listEmployees: vi.fn(),
  listPermissions: vi.fn(),
}));

import {
  createUser,
  updateUser,
  getUserDetail,
  listEmployees,
  listPermissions,
} from '../../src/api/usrApi';
import UserDetailPage from '../../src/pages/usr/UserDetail';
import { ApiError } from '../../src/api/request';
import { ERR_USERNAME_EXISTS } from '../../src/pages/usr/UserDetail/constants';
import type { UserVO, EmployeeOption, PermissionItem } from '../../src/api/types';

const mockedCreate = createUser as unknown as ReturnType<typeof vi.fn>;
const mockedUpdate = updateUser as unknown as ReturnType<typeof vi.fn>;
const mockedDetail = getUserDetail as unknown as ReturnType<typeof vi.fn>;
const mockedEmployees = listEmployees as unknown as ReturnType<typeof vi.fn>;
const mockedPermissions = listPermissions as unknown as ReturnType<typeof vi.fn>;

const EMPLOYEES: EmployeeOption[] = [{ value: 3, label: '张三', sEmployeeNo: 'zs' }];
const PERMISSIONS: PermissionItem[] = [
  { id: 1, name: '默认显示', category: '基础' },
  { id: 2, name: '高级查看', category: '基础' },
];

function makeVo(over: Partial<UserVO> = {}): UserVO {
  return {
    id: 7,
    sUserName: 'zhangsan',
    employeeName: '张三',
    sUserNo: 'zs',
    departmentName: null,
    sUserType: '超级管理员',
    sLanguage: '英文',
    iIsVoid: 0,
    tLastLoginDate: null,
    sCreator: 'admin',
    tCreateDate: '2026-01-01T00:00:00',
    ...over,
  };
}

function LocationProbe() {
  const loc = useLocation();
  return <div data-testid="loc">{loc.pathname}</div>;
}

// FE-04: edit 单据预填来自 FE-03 经 navigate state 透传的列表行（presetUser）。
// 测试入口可携带 state.user 复刻该数据流（路由 :id 仅为主键，无 by-id 读端点，
// 不能按主键查「用户号」列——详见 useUserDetail / docs/05 REQ-USR-002/003）。
function renderPage(entry: string, presetUser?: UserVO) {
  const initialEntry = presetUser
    ? { pathname: entry, state: { user: presetUser } }
    : entry;
  return renderShell(
    <>
      <LocationProbe />
      <Routes>
        <Route path="/usr/users" element={<div data-testid="list-sentinel">list</div>} />
        <Route path="/usr/users/new" element={<UserDetailPage />} />
        <Route path="/usr/users/:id" element={<UserDetailPage />} />
      </Routes>
    </>,
    {
      initialEntries: [initialEntry],
      preloadedAuth: {
        token: 't',
        user: { id: 1, sUserName: '朱子纯', sUserType: '超级管理员', sLanguage: '中文' },
      },
    },
  );
}

async function fillValidCreateForm(user: ReturnType<typeof userEvent.setup>) {
  await user.type(screen.getByTestId('field-username'), 'zhangsan');
  await user.type(screen.getByTestId('field-userno'), 'zs');
  // 语言必填
  await user.click(screen.getByTestId('select-language').querySelector('.ant-select-selector')!);
  await user.click(await screen.findByText('中文'));
}

describe('UserDetailPage 集成', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockedEmployees.mockResolvedValue(EMPLOYEES);
    mockedPermissions.mockResolvedValue(PERMISSIONS);
  });

  it('create mode renders empty form with defaults', async () => {
    renderPage('/usr/users/new');
    await waitFor(() => expect(mockedEmployees).toHaveBeenCalled());
    expect(await screen.findByText('保存后自动生成')).toBeInTheDocument();
    expect(within(screen.getByTestId('select-usertype')).getByText('普通用户')).toBeInTheDocument();
  });

  it('create submit success navigates to /usr/users with success', async () => {
    const user = userEvent.setup();
    mockedCreate.mockResolvedValue({ id: 9 });
    renderPage('/usr/users/new');
    await waitFor(() => expect(mockedEmployees).toHaveBeenCalled());
    await fillValidCreateForm(user);
    await user.click(screen.getByTestId('perm-check-1'));
    await user.click(screen.getByTestId('btn-save'));
    await waitFor(() => expect(mockedCreate).toHaveBeenCalled());
    const body = mockedCreate.mock.calls[0][0];
    expect(body.sUserName).toBe('zhangsan');
    expect(body.permissionIds).toContain(1);
    expect(messageSpy.success).toHaveBeenCalledWith('用户创建成功');
    await waitFor(() => expect(screen.getByTestId('loc').textContent).toBe('/usr/users'));
  });

  it('create username format invalid blocks submit', async () => {
    const user = userEvent.setup();
    renderPage('/usr/users/new');
    await waitFor(() => expect(mockedEmployees).toHaveBeenCalled());
    await user.type(screen.getByTestId('field-username'), 'ab');
    await user.click(screen.getByTestId('btn-save'));
    expect(await screen.findByText('用户名须为 3-20 位字母数字下划线')).toBeInTheDocument();
    expect(mockedCreate).not.toHaveBeenCalled();
  });

  it('create 40901 highlights username field', async () => {
    const user = userEvent.setup();
    mockedCreate.mockRejectedValue(new ApiError(ERR_USERNAME_EXISTS, 'dup'));
    renderPage('/usr/users/new');
    await waitFor(() => expect(mockedEmployees).toHaveBeenCalled());
    await fillValidCreateForm(user);
    await user.click(screen.getByTestId('btn-save'));
    await waitFor(() => expect(mockedCreate).toHaveBeenCalled());
    expect(await screen.findByText('用户名已存在，请更换')).toBeInTheDocument();
  });

  it('edit mode prefills from navigate state (presetUser) and username disabled', async () => {
    // FE-04: edit 预填走 FE-03 经 navigate state 透传的列表行，不再按主键查列表端点
    renderPage('/usr/users/7', makeVo());
    await waitFor(() =>
      expect((screen.getByTestId('field-username') as HTMLInputElement).value).toBe('zhangsan'),
    );
    expect(mockedDetail).not.toHaveBeenCalled();
    expect(screen.getByTestId('field-username')).toBeDisabled();
  });

  it('edit submit success navigates to /usr/users with 保存成功', async () => {
    const user = userEvent.setup();
    mockedUpdate.mockResolvedValue({ id: 7 });
    renderPage('/usr/users/7', makeVo());
    await waitFor(() =>
      expect((screen.getByTestId('field-username') as HTMLInputElement).value).toBe('zhangsan'),
    );
    await user.click(screen.getByTestId('btn-save'));
    await waitFor(() => expect(mockedUpdate).toHaveBeenCalled());
    expect(mockedUpdate.mock.calls[0][0]).toBe(7);
    expect(messageSpy.success).toHaveBeenCalledWith('保存成功');
    await waitFor(() => expect(screen.getByTestId('loc').textContent).toBe('/usr/users'));
  });

  it('cancel with dirty form confirms then navigates', async () => {
    const user = userEvent.setup();
    renderPage('/usr/users/new');
    await waitFor(() => expect(mockedEmployees).toHaveBeenCalled());
    await user.type(screen.getByTestId('field-username'), 'dirtyuser');
    await user.click(screen.getByTestId('btn-cancel'));
    // AntD Modal.confirm 弹确认
    expect((await screen.findAllByText('放弃未保存的修改？')).length).toBeGreaterThan(0);
    await user.click(screen.getByRole('button', { name: /确\s*定|OK/ }));
    await waitFor(() => expect(screen.getByTestId('loc').textContent).toBe('/usr/users'));
  });

  it('新增 navigates to /usr/users/new', async () => {
    const user = userEvent.setup();
    // edit 经 navigate state 预填后，工具栏「新增」跳 /usr/users/new（BR14）
    renderPage('/usr/users/7', makeVo());
    await waitFor(() =>
      expect((screen.getByTestId('field-username') as HTMLInputElement).value).toBe('zhangsan'),
    );
    await user.click(screen.getByTestId('btn-new'));
    await waitFor(() => expect(screen.getByTestId('loc').textContent).toBe('/usr/users/new'));
  });

  it('loadError shows retry; retry calls reload', async () => {
    mockedPermissions.mockRejectedValueOnce(new ApiError(-1, 'net'));
    renderPage('/usr/users/new');
    expect(await screen.findByTestId('userdetail-loaderror')).toBeInTheDocument();
    mockedPermissions.mockResolvedValue(PERMISSIONS);
    const user = userEvent.setup();
    await user.click(within(screen.getByTestId('userdetail-loaderror')).getByText('点击重试'));
    await waitFor(() => expect(screen.queryByTestId('userdetail-loaderror')).toBeNull());
  });

  it('edit without navigate state shows loadError offering 点击重试 + 返回列表', async () => {
    // FE-04 B1 fix: edit 缺 presetUser（直接访问 URL / 刷新丢 state）→ loadError，
    // 整页给「点击重试」与「返回列表」两个入口（spec § 4 loadError）。
    renderPage('/usr/users/7');
    const loadError = await screen.findByTestId('userdetail-loaderror');
    expect(within(loadError).getByText('点击重试')).toBeInTheDocument();
    expect(within(loadError).getByText('返回列表')).toBeInTheDocument();
    expect(mockedDetail).not.toHaveBeenCalled();
    const user = userEvent.setup();
    await user.click(within(loadError).getByText('返回列表'));
    await waitFor(() => expect(screen.getByTestId('loc').textContent).toBe('/usr/users'));
  });
});
