import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { afterEach, expect, test, vi } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import type { AccessVehicleRow, Page } from '../../api/types';
import { ROUTER_FUTURE } from '../routing/routerConfig';
import AccessPage from './AccessPage';

const mocks = vi.hoisted(() => ({
  accessSummary: vi.fn(), accessVehicles: vi.fn(), accessUnresolvedIdentities: vi.fn(),
  accessThresholds: vi.fn(), updateAccessThresholds: vi.fn()
}));
const auth = vi.hoisted(() => ({ role: 'admin' }));
const layout = vi.hoisted(() => ({ mobile: false }));
vi.mock('../../api/client', () => ({ api: mocks }));
vi.mock('../auth/AuthGate', () => ({ usePlatformSession: () => ({ session: { role: auth.role } }) }));
vi.mock('../hooks/useMobileLayout', () => ({ useMobileLayout: () => layout.mobile }));

afterEach(() => {
  cleanup();
  auth.role = 'admin';
  layout.mobile = false;
  Object.values(mocks).forEach((mock) => mock.mockReset());
});

function accessRow(vin: string, plate: string): AccessVehicleRow {
  return {
    vin, plate, oem: '测试品牌', model: '测试车型', company: '测试公司', protocol: 'JT808', provider: '测试厂家', source: 'production',
    firstSeenAt: '2026-07-16T00:00:00Z', latestEventAt: '2026-07-16T04:00:00Z', latestReceivedAt: '2026-07-16T04:00:01Z', reportIntervalSec: 10,
    dataDelaySec: 1, freshnessSec: 3, onlineState: 'online', thresholdSec: 300, latestMessageType: 'location', latestEventId: `${vin}-event`, latestError: '', delayAbnormal: false,
    firstSeenEvidence: 'test first seen', firstSeenSource: 'test', reportIntervalEvidence: 'test interval', reportSampleCount: 10,
    expectedProtocols: [], actualProtocols: ['JT808'], missingProtocols: [], masterDataIssues: [], connectionState: 'healthy', expectationEvidence: '尚未接业务口径',
    protocolStatuses: [{ protocol: 'JT808', expected: false, connected: true, provider: '测试厂家', firstSeenAt: '2026-07-16T00:00:00Z', latestEventAt: '2026-07-16T04:00:00Z', latestReceivedAt: '2026-07-16T04:00:01Z', reportIntervalSec: 10, dataDelaySec: 1, freshnessSec: 3, onlineState: 'online', thresholdSec: 300, delayAbnormal: false, firstSeenEvidence: 'test first seen', reportIntervalEvidence: 'test interval' }]
  };
}

function prepareBaseData() {
  mocks.accessSummary.mockResolvedValue({ totalVehicles: 2, onlineVehicles: 2, offlineVehicles: 0, longOfflineVehicles: 0, neverReported: 0, unknownVehicles: 0, delayAbnormal: 0, reportedToday: 2, onlineRate: 100, healthyVehicles: 2, incompleteVehicles: 0, degradedVehicles: 0, masterDataIncompleteVehicles: 0, protocols: [], oems: [{ name: '测试品牌', total: 2, online: 2, onlineRate: 100 }], asOf: '2026-07-16T04:00:00Z', thresholdVersion: 1 });
  mocks.accessUnresolvedIdentities.mockResolvedValue({ items: [], total: 0, limit: 20, offset: 0 });
  mocks.accessThresholds.mockResolvedValue({ version: 1, defaultThresholdSec: 300, delayThresholdSec: 60, longOfflineSec: 86_400, protocols: [], updatedBy: 'test', updatedAt: '2026-07-16T04:00:00Z', audit: [] });
}

test('removes old access rows immediately when the vehicle filter scope changes', async () => {
  prepareBaseData();
  let resolveNew!: (value: Page<AccessVehicleRow>) => void;
  mocks.accessVehicles.mockImplementation((query: { keyword?: string }) => query.keyword === 'OLDVIN'
    ? Promise.resolve({ items: [accessRow('OLDVIN', '旧接入车牌')], total: 1, limit: 50, offset: 0 })
    : new Promise<Page<AccessVehicleRow>>((resolve) => { resolveNew = resolve; }));
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/access?keyword=OLDVIN']}><AccessPage /></MemoryRouter></QueryClientProvider>);

  expect(await screen.findByText('旧接入车牌')).toBeInTheDocument();
  expect(screen.getByRole('heading', { name: '真实来源与接入健康', level: 5 })).toBeInTheDocument();
  expect(screen.getByLabelText('接入管理操作')).toHaveClass('v2-workspace-command-bar', 'v2-access-command-bar');
  expect(view.container.querySelector('.v2-access-discovery-shell')).toBeInTheDocument();
  for (const className of ['v2-access-filter-card-v3', 'v2-access-table-v3']) {
    expect(view.container.querySelector(`.${className}.semi-card`)).toBeInTheDocument();
  }
  expect(screen.getByRole('navigation', { name: '接入差异筛选' })).toHaveClass('v2-access-status-tabs');
  const protocolCoverage = screen.getByLabelText('真实协议来源概览');
  expect(protocolCoverage).toHaveTextContent('真实来源覆盖');
  expect(protocolCoverage).toHaveTextContent('32960');
  expect(protocolCoverage).toHaveTextContent('808');
  expect(protocolCoverage).toHaveTextContent('宇通');
  expect(screen.getByRole('columnheader', { name: '差异摘要' })).toBeInTheDocument();
  expect(screen.queryByRole('columnheader', { name: '真实来源' })).not.toBeInTheDocument();
  expect(screen.getByText('已接来源状态正常')).toBeInTheDocument();
  const filterActions = view.container.querySelector<HTMLElement>('.v2-access-filter-actions');
  expect(filterActions).toBeInTheDocument();
  expect(within(filterActions!).getByRole('button', { name: '查询' })).toBeInTheDocument();
  expect(within(filterActions!).getByRole('button', { name: '重置' })).toBeInTheDocument();
  expect(view.container.querySelector('.v2-access-semi-table.semi-table-wrapper')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-access-table-scroll-v3 > table')).not.toBeInTheDocument();
  const desktopAccessScroll = view.container.querySelector<HTMLElement>('.v2-access-table-scroll-v3');
  expect(desktopAccessScroll).toHaveAttribute('tabindex', '0');
  expect(desktopAccessScroll).toHaveAttribute('aria-label', '车辆接入表格，可使用左右方向键浏览协议来源');
  const desktopTableScroller = desktopAccessScroll?.querySelector<HTMLElement>('.semi-table-body');
  expect(desktopTableScroller).toBeInTheDocument();
  Object.defineProperty(desktopAccessScroll!, 'clientWidth', { configurable: true, value: 760 });
  Object.defineProperty(desktopAccessScroll!, 'scrollWidth', { configurable: true, value: 760 });
  const scrollBy = vi.fn();
  Object.defineProperty(desktopTableScroller!, 'scrollBy', { configurable: true, value: scrollBy });
  fireEvent.keyDown(desktopAccessScroll!, { key: 'ArrowRight' });
  expect(scrollBy).toHaveBeenCalledWith({ left: 260, behavior: 'smooth' });
  fireEvent.keyDown(desktopAccessScroll!, { key: 'ArrowLeft' });
  expect(scrollBy).toHaveBeenCalledWith({ left: -260, behavior: 'smooth' });
  expect(screen.getByRole('columnheader', { name: '车辆' })).toHaveClass('semi-table-cell-fixed-left', 'v2-access-vehicle-column');
  expect(screen.getByRole('columnheader', { name: '品牌 / 车型' })).toHaveClass('semi-table-cell-fixed-left', 'v2-access-model-column');
  const desktopRow = screen.getByTestId('access-row-OLDVIN');
  expect(desktopRow).toHaveAttribute('role', 'button');
  expect(desktopRow).toHaveAttribute('aria-expanded', 'false');
  fireEvent.keyDown(desktopRow, { key: 'Enter' });
  expect(await screen.findByRole('button', { name: '关闭车辆接入详情' })).toBeInTheDocument();
  const detailDialog = screen.getByRole('dialog', { name: '车辆接入详情' });
  expect(detailDialog).toBeInTheDocument();
  expect(desktopRow).toHaveAttribute('aria-expanded', 'true');
  expect(within(detailDialog).getByText('旧接入车牌 · OLDVIN')).toBeInTheDocument();
  expect(within(detailDialog).getByText('接入结论')).toBeInTheDocument();
  expect(within(detailDialog).getByText('已接来源状态正常')).toBeInTheDocument();
  expect(within(detailDialog).getByText('处置建议 · 无需处理，持续监测来源上报')).toBeInTheDocument();
  const healthOverview = within(detailDialog).getByRole('group', { name: '接入健康概览' });
  expect(healthOverview).toHaveTextContent('已接入1 / 3');
  expect(healthOverview).toHaveTextContent('当前在线1');
  expect(healthOverview).toHaveTextContent('异常来源0');
  expect(healthOverview).toHaveTextContent('资料状态已维护');
  expect(detailDialog.querySelector('.v2-access-inspector-header')).not.toBeInTheDocument();
  fireEvent.change(screen.getByRole('textbox', { name: '车辆' }), { target: { value: 'NEWVIN' } });
  fireEvent.click(screen.getByRole('button', { name: '查询' }));

  expect(await screen.findByText('正在更新车辆接入状态…')).toBeInTheDocument();
  expect(screen.queryByText('旧接入车牌')).not.toBeInTheDocument();

  await act(async () => resolveNew({ items: [accessRow('NEWVIN', '新接入车牌')], total: 1, limit: 50, offset: 0 }));
  expect(await screen.findByText('新接入车牌')).toBeInTheDocument();
  await waitFor(() => expect(screen.queryByText('正在更新车辆接入状态…')).not.toBeInTheDocument());
});

test('reports and independently retries unresolved identity and threshold failures', async () => {
  prepareBaseData();
  mocks.accessVehicles.mockResolvedValue({ items: [accessRow('VIN001', '粤A00001')], total: 1, limit: 50, offset: 0 });
  mocks.accessUnresolvedIdentities.mockRejectedValueOnce(new Error('待绑定身份服务超时')).mockResolvedValueOnce({ items: [{ id: 'identity-1', identifierMasked: '138****0001', protocol: 'JT808', plate: '粤A待核', latestSeenAt: '2026-07-16T04:00:00Z', recommendedAction: '核对 VIN' }], total: 1, limit: 20, offset: 0 });
  mocks.accessThresholds.mockRejectedValueOnce(new Error('阈值配置读取失败')).mockResolvedValueOnce({ version: 1, defaultThresholdSec: 300, delayThresholdSec: 60, longOfflineSec: 86_400, protocols: [], updatedBy: 'test', updatedAt: '2026-07-16T04:00:00Z', audit: [] });
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/access']}><AccessPage /></MemoryRouter></QueryClientProvider>);

  fireEvent.click(await screen.findByRole('button', { name: /接入治理/ }));
  expect(await screen.findByRole('dialog', { name: '接入治理配置' })).toBeInTheDocument();
  const identityError = await screen.findByText('待绑定身份服务超时');
  const thresholdError = await screen.findByText('阈值配置读取失败');
  fireEvent.click(within(identityError.closest('[role="alert"]')!).getByRole('button', { name: /重试/ }));
  fireEvent.click(within(thresholdError.closest('[role="alert"]')!).getByRole('button', { name: /重试/ }));

  const identityTitle = await screen.findByText('来源身份待绑定');
  const identityCollapse = identityTitle.closest<HTMLElement>('.semi-collapse');
  expect(identityCollapse).toHaveClass('v2-access-identity-queue-v3');
  expect(identityTitle.closest('.semi-collapse-header')).toHaveAttribute('aria-expanded', 'true');
  expect(within(identityCollapse!).getByText('1 条').closest('.semi-tag')).toBeTruthy();
  expect(within(identityCollapse!).getByText('138****0001').closest('.semi-card')).toHaveClass('v2-access-identity-card');
  expect(within(identityCollapse!).getByText('来源标识')).toBeInTheDocument();
  expect(within(identityCollapse!).getByText('关联车牌')).toBeInTheDocument();
  expect(within(identityCollapse!).getByText('最后出现')).toBeInTheDocument();
  expect(within(identityCollapse!).getByText('建议动作')).toBeInTheDocument();

  const thresholdTitle = await screen.findByText('在线判定阈值');
  const thresholdCollapse = thresholdTitle.closest<HTMLElement>('.semi-collapse');
  expect(thresholdCollapse).toHaveClass('v2-access-settings');
  expect(within(thresholdCollapse!).getByText('v1').closest('.semi-tag')).toBeTruthy();
  expect(thresholdTitle.closest('.semi-collapse-header')).toHaveAttribute('aria-expanded', 'false');
  expect(within(thresholdCollapse!).queryByRole('spinbutton')).not.toBeInTheDocument();
  fireEvent.click(thresholdTitle.closest('.semi-collapse-header')!);
  expect(thresholdTitle.closest('.semi-collapse-header')).toHaveAttribute('aria-expanded', 'true');
  expect(within(thresholdCollapse!).getAllByRole('spinbutton')).toHaveLength(6);
  expect(within(thresholdCollapse!).getByText('通用判定')).toBeInTheDocument();
  expect(within(thresholdCollapse!).getByText('协议在线阈值')).toBeInTheDocument();
  expect(within(thresholdCollapse!).getAllByText('5 分 0 秒').length).toBeGreaterThan(0);
  expect(within(thresholdCollapse!).getByText('1 分 0 秒')).toBeInTheDocument();
  expect(within(thresholdCollapse!).getByText('24 小时')).toBeInTheDocument();
  expect(screen.queryByText('待绑定身份服务超时')).not.toBeInTheDocument();
  expect(screen.queryByText('阈值配置读取失败')).not.toBeInTheDocument();
  expect(mocks.accessUnresolvedIdentities).toHaveBeenCalledTimes(2);
  expect(mocks.accessThresholds).toHaveBeenCalledTimes(2);
});

test('does not request or render admin-only thresholds for a read-only session', async () => {
  auth.role = 'viewer';
  prepareBaseData();
  mocks.accessVehicles.mockResolvedValue({ items: [accessRow('VIN001', '粤A00001')], total: 1, limit: 50, offset: 0 });
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/access']}><AccessPage /></MemoryRouter></QueryClientProvider>);

  expect(await screen.findByText('粤A00001')).toBeInTheDocument();
  expect(mocks.accessUnresolvedIdentities).not.toHaveBeenCalled();
  expect(mocks.accessThresholds).not.toHaveBeenCalled();
  expect(screen.queryByText(/在线判定阈值/)).not.toBeInTheDocument();

  fireEvent.click(screen.getByRole('button', { name: /刷新/ }));
  await waitFor(() => expect(mocks.accessSummary).toHaveBeenCalledTimes(2));
  expect(mocks.accessUnresolvedIdentities).not.toHaveBeenCalled();
  expect(mocks.accessThresholds).not.toHaveBeenCalled();
  expect(screen.queryByRole('alert')).not.toBeInTheDocument();
});

test('renders mobile access vehicles as selectable Semi cards', async () => {
  layout.mobile = true;
  prepareBaseData();
  const delayedRow = accessRow('VIN001', '粤A00001');
  delayedRow.connectionState = 'degraded';
  delayedRow.protocolStatuses[0].delayAbnormal = true;
  delayedRow.protocolStatuses[0].dataDelaySec = 128;
  mocks.accessVehicles.mockResolvedValue({ items: [delayedRow], total: 1, limit: 50, offset: 0 });
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/access']}><AccessPage /></MemoryRouter></QueryClientProvider>);

  const action = await screen.findByRole('button', { name: '查看 粤A00001 接入详情' });
  expect(action).toHaveClass('semi-button', 'v2-access-mobile-action');
  expect(action.closest('.semi-card')).toHaveClass('v2-access-mobile-card');
  expect(view.container.querySelector('.v2-access-table-scroll-v3.is-mobile-scroll')).toHaveAttribute('tabindex', '0');
  expect(view.container.querySelector('.v2-access-table-scroll-v3.is-mobile-scroll')).toHaveAttribute('aria-label', '车辆接入列表，可上下滚动');
  expect(within(action).getByText('32960')).toBeInTheDocument();
  expect(within(action).getByText('808')).toBeInTheDocument();
  expect(within(action).getByText('宇通')).toBeInTheDocument();
  expect(screen.queryByLabelText('每页车辆数')).not.toBeInTheDocument();
  expect(screen.getByRole('button', { name: '打开接入治理' })).toHaveTextContent('治理');
  expect(screen.getByRole('button', { name: '打开接入治理' })).toHaveClass('v2-workspace-mobile-tool-button', 'is-muted');
  const toolsButton = screen.getByRole('button', { name: '打开接入管理工具' });
  expect(toolsButton).toHaveTextContent('工具');
  expect(toolsButton).toHaveClass('v2-workspace-mobile-tool-button', 'is-muted');
  expect(screen.queryByRole('menuitem', { name: /导出当前页/ })).not.toBeInTheDocument();
  fireEvent.click(toolsButton);
  await waitFor(() => expect(toolsButton).toHaveAttribute('aria-expanded', 'true'));
  expect(screen.getByRole('menuitem', { name: /导出当前页/ })).toBeInTheDocument();
  fireEvent.click(screen.getByRole('menuitem', { name: /刷新数据/ }));
  await waitFor(() => expect(mocks.accessSummary).toHaveBeenCalledTimes(2));
  expect(toolsButton).toHaveAttribute('aria-expanded', 'false');
  expect(action).toHaveAttribute('aria-pressed', 'false');
  expect(action).toHaveAttribute('aria-expanded', 'false');
  fireEvent.click(action);
  expect(action).toHaveAttribute('aria-pressed', 'true');
  expect(action).toHaveAttribute('aria-expanded', 'true');
  const dialog = await screen.findByRole('dialog', { name: '车辆接入详情' });
  expect(within(dialog).getByRole('button', { name: '关闭车辆接入详情' })).toBeInTheDocument();
  expect(dialog.querySelector('.v2-access-inspector-v3.semi-card')).toBeInTheDocument();
  expect(dialog.querySelector('.v2-access-inspector-focus.semi-card')).toBeInTheDocument();
  expect(within(dialog).getByRole('group', { name: '接入健康概览' })).toBeInTheDocument();
  expect(dialog.querySelector('.v2-access-mobile-profile')).toHaveTextContent('车辆档案测试品牌 / 测试车型');
  expect(within(dialog).getByText('协议证据')).toBeInTheDocument();
  expect(within(dialog).getByText('1 / 3 已接入').closest('.semi-tag')).toBeInTheDocument();
  expect(dialog.querySelector('.v2-access-protocol-details.semi-card-group')).toBeInTheDocument();
  expect(dialog.querySelectorAll('.v2-access-protocol-detail.semi-card')).toHaveLength(3);
  const evidenceToggle = within(dialog).getByRole('button', { name: '收起 JT808 完整证据' });
  expect(evidenceToggle).toHaveAttribute('aria-expanded', 'true');
  expect(within(dialog).getByRole('region', { name: 'JT808 完整接入证据' })).toBeInTheDocument();
  expect(within(dialog).getByText('test first seen')).toBeInTheDocument();
  expect(within(dialog).getAllByText('当前未发现真实接入来源')).toHaveLength(2);
  const vehicleRequestsBeforeEvidenceToggle = mocks.accessVehicles.mock.calls.length;
  const summaryRequestsBeforeEvidenceToggle = mocks.accessSummary.mock.calls.length;
  fireEvent.click(evidenceToggle);
  expect(within(dialog).getByRole('button', { name: '查看 JT808 完整证据' })).toHaveAttribute('aria-expanded', 'false');
  expect(within(dialog).queryByRole('region', { name: 'JT808 完整接入证据' })).not.toBeInTheDocument();
  fireEvent.click(within(dialog).getByRole('button', { name: '查看 JT808 完整证据' }));
  expect(within(dialog).getByRole('button', { name: '收起 JT808 完整证据' })).toHaveAttribute('aria-expanded', 'true');
  expect(mocks.accessVehicles).toHaveBeenCalledTimes(vehicleRequestsBeforeEvidenceToggle);
  expect(mocks.accessSummary).toHaveBeenCalledTimes(summaryRequestsBeforeEvidenceToggle);
  expect(document.querySelector('.v2-access-detail-sidesheet')).toHaveClass('semi-sidesheet-bottom');
  expect(document.querySelector('.v2-access-detail-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(84dvh, 740px)' });
  fireEvent.click(within(dialog).getByRole('button', { name: '关闭车辆接入详情' }));
  expect(action).toHaveAttribute('aria-pressed', 'false');
  expect(action).toHaveAttribute('aria-expanded', 'false');
  expect(document.body.style.overflow).not.toBe('hidden');

  const governanceButton = screen.getByRole('button', { name: /接入治理/ });
  fireEvent.click(governanceButton);
  const governanceDialog = await screen.findByRole('dialog', { name: '接入治理配置' });
  expect(document.querySelector('.v2-access-governance-sidesheet')).toHaveClass('semi-sidesheet-bottom');
  expect(document.querySelector('.v2-access-governance-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(86dvh, 760px)' });
  fireEvent.click(within(governanceDialog).getByRole('button', { name: '关闭接入治理配置' }));
  expect(governanceButton).toHaveAttribute('aria-expanded', 'false');
  expect(document.body.style.overflow).not.toBe('hidden');
});
