import { App as AntApp, ConfigProvider } from 'antd';
import { act, render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { AppContext, type AppContextValue } from '../../src/frontend/app-context';
import { I18nProvider } from '../../src/frontend/i18n/react';
import { api } from '../../src/frontend/services/api';
import { mailhubTheme } from '../../src/frontend/theme';
import type { AdminUser, AuditLogEntry, InboundMailbox, InboundMessage, RuntimeConfig, SendEvent } from '../../src/frontend/types';
import AdminPage from '../../src/pages/Admin';
import Inbox from '../../src/pages/Inbox';
import SendingLogs from '../../src/pages/SendingLogs';

describe('Operational navigation state', () => {
  afterEach(() => vi.restoreAllMocks());

  it('keeps inbox body tabs in the URL and does not reopen a closed detail drawer on back', async () => {
    const user = userEvent.setup();
    vi.spyOn(api, 'domains').mockResolvedValue({ domains: [] });
    vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [mailbox] });
    vi.spyOn(api, 'inboundFolders').mockResolvedValue({
      folders: [{ name: 'INBOX', specialUse: null, messageCount: 1, unreadCount: 0 }]
    });
    vi.spyOn(api, 'inboundMessages').mockResolvedValue({ messages: [inboundMessage], total: 1, page: 1, pageSize: 25 });
    vi.spyOn(api, 'inboundMessage').mockResolvedValue({ message: inboundMessage });
    const router = createMemoryRouter([
      { path: '/inbox', element: <Inbox /> },
      { path: '/inbox/messages/:messageId', element: <Inbox /> },
      { path: '*', element: <div>other</div> }
    ], {
      initialEntries: ['/overview', '/inbox?mailboxId=1&folder=INBOX'],
      initialIndex: 1
    });
    renderRouter(router);

    await user.click(await screen.findByRole('button', { name: 'Quarterly report · sender@example.test' }));
    await waitFor(() => expect(router.state.location.pathname).toBe('/inbox/messages/9'));
    await user.click(await screen.findByRole('tab', { name: 'HTML 预览' }));
    await waitFor(() => expect(new URLSearchParams(router.state.location.search).get('tab')).toBe('html'));
    await act(async () => {
      await router.navigate(-1);
    });
    expect(router.state.location.pathname).toBe('/inbox/messages/9');
    expect(new URLSearchParams(router.state.location.search).has('tab')).toBe(false);
    await act(async () => {
      await router.navigate(1);
    });
    expect(new URLSearchParams(router.state.location.search).get('tab')).toBe('html');

    await user.click(screen.getByRole('button', { name: 'Close' }));
    await waitFor(() => expect(router.state.location.pathname).toBe('/inbox'));
    expect(new URLSearchParams(router.state.location.search).has('tab')).toBe(false);

    await act(async () => {
      await router.navigate(-1);
    });
    expect(router.state.location.pathname).toBe('/overview');
    expect(router.state.location.pathname).not.toContain('/messages/');
  });

  it('does not reopen a closed activity drawer after switching its detail tab', async () => {
    const user = userEvent.setup();
    vi.spyOn(api, 'events').mockResolvedValue({ events: [sendEvent], total: 1, page: 1, pageSize: 25 });
    vi.spyOn(api, 'domains').mockResolvedValue({ domains: [] });
    vi.spyOn(api, 'event').mockResolvedValue({ event: sendEvent });
    const router = createMemoryRouter([
      { path: '/activity', element: <SendingLogs /> },
      { path: '/activity/:eventId', element: <SendingLogs /> },
      { path: '*', element: <div>other</div> }
    ], {
      initialEntries: ['/overview', '/activity?page=1&pageSize=25'],
      initialIndex: 1
    });
    renderRouter(router);

    await user.click(await screen.findByRole('button', { name: '查看发送详情' }));
    await waitFor(() => expect(router.state.location.pathname).toBe('/activity/11'));
    await user.click(await screen.findByRole('tab', { name: 'Webhook' }));
    await waitFor(() => expect(new URLSearchParams(router.state.location.search).get('tab')).toBe('webhooks'));
    await act(async () => {
      await router.navigate(-1);
    });
    expect(router.state.location.pathname).toBe('/activity/11');
    expect(new URLSearchParams(router.state.location.search).has('tab')).toBe(false);
    await act(async () => {
      await router.navigate(1);
    });
    expect(new URLSearchParams(router.state.location.search).get('tab')).toBe('webhooks');

    await user.click(screen.getByRole('button', { name: 'Close' }));
    await waitFor(() => expect(router.state.location.pathname).toBe('/activity'));
    await act(async () => {
      await router.navigate(-1);
    });
    expect(router.state.location.pathname).toBe('/overview');
  });

  it('keeps other admin rows interactive while a user mutation is pending', async () => {
    const user = userEvent.setup();
    const pending = deferred<{ message: string }>();
    vi.spyOn(api, 'adminUsers').mockResolvedValue({ users: adminUsers });
    vi.spyOn(api, 'resendAdminVerification').mockReturnValue(pending.promise);
    const router = createMemoryRouter([{ path: '/admin/:section', element: <AdminPage /> }], {
      initialEntries: ['/admin/users']
    });
    renderRouter(router);

    const firstCard = (await screen.findByText('first@example.test')).closest('.admin-user-card');
    const secondCard = screen.getByText('second@example.test').closest('.admin-user-card');
    expect(firstCard).not.toBeNull();
    expect(secondCard).not.toBeNull();
    const firstActions = within(firstCard!).getByRole('button', { name: /用户操作.*first/ });
    const secondActions = within(secondCard!).getByRole('button', { name: /用户操作.*second/ });
    await user.click(firstActions);
    await user.click(await screen.findByRole('menuitem', { name: /重发验证/ }));

    await waitFor(() => expect((firstActions as HTMLButtonElement).disabled).toBe(true));
    expect((secondActions as HTMLButtonElement).disabled).toBe(false);

    pending.resolve({ message: 'ok' });
    await waitFor(() => expect((firstActions as HTMLButtonElement).disabled).toBe(false));
  });

  it('labels admin user controls and keeps risky actions in a confirmed overflow menu', async () => {
    const user = userEvent.setup();
    vi.spyOn(api, 'adminUsers').mockResolvedValue({ users: adminUsers });
    const router = createMemoryRouter([{ path: '/admin/:section', element: <AdminPage /> }], {
      initialEntries: ['/admin/users']
    });
    renderRouter(router);

    expect(await screen.findByRole('heading', { name: '管理中心' })).toBeTruthy();
    const firstCard = screen.getByText('first@example.test').closest('.admin-user-card');
    expect(firstCard).not.toBeNull();
    expect(within(firstCard!).getByRole('combobox', { name: /用户状态.*first/ })).toBeTruthy();
    expect(within(firstCard!).getByRole('combobox', { name: /用户角色.*first/ })).toBeTruthy();
    expect(within(firstCard!).queryByRole('button', { name: '重置邮件' })).toBeNull();

    await user.click(within(firstCard!).getByRole('button', { name: /用户操作.*first/ }));
    await user.click(await screen.findByRole('menuitem', { name: /重置邮件/ }));
    const confirmation = await screen.findByRole('dialog');
    expect(confirmation.textContent).toContain('确认给 first@example.test 发送密码重置邮件？');
  });

  it('restores audit filters from URL history', async () => {
    const user = userEvent.setup();
    vi.spyOn(api, 'adminUsers').mockResolvedValue({ users: adminUsers });
    const auditLogs = vi.spyOn(api, 'adminAuditLogs').mockResolvedValue({ logs: [] });
    const router = createMemoryRouter([{ path: '/admin/:section', element: <AdminPage /> }], {
      initialEntries: ['/admin/audit-logs?action=admin.old&actorUserId=1']
    });
    renderRouter(router);

    const actionInput = await screen.findByLabelText('动作');
    await waitFor(() => expect((actionInput as HTMLInputElement).value).toBe('admin.old'));
    expect(auditLogs).toHaveBeenCalledWith('action=admin.old&actorUserId=1');
    await user.clear(actionInput);
    await user.type(actionInput, 'admin.new');
    await user.click(screen.getByRole('button', { name: /查.*询/ }));
    await waitFor(() => expect(new URLSearchParams(router.state.location.search).get('action')).toBe('admin.new'));

    await act(async () => {
      await router.navigate(-1);
    });
    await waitFor(() => expect((screen.getByLabelText('动作') as HTMLInputElement).value).toBe('admin.old'));
    await waitFor(() => expect(auditLogs).toHaveBeenLastCalledWith('action=admin.old&actorUserId=1'));
  });

  it('ignores a late audit response after browser back restores an earlier query', async () => {
    const user = userEvent.setup();
    const delayedNewQuery = deferred<{ logs: AuditLogEntry[] }>();
    const oldLog = auditLog(1, 'admin.old.result');
    const lateLog = auditLog(2, 'admin.new.late-result');
    vi.spyOn(api, 'adminUsers').mockResolvedValue({ users: adminUsers });
    const auditLogs = vi.spyOn(api, 'adminAuditLogs').mockImplementation((query) => (
      query === 'action=admin.new' ? delayedNewQuery.promise : Promise.resolve({ logs: [oldLog] })
    ));
    const router = createMemoryRouter([{ path: '/admin/:section', element: <AdminPage /> }], {
      initialEntries: ['/admin/audit-logs?action=admin.old']
    });
    renderRouter(router);

    const actionInput = await screen.findByLabelText('动作');
    expect(await screen.findByText(oldLog.action)).toBeTruthy();
    await user.clear(actionInput);
    await user.type(actionInput, 'admin.new');
    await user.click(screen.getByRole('button', { name: /查.*询/ }));
    await waitFor(() => expect(auditLogs).toHaveBeenCalledWith('action=admin.new'));

    await act(async () => {
      await router.navigate(-1);
    });
    await waitFor(() => expect((screen.getByLabelText('动作') as HTMLInputElement).value).toBe('admin.old'));
    await waitFor(() => expect(auditLogs).toHaveBeenLastCalledWith('action=admin.old'));
    expect(screen.getByText(oldLog.action)).toBeTruthy();

    await act(async () => {
      delayedNewQuery.resolve({ logs: [lateLog] });
      await delayedNewQuery.promise;
    });
    await waitFor(() => expect(screen.queryByText(lateLog.action)).toBeNull());
    expect(screen.getByText(oldLog.action)).toBeTruthy();
  });
});

function renderRouter(router: ReturnType<typeof createMemoryRouter>) {
  return render(
    <ConfigProvider theme={{ ...mailhubTheme, token: { ...mailhubTheme.token, motion: false } }}>
      <AntApp>
        <I18nProvider>
          <AppContext.Provider value={appContext}>
            <RouterProvider router={router} />
          </AppContext.Provider>
        </I18nProvider>
      </AntApp>
    </ConfigProvider>
  );
}

function deferred<T>() {
  let resolve!: (value: T) => void;
  const promise = new Promise<T>((done) => {
    resolve = done;
  });
  return { promise, resolve };
}

function auditLog(id: number, action: string): AuditLogEntry {
  return {
    id,
    actorUserId: 1,
    action,
    targetType: 'user',
    targetId: '2',
    targetUserId: 2,
    summary: {},
    createdAt: '2026-07-14T00:00:00.000Z'
  };
}

const runtimeConfig: RuntimeConfig = {
  appBaseUrl: 'https://mail.example.test',
  mailHostname: 'mail.example.test',
  sendingIp: '192.0.2.10',
  defaultSpfMechanisms: '',
  dmarcPolicy: 'none',
  dmarcRua: '',
  sendRequiresVerified: true,
  engagementTrackingEnabled: true,
  listUnsubscribeMailto: '',
  listUnsubscribeUrl: '',
  listUnsubscribePostEnabled: false,
  feedbackIdEnabled: false,
  reportAbuseTo: '',
  csaComplaintsTo: '',
  bounceAddress: '',
  bounceEnvelopeEnabled: false
};

const appContext: AppContextValue = {
  user: { id: 1, username: 'admin', email: 'admin@example.test', role: 'admin', status: 'active' },
  config: runtimeConfig,
  refreshBootstrap: vi.fn(async () => undefined),
  logout: vi.fn(async () => undefined)
};

const mailbox: InboundMailbox = {
  id: 1,
  userId: 1,
  domainId: 1,
  domain: 'example.test',
  address: 'inbox@example.test',
  localPart: 'inbox',
  displayName: 'Inbox',
  aliases: [],
  forwardTo: [],
  keepForwarded: true,
  quotaMb: 1024,
  passwordSet: true,
  passwordRecoverable: false,
  status: 'active',
  messageCount: 1,
  unreadCount: 0,
  createdAt: '2026-07-14T00:00:00.000Z',
  updatedAt: '2026-07-14T00:00:00.000Z'
};

const inboundMessage: InboundMessage = {
  id: 9,
  mailboxId: 1,
  userId: 1,
  domainId: 1,
  domain: 'example.test',
  mailboxAddress: 'inbox@example.test',
  folder: 'INBOX',
  sender: 'sender@example.test',
  recipients: ['inbox@example.test'],
  subject: 'Quarterly report',
  messageId: '<message-9@example.test>',
  preview: 'Report preview',
  read: true,
  receivedAt: '2026-07-14T00:00:00.000Z',
  createdAt: '2026-07-14T00:00:00.000Z',
  updatedAt: '2026-07-14T00:00:00.000Z',
  textBody: 'Plain text',
  htmlBody: '<p>HTML</p>',
  rawMessage: 'Raw MIME'
};

const sendEvent: SendEvent = {
  id: 11,
  userId: 1,
  domainId: null,
  smtpRelayId: null,
  domain: 'example.test',
  sender: 'sender@example.test',
  recipients: ['recipient@example.test'],
  subject: 'Delivery test',
  status: 'delivered',
  detail: '250 accepted',
  queueId: 'QUEUE-11',
  messageId: 'mh-11',
  createdAt: '2026-07-14T00:00:00.000Z'
};

const resourceCounts = {
  domains: 0,
  dnsCredentials: 0,
  apiTokens: 0,
  inboundMailboxes: 0,
  inboundMessages: 0,
  sendEvents: 0,
  smtpCredential: 0
};

const adminUsers: AdminUser[] = [
  { id: 1, username: 'first', email: 'first@example.test', role: 'user', status: 'pending_email', resourceCounts },
  { id: 2, username: 'second', email: 'second@example.test', role: 'user', status: 'active', resourceCounts }
];
