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, useLocation } 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 {
  AdminMailboxAccessEntry,
  AdminUser,
  InboundMailbox,
  MailboxAccessType,
  MailboxPermissions,
  RuntimeConfig,
  User,
  WebmailLogin
} from '../../src/frontend/types';
import Account from '../../src/pages/Account';
import AdminPage from '../../src/pages/Admin';

describe('Mailbox access UI', () => {
  afterEach(() => vi.restoreAllMocks());

  it('keeps the admin mailbox editor in the URL and saves normalized grants', async () => {
    const browser = userEvent.setup();
    const entry = accessEntry();
    vi.spyOn(api, 'adminUsers').mockResolvedValue({ users: adminUsers });
    vi.spyOn(api, 'adminMailboxAccess').mockResolvedValue({ mailboxes: [entry] });
    const save = vi.spyOn(api, 'saveAdminMailboxAccess').mockImplementation(async (_id, grants) => ({
      mailbox: {
        ...entry,
        grants: grants.map((grant) => ({
          user: adminUsers.find((user) => user.id === grant.userId)!,
          permissions: grant,
          createdAt: '2026-07-18T00:00:00.000Z',
          updatedAt: '2026-07-18T00:00:00.000Z'
        }))
      }
    }));
    const router = createMemoryRouter([{ path: '/admin/:section', element: <AdminPage /> }], {
      initialEntries: ['/admin/mailbox-access']
    });
    renderWithRouter(router, adminContext);

    const configure = await screen.findByRole('button', { name: /配置权限/ });
    expect(configure.style.minHeight).toBe('44px');
    await browser.click(configure);
    await waitFor(() => expect(new URLSearchParams(router.state.location.search).get('mailboxId')).toBe('10'));

    const drawer = await screen.findByRole('dialog');
    expect(within(drawer).getByText(/owner@example\.test/)).toBeTruthy();
    expect(within(drawer).getByText('所有者权限')).toBeTruthy();
    await browser.click(within(drawer).getByRole('checkbox', { name: '收取邮件' }));
    await browser.click(within(drawer).getByRole('checkbox', { name: '查看配置' }));
    await browser.click(within(drawer).getByRole('button', { name: /保\s*存/ }));
    expect(await screen.findByText('每个用户至少选择一项权限，或移除该授权行。')).toBeTruthy();
    expect(save).not.toHaveBeenCalled();
    await browser.click(within(drawer).getByRole('checkbox', { name: '发送邮件' }));
    expect((within(drawer).getByRole('checkbox', { name: '查看配置' }) as HTMLInputElement).checked).toBe(true);
    await browser.click(within(drawer).getByRole('button', { name: /保\s*存/ }));

    await waitFor(() => expect(save).toHaveBeenCalledWith(10, [{
      userId: 2,
      view: true,
      receive: false,
      send: true
    }]));
    expect(await screen.findByText('邮箱权限已保存')).toBeTruthy();

    await browser.click(within(drawer).getByRole('button', { name: 'Close' }));
    await waitFor(() => expect(new URLSearchParams(router.state.location.search).has('mailboxId')).toBe(false));
    await waitFor(() => expect(document.activeElement).toBe(configure));
  });

  it('shows owned and assigned mailboxes in account center with permission-based actions', async () => {
    const browser = userEvent.setup();
    const owned = mailboxFixture(1, 'owned@example.test', 'owner', { view: true, receive: true, send: true }, 2);
    const assigned = mailboxFixture(2, 'assigned@example.test', 'assigned', { view: true, receive: true, send: false }, 1);
    const viewOnly = mailboxFixture(3, 'view-only@example.test', 'assigned', { view: true, receive: false, send: false }, 1);
    const list = vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [owned, assigned, viewOnly] });
    const router = createMemoryRouter([
      { path: '/account', element: <Account /> },
      { path: '/inbox', element: <LocationProbe /> }
    ], { initialEntries: ['/account'] });
    renderWithRouter(router, userContext);

    expect(await screen.findByRole('heading', { name: '账号与邮箱权限' })).toBeTruthy();
    expect(list).toHaveBeenCalledWith('effective');
    expect(screen.getAllByText('管理员分配').length).toBeGreaterThan(0);

    const viewOnlyCard = screen.getByText(viewOnly.address).closest('.ant-card');
    expect(viewOnlyCard).toBeTruthy();
    expect(within(viewOnlyCard as HTMLElement).queryByRole('button', { name: /打开收件箱/ })).toBeNull();
    expect(within(viewOnlyCard as HTMLElement).queryByRole('button', { name: /一键登录 Webmail/ })).toBeNull();
    expect(screen.getAllByRole('button', { name: /管理邮箱/ })).toHaveLength(1);

    const assignedCard = screen.getByText(assigned.address).closest('.ant-card');
    expect(assignedCard).toBeTruthy();
    await browser.click(within(assignedCard as HTMLElement).getByRole('button', { name: /打开收件箱/ }));
    expect(screen.getByTestId('location').textContent).toBe('/inbox?mailboxId=2&folder=INBOX');
  });

  it('exchanges an assigned mailbox for a hidden Webmail POST without exposing the ticket', async () => {
    const browser = userEvent.setup();
    const owner = mailboxFixture(1, 'owned@example.test', 'owner', { view: true, receive: true, send: true }, 2);
    const assigned = mailboxFixture(2, 'assigned@example.test', 'assigned', { view: true, receive: true, send: false }, 1);
    const viewOnly = mailboxFixture(3, 'view-only@example.test', 'assigned', { view: true, receive: false, send: false }, 1);
    const inactive = { ...mailboxFixture(4, 'inactive@example.test', 'assigned', { view: true, receive: true, send: false }, 1), status: 'disabled' };
    vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [owner, assigned, viewOnly, inactive] });

    const webmailLogin: WebmailLogin = {
      action: 'https://mail.us.ss5.xyz/',
      ticket: 'mht_secret-ticket',
      expiresAt: '2026-07-18T01:00:00.000Z'
    };
    let resolveLogin!: (value: { webmailLogin: WebmailLogin }) => void;
    const login = vi.spyOn(api, 'createWebmailLogin').mockReturnValue(new Promise((resolve) => {
      resolveLogin = resolve;
    }));
    let submittedForm: HTMLFormElement | null = null;
    const requestSubmit = vi.spyOn(HTMLFormElement.prototype, 'requestSubmit').mockImplementation(function (this: HTMLFormElement) {
      submittedForm = this.cloneNode(true) as HTMLFormElement;
    });
    const router = createMemoryRouter([{ path: '/account', element: <Account /> }], { initialEntries: ['/account'] });
    renderWithRouter(router, userContext);

    const assignedCard = (await screen.findByText(assigned.address)).closest('.ant-card') as HTMLElement;
    const ownerCard = screen.getByText(owner.address).closest('.ant-card') as HTMLElement;
    const viewOnlyCard = screen.getByText(viewOnly.address).closest('.ant-card') as HTMLElement;
    const inactiveCard = screen.getByText(inactive.address).closest('.ant-card') as HTMLElement;
    const assignedButton = within(assignedCard).getByRole('button', { name: /一键登录 Webmail/ });
    const ownerButton = within(ownerCard).getByRole('button', { name: /一键登录 Webmail/ });
    expect(within(viewOnlyCard).queryByRole('button', { name: /一键登录 Webmail/ })).toBeNull();
    const inactiveButton = within(inactiveCard).getByRole('button', { name: /一键登录 Webmail/ }) as HTMLButtonElement;
    expect(inactiveButton.disabled).toBe(true);
    expect(inactiveButton.title).toContain('邮箱已停用');

    const setItem = vi.spyOn(Storage.prototype, 'setItem');
    await browser.click(assignedButton);
    await waitFor(() => expect(login).toHaveBeenCalledWith(assigned.id));
    expect(assignedButton.classList.contains('ant-btn-loading')).toBe(true);
    expect(ownerButton.classList.contains('ant-btn-loading')).toBe(false);

    await act(async () => resolveLogin({ webmailLogin }));
    await waitFor(() => expect(requestSubmit).toHaveBeenCalledTimes(1));
    expect(submittedForm).not.toBeNull();
    expect(submittedForm!.getAttribute('method')).toBe('POST');
    expect(submittedForm!.action).toBe(webmailLogin.action);
    expect(submittedForm!.target).toBe('_self');
    expect(Object.fromEntries(new FormData(submittedForm!))).toEqual({
      mailhub_ticket: webmailLogin.ticket,
      _task: 'mail',
      _mbox: 'INBOX'
    });
    expect(document.querySelector(`form[action="${webmailLogin.action}"]`)).toBeNull();
    expect(`${router.state.location.pathname}${router.state.location.search}`).toBe('/account');
    expect(window.location.href).not.toContain(webmailLogin.ticket);
    expect(setItem.mock.calls.flat().join(' ')).not.toContain(webmailLogin.ticket);
  });

  it('shows a Webmail login error without navigating away', async () => {
    const browser = userEvent.setup();
    const mailbox = mailboxFixture(1, 'owned@example.test', 'owner', { view: true, receive: true, send: true }, 2);
    vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [mailbox] });
    vi.spyOn(api, 'createWebmailLogin').mockRejectedValue(new Error('Webmail 暂时不可用'));
    const router = createMemoryRouter([{ path: '/account', element: <Account /> }], { initialEntries: ['/account'] });
    renderWithRouter(router, userContext);

    await browser.click(await screen.findByRole('button', { name: /一键登录 Webmail/ }));
    expect(await screen.findByText('Webmail 暂时不可用')).toBeTruthy();
    expect(router.state.location.pathname).toBe('/account');
  });

  it('hides the Webmail shortcut until the server explicitly enables SSO', async () => {
    const mailbox = mailboxFixture(1, 'owned@example.test', 'owner', { view: true, receive: true, send: true }, 2);
    vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [mailbox] });
    const router = createMemoryRouter([{ path: '/account', element: <Account /> }], { initialEntries: ['/account'] });
    renderWithRouter(router, {
      ...userContext,
      config: { ...config, webmailSsoEnabled: false }
    });

    expect(await screen.findByText(mailbox.address)).toBeTruthy();
    expect(screen.queryByRole('button', { name: /一键登录 Webmail/ })).toBeNull();
  });
});

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

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

function accessEntry(): AdminMailboxAccessEntry {
  const mailbox = mailboxFixture(10, 'support@example.test', 'owner', { view: true, receive: true, send: true }, 1);
  return {
    mailbox,
    owner: adminUsers[0],
    grants: [{
      user: adminUsers[1],
      permissions: { view: true, receive: true, send: false },
      createdAt: '2026-07-18T00:00:00.000Z',
      updatedAt: '2026-07-18T00:00:00.000Z'
    }]
  };
}

function mailboxFixture(
  id: number,
  address: string,
  type: MailboxAccessType,
  permissions: MailboxPermissions,
  ownerUserId: number
): InboundMailbox {
  const [, domain] = address.split('@');
  return {
    id,
    userId: ownerUserId,
    ownerUserId,
    domainId: id,
    domain,
    address,
    localPart: address.split('@')[0],
    displayName: '',
    aliases: [],
    forwardTo: [],
    keepForwarded: true,
    quotaMb: 1024,
    passwordSet: true,
    passwordRecoverable: false,
    status: 'active',
    messageCount: permissions.receive ? 3 : null,
    unreadCount: permissions.receive ? 1 : null,
    lastMessageAt: permissions.receive ? '2026-07-18T00:00:00.000Z' : null,
    access: { type, permissions },
    createdAt: '2026-07-18T00:00:00.000Z',
    updatedAt: '2026-07-18T00:00:00.000Z'
  };
}

const adminUsers: AdminUser[] = [
  { id: 1, username: 'owner', email: 'owner@example.test', role: 'admin', status: 'active', resourceCounts: resourceCounts() },
  { id: 2, username: 'reader', email: 'reader@example.test', role: 'user', status: 'active', resourceCounts: resourceCounts() }
];

function contextFor(user: User): AppContextValue {
  return {
    user,
    config,
    refreshBootstrap: vi.fn(async () => undefined),
    logout: vi.fn(async () => undefined)
  };
}

function resourceCounts() {
  return { domains: 0, dnsCredentials: 0, apiTokens: 0, inboundMailboxes: 0, inboundMessages: 0, sendEvents: 0, smtpCredential: 0 };
}

const config: RuntimeConfig = {
  appBaseUrl: 'https://mail.example.test',
  webmailSsoEnabled: true,
  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
};

const adminContext: AppContextValue = contextFor(adminUsers[0]);
const userContext: AppContextValue = contextFor({ id: 2, username: 'reader', email: 'reader@example.test', role: 'user', status: 'active' });
