import { App as AntApp, ConfigProvider } from 'antd';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import { 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 { ApiToken, InboundMailbox, RuntimeConfig, UserRole } from '../../src/frontend/types';
import ApiTokens from '../../src/pages/ApiTokens';

describe('API token secrets and message access', () => {
  it('keeps a newly created full token copyable after acknowledgement', async () => {
    const user = userEvent.setup();
    const fullToken = 'mh_12345678.full-secret-value';
    const summary = tokenFixture({ token: fullToken, tokenRecoverable: true });

    vi.spyOn(api, 'apiTokens')
      .mockResolvedValueOnce({ tokens: [] })
      .mockResolvedValue({ tokens: [summary] });
    vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [] });
    const createToken = vi.spyOn(api, 'createApiToken').mockResolvedValue({ token: summary });

    renderPage();
    await screen.findByText(/新 API 密钥会加密保存并支持完整复制/);
    await user.click(screen.getAllByRole('button', { name: /创建密钥/ })[0]);

    const editor = await screen.findByRole('dialog');
    await user.type(within(editor).getByLabelText('名称'), 'CI sender');
    await user.click(within(editor).getByRole('button', { name: /创建密钥/ }));

    await waitFor(() => expect(createToken).toHaveBeenCalledWith({
      name: 'CI sender',
      scopes: ['send'],
      expiresAt: null,
      mailboxAccess: 'owner',
      mailboxIds: []
    }));
    expect((await screen.findAllByText(fullToken)).length).toBeGreaterThan(0);

    const reveal = screen.getByRole('dialog', { name: 'API 密钥已创建' });
    await user.click(within(reveal).getByRole('button', { name: /确.*认/ }));

    await waitFor(() => expect(screen.queryByRole('dialog', { name: 'API 密钥已创建' })).toBeNull());
    await waitFor(() => expect(screen.getAllByText(fullToken)).toHaveLength(1));
    const copyButtons = screen.getAllByRole('button', { name: '复制完整 Token CI sender' });
    expect(copyButtons).toHaveLength(1);
    await user.click(copyButtons[0]);
    expect(screen.queryByRole('dialog', { name: 'CI sender' })).toBeNull();
  });

  it('requires selected mailboxes for send or messages:read and loads all choices for admins', async () => {
    const user = userEvent.setup();
    const mailbox = mailboxFixture();
    const created = tokenFixture({
      token: 'mh_selected.full-secret',
      tokenRecoverable: true,
      scopes: ['send', 'messages:read'],
      mailboxAccess: 'selected',
      mailboxIds: [mailbox.id]
    });
    vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [] });
    const loadMailboxes = vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [mailbox] });
    const createToken = vi.spyOn(api, 'createApiToken').mockResolvedValue({ token: created });

    renderPage('admin');
    await waitFor(() => expect(loadMailboxes).toHaveBeenCalledWith('all'));
    await user.click(screen.getAllByRole('button', { name: /创建密钥/ })[0]);
    const editor = await screen.findByRole('dialog');
    expect(within(editor).getByText('邮箱访问范围')).toBeTruthy();

    await user.type(within(editor).getByLabelText('名称'), 'Message reader');
    await user.click(within(editor).getByRole('checkbox', { name: 'messages:read' }));
    expect(await within(editor).findByText('邮箱访问范围')).not.toBeNull();
    await user.click(within(editor).getByRole('radio', { name: '指定邮箱' }));
    await user.click(within(editor).getByLabelText('授权邮箱'));
    await user.click(await screen.findByText(new RegExp(mailbox.address)));
    await user.click(within(editor).getByRole('button', { name: /创建密钥/ }));

    await waitFor(() => expect(createToken).toHaveBeenCalledWith({
      name: 'Message reader',
      scopes: ['send', 'messages:read'],
      expiresAt: null,
      mailboxAccess: 'selected',
      mailboxIds: [mailbox.id]
    }));
  });

  it('allows a send-only token to select an assigned mailbox with send access', async () => {
    const browser = userEvent.setup();
    const base = mailboxFixture();
    const owned = {
      ...base,
      id: 1,
      userId: 1,
      ownerUserId: 1,
      address: 'owned@example.test',
      access: { type: 'owner' as const, permissions: { view: true, receive: true, send: true } }
    };
    const assigned = {
      ...base,
      id: 2,
      address: 'assigned@example.test',
      access: { type: 'assigned' as const, permissions: { view: true, receive: true, send: false } }
    };
    const sendOnly = {
      ...base,
      id: 3,
      address: 'send-only@example.test',
      access: { type: 'assigned' as const, permissions: { view: true, receive: false, send: true } }
    };
    vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [] });
    const loadMailboxes = vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [owned, assigned, sendOnly] });
    const created = tokenFixture({
      name: 'Shared sender',
      token: 'mh_shared.full-secret',
      tokenRecoverable: true,
      scopes: ['send'],
      mailboxAccess: 'selected',
      mailboxIds: [sendOnly.id]
    });
    const createToken = vi.spyOn(api, 'createApiToken').mockResolvedValue({ token: created });

    renderPage('user');
    await waitFor(() => expect(loadMailboxes).toHaveBeenCalledWith('effective'));
    await browser.click(screen.getAllByRole('button', { name: /创建密钥/ })[0]);
    const editor = await screen.findByRole('dialog');
    await browser.type(within(editor).getByLabelText('名称'), 'Shared sender');
    await browser.click(within(editor).getByRole('radio', { name: '指定邮箱' }));
    await browser.click(within(editor).getByLabelText('授权邮箱'));

    expect(await screen.findByText(owned.address)).toBeTruthy();
    expect(screen.getByText(new RegExp(assigned.address))).toBeTruthy();
    expect(screen.getByText(new RegExp(sendOnly.address))).toBeTruthy();
    await browser.click(screen.getByText(new RegExp(sendOnly.address)));
    await browser.click(within(editor).getByRole('button', { name: /创建密钥/ }));

    await waitFor(() => expect(createToken).toHaveBeenCalledWith({
      name: 'Shared sender',
      scopes: ['send'],
      expiresAt: null,
      mailboxAccess: 'selected',
      mailboxIds: [sendOnly.id]
    }));
  });

  it('shows selected mailbox access in the detail drawer for a send-only token', async () => {
    const browser = userEvent.setup();
    const mailbox = mailboxFixture();
    const token = tokenFixture({
      scopes: ['send'],
      mailboxAccess: 'selected',
      mailboxIds: [mailbox.id]
    });
    vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [token] });
    vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [mailbox] });

    renderPage('user');
    await browser.click(await screen.findByText(token.name));

    const detail = await screen.findByRole('dialog');
    expect(within(detail).getByText('指定邮箱 · 1')).toBeTruthy();
    expect(within(detail).getByText(mailbox.address)).toBeTruthy();
  });

  it('preserves selected mailbox access when editing a mailboxes:read token', async () => {
    const browser = userEvent.setup();
    const mailbox = mailboxFixture();
    const token = tokenFixture({
      name: 'Mailbox reader',
      scopes: ['mailboxes:read'],
      mailboxAccess: 'selected',
      mailboxIds: [mailbox.id]
    });
    vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [token] });
    vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [mailbox] });
    const updateToken = vi.spyOn(api, 'updateApiToken').mockResolvedValue({ token });

    renderPage('user');
    await browser.click(await screen.findByRole('button', { name: /编辑.*Mailbox reader/ }));
    const editor = await screen.findByRole('dialog');
    expect(within(editor).getByText('邮箱访问范围')).toBeTruthy();
    expect(within(editor).getByText(mailbox.address)).toBeTruthy();
    await browser.click(within(editor).getByRole('button', { name: /保\s*存/ }));

    await waitFor(() => expect(updateToken).toHaveBeenCalledWith(token.id, {
      name: token.name,
      scopes: ['mailboxes:read'],
      expiresAt: null,
      mailboxAccess: 'selected',
      mailboxIds: [mailbox.id]
    }));
  });

  it('regenerates an unrecoverable legacy token only after destructive confirmation', async () => {
    const user = userEvent.setup();
    const legacy = tokenFixture({ tokenRecoverable: false, token: undefined, name: 'Legacy worker' });
    const rotated = tokenFixture({ tokenRecoverable: true, token: 'mh_rotated.new-secret', name: 'Legacy worker' });
    vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [legacy] });
    vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [] });
    const rotate = vi.spyOn(api, 'rotateApiToken').mockResolvedValue({ token: rotated });

    renderPage();
    await user.click(await screen.findByRole('button', { name: '重新生成 Legacy worker' }));
    expect(screen.getByText('旧 Token 会立即失效，所有仍使用旧值的调用都会失败。此操作无法撤销。')).not.toBeNull();
    const confirmations = screen.getAllByRole('button', { name: '重新生成' });
    await user.click(confirmations[confirmations.length - 1]);

    await waitFor(() => expect(rotate).toHaveBeenCalledWith(9));
    expect(await screen.findByText('API 密钥已重新生成')).not.toBeNull();
    expect((await screen.findAllByText('mh_rotated.new-secret')).length).toBeGreaterThan(0);
  });
});

function renderPage(role: UserRole = 'admin') {
  const context: AppContextValue = {
    user: { id: 1, username: 'operator', email: 'operator@example.test', role, status: 'active' },
    config,
    refreshBootstrap: vi.fn(async () => undefined),
    logout: vi.fn(async () => undefined)
  };
  return render(
    <ConfigProvider theme={{ ...mailhubTheme, token: { ...mailhubTheme.token, motion: false } }}>
      <AntApp>
        <I18nProvider>
          <AppContext.Provider value={context}>
            <MemoryRouter initialEntries={['/integrations/api-keys']}>
              <ApiTokens />
            </MemoryRouter>
          </AppContext.Provider>
        </I18nProvider>
      </AntApp>
    </ConfigProvider>
  );
}

function tokenFixture(overrides: Partial<ApiToken> = {}): ApiToken {
  return {
    id: 9,
    name: 'CI sender',
    tokenPrefix: 'mh_12345678',
    tokenRecoverable: false,
    scopes: ['send'],
    mailboxAccess: 'owner',
    mailboxIds: [],
    status: 'active',
    createdAt: '2026-07-14T00:00:00.000Z',
    ...overrides
  };
}

function mailboxFixture(): InboundMailbox {
  return {
    id: 42,
    userId: 2,
    ownerUserId: 2,
    domainId: 5,
    domain: 'example.test',
    address: 'billing@example.test',
    localPart: 'billing',
    displayName: 'Billing',
    aliases: [],
    forwardTo: [],
    keepForwarded: true,
    quotaMb: 1024,
    passwordSet: true,
    passwordRecoverable: false,
    status: 'active',
    messageCount: 1,
    unreadCount: 1,
    access: { type: 'admin', permissions: { view: true, receive: true, send: true } },
    createdAt: '2026-07-14T00:00:00.000Z',
    updatedAt: '2026-07-14T00:00:00.000Z'
  };
}

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